summaryrefslogtreecommitdiff
path: root/src/cpu/testers/traffic_gen/traffic_gen.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/cpu/testers/traffic_gen/traffic_gen.cc')
-rw-r--r--src/cpu/testers/traffic_gen/traffic_gen.cc638
1 files changed, 638 insertions, 0 deletions
diff --git a/src/cpu/testers/traffic_gen/traffic_gen.cc b/src/cpu/testers/traffic_gen/traffic_gen.cc
new file mode 100644
index 000000000..054900b20
--- /dev/null
+++ b/src/cpu/testers/traffic_gen/traffic_gen.cc
@@ -0,0 +1,638 @@
+/*
+ * Copyright (c) 2012 ARM Limited
+ * All rights reserved
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder. You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Thomas Grass
+ * Andreas Hansson
+ * Sascha Bischoff
+ */
+
+#include <sstream>
+
+#include "base/random.hh"
+#include "cpu/testers/traffic_gen/traffic_gen.hh"
+#include "debug/Checkpoint.hh"
+#include "debug/TrafficGen.hh"
+#include "sim/stats.hh"
+#include "sim/system.hh"
+
+using namespace std;
+
+TrafficGen::TrafficGen(const TrafficGenParams* p)
+ : MemObject(p),
+ system(p->system),
+ masterID(system->getMasterId(name())),
+ port(name() + ".port", *this),
+ stateGraph(*this, port, p->config_file, masterID),
+ updateStateGraphEvent(this)
+{
+}
+
+TrafficGen*
+TrafficGenParams::create()
+{
+ return new TrafficGen(this);
+}
+
+MasterPort&
+TrafficGen::getMasterPort(const string& if_name, int idx)
+{
+ if (if_name == "port") {
+ return port;
+ } else {
+ return MemObject::getMasterPort(if_name, idx);
+ }
+}
+
+void
+TrafficGen::init()
+{
+ if (!port.isConnected())
+ fatal("The port of %s is not connected!\n", name());
+
+ Enums::MemoryMode mode = system->getMemoryMode();
+
+ // if the system is in timing mode active the request generator
+ if (mode == Enums::timing) {
+ DPRINTF(TrafficGen, "Timing mode, activating request generator\n");
+
+ // enter initial state
+ stateGraph.enterState(stateGraph.currState);
+ } else {
+ DPRINTF(TrafficGen,
+ "Traffic generator is only active in timing mode\n");
+ }
+}
+
+void
+TrafficGen::initState()
+{
+ // when not restoring from a checkpoint, make sure we kick things off
+ if (system->getMemoryMode() == Enums::timing) {
+ Tick nextStateGraphEvent = stateGraph.nextEventTick();
+ schedule(updateStateGraphEvent, nextStateGraphEvent);
+ } else {
+ DPRINTF(TrafficGen,
+ "Traffic generator is only active in timing mode\n");
+ }
+}
+
+unsigned int
+TrafficGen::drain(Event* drain_event)
+{
+ // @todo we should also stop putting new requests in the queue and
+ // either interrupt the current state or wait for a transition
+ return port.drain(drain_event);
+}
+
+void
+TrafficGen::serialize(ostream &os)
+{
+ DPRINTF(Checkpoint, "Serializing TrafficGen\n");
+
+ // save ticks of the graph event if it is scheduled
+ Tick nextStateGraphEvent = updateStateGraphEvent.scheduled() ?
+ updateStateGraphEvent.when() : 0;
+
+ DPRINTF(TrafficGen, "Saving nextStateGraphEvent=%llu\n",
+ nextStateGraphEvent);
+
+ SERIALIZE_SCALAR(nextStateGraphEvent);
+
+ Tick nextTransitionTick = stateGraph.nextTransitionTick;
+ SERIALIZE_SCALAR(nextTransitionTick);
+
+ // @todo: also serialise the current state, figure out the best
+ // way to drain and restore
+}
+
+void
+TrafficGen::unserialize(Checkpoint* cp, const string& section)
+{
+ // restore scheduled events
+ Tick nextStateGraphEvent;
+ UNSERIALIZE_SCALAR(nextStateGraphEvent);
+ if (nextStateGraphEvent != 0) {
+ schedule(updateStateGraphEvent, nextStateGraphEvent);
+ }
+
+ Tick nextTransitionTick;
+ UNSERIALIZE_SCALAR(nextTransitionTick);
+ stateGraph.nextTransitionTick = nextTransitionTick;
+}
+
+void
+TrafficGen::updateStateGraph()
+{
+ // schedule next update event based on either the next execute
+ // tick or the next transition, which ever comes first
+ Tick nextStateGraphEvent = stateGraph.nextEventTick();
+ DPRINTF(TrafficGen, "Updating state graph, next event at %lld\n",
+ nextStateGraphEvent);
+ schedule(updateStateGraphEvent, nextStateGraphEvent);
+
+ // perform the update associated with the current update event
+ stateGraph.update();
+}
+
+void
+TrafficGen::StateGraph::parseConfig(const string& file_name,
+ MasterID master_id)
+{
+ // keep track of the transitions parsed to create the matrix when
+ // done
+ vector<Transition> transitions;
+
+ // open input file
+ ifstream infile;
+ infile.open(file_name.c_str(), ifstream::in);
+ if (!infile.is_open()) {
+ fatal("Traffic generator %s config file not found at %s\n",
+ owner.name(), file_name);
+ }
+
+ // read line by line and determine the action based on the first
+ // keyword
+ string keyword;
+ string line;
+
+ while (getline(infile, line).good()) {
+ // see if this line is a comment line, and if so skip it
+ if (line.find('#') != 1) {
+ // create an input stream for the tokenization
+ istringstream is(line);
+
+ // determine the keyword
+ is >> keyword;
+
+ if (keyword == "STATE") {
+ // parse the behaviour of this state
+ uint32_t id;
+ Tick duration;
+ string mode;
+
+ is >> id >> duration >> mode;
+
+ if (mode == "TRACE") {
+ string traceFile;
+ Addr addrOffset;
+
+ is >> traceFile >> addrOffset;
+
+ states[id] = new TraceGen(port, master_id, duration,
+ traceFile, addrOffset);
+ DPRINTF(TrafficGen, "State: %d TraceGen\n", id);
+ } else if (mode == "IDLE") {
+ states[id] = new IdleGen(port, master_id, duration);
+ DPRINTF(TrafficGen, "State: %d IdleGen\n", id);
+ } else if (mode == "LINEAR" || mode == "RANDOM") {
+ uint32_t read_percent;
+ Addr start_addr;
+ Addr end_addr;
+ Addr blocksize;
+ Tick min_period;
+ Tick max_period;
+ Addr data_limit;
+
+ is >> read_percent >> start_addr >> end_addr >>
+ blocksize >> min_period >> max_period >> data_limit;
+
+ DPRINTF(TrafficGen, "%s, addr %x to %x, size %d,"
+ " period %d to %d, %d%% reads\n",
+ mode, start_addr, end_addr, blocksize, min_period,
+ max_period, read_percent);
+
+ if (read_percent > 100)
+ panic("%s cannot have more than 100% reads", name());
+
+ if (mode == "LINEAR") {
+ states[id] = new LinearGen(port, master_id,
+ duration, start_addr,
+ end_addr, blocksize,
+ min_period, max_period,
+ read_percent, data_limit);
+ DPRINTF(TrafficGen, "State: %d LinearGen\n", id);
+ } else if (mode == "RANDOM") {
+ states[id] = new RandomGen(port, master_id,
+ duration, start_addr,
+ end_addr, blocksize,
+ min_period, max_period,
+ read_percent, data_limit);
+ DPRINTF(TrafficGen, "State: %d RandomGen\n", id);
+ }
+ } else {
+ fatal("%s: Unknown traffic generator mode: %s",
+ name(), mode);
+ }
+ } else if (keyword == "TRANSITION") {
+ Transition transition;
+
+ is >> transition.from >> transition.to >> transition.p;
+
+ transitions.push_back(transition);
+
+ DPRINTF(TrafficGen, "Transition: %d -> %d\n", transition.from,
+ transition.to);
+ } else if (keyword == "INIT") {
+ // set the initial state as the active state
+ is >> currState;
+
+ DPRINTF(TrafficGen, "Initial state: %d\n", currState);
+ }
+ }
+ }
+
+ // resize and populate state transition matrix
+ transitionMatrix.resize(transitions.size());
+ for (size_t i = 0; i < transitions.size(); i++) {
+ transitionMatrix[i].resize(transitions.size());
+ }
+
+ for (vector<Transition>::iterator t = transitions.begin();
+ t != transitions.end(); ++t) {
+ transitionMatrix[t->from][t->to] = t->p;
+ }
+
+ // ensure the egress edges do not have a probability larger than
+ // one
+ for (size_t i = 0; i < transitions.size(); i++) {
+ double sum = 0;
+ for (size_t j = 0; j < transitions.size(); j++) {
+ sum += transitionMatrix[i][j];
+ }
+
+ // avoid comparing floating point numbers
+ if (abs(sum - 1.0) > 0.001)
+ fatal("%s has transition probability != 1 for state %d\n",
+ name(), i);
+ }
+
+ // close input file
+ infile.close();
+}
+
+void
+TrafficGen::StateGraph::update()
+{
+ // if we have reached the time for the next state transition, then
+ // perform the transition
+ if (curTick() >= nextTransitionTick) {
+ transition();
+ } else {
+ // we are still in the current state and should execute it
+ states[currState]->execute();
+ }
+}
+
+void
+TrafficGen::StateGraph::transition()
+{
+ // exit the current state
+ states[currState]->exit();
+
+ // determine next state
+ double p = random_mt.gen_real1();
+ assert(currState < transitionMatrix.size());
+ double cumulative = transitionMatrix[currState][0];
+ size_t i = 1;
+ while (p < cumulative && i != transitionMatrix[currState].size()) {
+ cumulative += transitionMatrix[currState][i];
+ ++i;
+ }
+ enterState(i);
+}
+
+void
+TrafficGen::StateGraph::enterState(uint32_t newState)
+{
+ DPRINTF(TrafficGen, "Transition to state %d\n", newState);
+
+ currState = newState;
+ nextTransitionTick += states[currState]->duration;
+ states[currState]->enter();
+}
+
+TrafficGen::StateGraph::BaseGen::BaseGen(QueuedMasterPort& _port,
+ MasterID master_id,
+ Tick _duration)
+ : port(_port), masterID(master_id), duration(_duration)
+{
+}
+
+void
+TrafficGen::StateGraph::LinearGen::enter()
+{
+ // reset the address and the data counter
+ nextAddr = startAddr;
+ dataManipulated = 0;
+
+ // this test only needs to happen once, but cannot be performed
+ // before init() is called and the ports are connected
+ if (port.deviceBlockSize() && blocksize > port.deviceBlockSize())
+ fatal("TrafficGen %s block size (%d) is larger than port"
+ " block size (%d)\n", blocksize, port.deviceBlockSize());
+
+}
+
+void
+TrafficGen::StateGraph::LinearGen::execute()
+{
+ // choose if we generate a read or a write here
+ bool isRead = random_mt.random<uint8_t>(0, 100) < readPercent;
+
+ if (readPercent == 0)
+ assert(!isRead);
+
+ DPRINTF(TrafficGen, "LinearGen::execute: %c to addr %x, size %d\n",
+ isRead ? 'r' : 'w', nextAddr, blocksize);
+
+ // Create new request
+ Request::Flags flags;
+ Request *req = new Request(nextAddr, blocksize, flags, masterID);
+
+ PacketPtr pkt = new Packet(req, isRead ? MemCmd::ReadReq :
+ MemCmd::WriteReq);
+
+ uint8_t* pkt_data = new uint8_t[req->getSize()];
+ pkt->dataDynamicArray(pkt_data);
+
+ if (!isRead) {
+ memset(pkt_data, 0xA, req->getSize());
+ }
+
+ port.schedTimingReq(pkt, curTick());
+
+ // increment the address
+ nextAddr += blocksize;
+
+ // Add the amount of data manipulated to the total
+ dataManipulated += blocksize;
+}
+
+Tick
+TrafficGen::StateGraph::LinearGen::nextExecuteTick()
+{
+ // If we have reached the end of the address space, reset the
+ // address to the start of the range
+ if (nextAddr + blocksize > endAddr) {
+ DPRINTF(TrafficGen, "Wrapping address to the start of "
+ "the range\n");
+ nextAddr = startAddr;
+ }
+
+ // Check to see if we have reached the data limit. If dataLimit is
+ // zero we do not have a data limit and therefore we will keep
+ // generating requests for the entire residency in this state.
+ if (dataLimit && dataManipulated >= dataLimit) {
+ DPRINTF(TrafficGen, "Data limit for LinearGen reached.\n");
+ // there are no more requests, therefore return MaxTick
+ return MaxTick;
+ } else {
+ // return the time when the next request should take place
+ return curTick() + random_mt.random<Tick>(minPeriod, maxPeriod);
+ }
+}
+
+void
+TrafficGen::StateGraph::RandomGen::enter()
+{
+ // reset the counter to zero
+ dataManipulated = 0;
+
+ // this test only needs to happen once, but cannot be performed
+ // before init() is called and the ports are connected
+ if (port.deviceBlockSize() && blocksize > port.deviceBlockSize())
+ fatal("TrafficGen %s block size (%d) is larger than port"
+ " block size (%d)\n", name(), blocksize, port.deviceBlockSize());
+}
+
+void
+TrafficGen::StateGraph::RandomGen::execute()
+{
+ // choose if we generate a read or a write here
+ bool isRead = random_mt.random<uint8_t>(0, 100) < readPercent;
+
+ if (readPercent == 0)
+ assert(!isRead);
+
+ // address of the request
+ Addr addr = random_mt.random<Addr>(startAddr, endAddr - 1);
+
+ // round down to start address of block
+ addr -= addr % blocksize;
+
+ DPRINTF(TrafficGen, "RandomGen::execute: %c to addr %x, size %d\n",
+ isRead ? 'r' : 'w', addr, blocksize);
+
+ // create new request packet
+ Request::Flags flags;
+ Request *req = new Request(addr, blocksize, flags, masterID);
+
+ PacketPtr pkt = new Packet(req, isRead ? MemCmd::ReadReq :
+ MemCmd::WriteReq);
+
+ uint8_t* pkt_data = new uint8_t[req->getSize()];
+ pkt->dataDynamicArray(pkt_data);
+
+ if (!isRead) {
+ memset(pkt_data, 0xA, req->getSize());
+ }
+
+ port.schedTimingReq(pkt, curTick());
+
+ // Add the amount of data manipulated to the total
+ dataManipulated += blocksize;
+}
+
+Tick
+TrafficGen::StateGraph::RandomGen::nextExecuteTick()
+{
+ // Check to see if we have reached the data limit. If dataLimit is
+ // zero we do not have a data limit and therefore we will keep
+ // generating requests for the entire residency in this state.
+ if (dataLimit && dataManipulated >= dataLimit)
+ {
+ DPRINTF(TrafficGen, "Data limit for RandomGen reached.\n");
+ // No more requests. Return MaxTick.
+ return MaxTick;
+ } else {
+ // Return the time when the next request should take place.
+ return curTick() + random_mt.random<Tick>(minPeriod, maxPeriod);
+ }
+}
+
+Tick
+TrafficGen::StateGraph::TraceGen::nextExecuteTick() {
+ // We need to look at the next line to calculate the next time an
+ // event occurs, or potentially return MaxTick to signal that
+ // nothing has to be done.
+ string buffer;
+ if (!traceComplete && trace.good()){
+ getline(trace, buffer);
+ DPRINTF(TrafficGen, "Input trace: %s\n", buffer);
+ } else {
+ // We are at the end of the file, thus we have no more data in
+ // the trace Return MaxTick to signal that there will be no
+ // more transactions in this active period for the state.
+ return MaxTick;
+ }
+
+ //Reset the nextElement to the default values
+ currElement = nextElement;
+ nextElement.clear();
+
+ // Check that we have something to process. This assume no EOF at
+ // the end of the line.
+ if (buffer.size() > 0 && !trace.eof()) {
+ istringstream iss(buffer);
+
+ char rOrW, ch;
+ iss >> rOrW;
+ iss >> ch; assert(ch == ',');
+ iss >> nextElement.addr;
+ iss >> ch; assert(ch == ',');
+ iss >> nextElement.blocksize;
+ iss >> ch; assert(ch == ',');
+ iss >> nextElement.tick;
+
+ if (rOrW == 'r') {
+ nextElement.cmd = MemCmd::ReadReq;
+ } else if (rOrW == 'w') {
+ nextElement.cmd = MemCmd::WriteReq;
+ } else {
+ fatal("Incorrect trace file format!\n");
+ }
+ }
+
+ // Check that we have a valid request
+ if (!nextElement.isValid()) {
+ // If it is not valid, assume that we have reached the end of
+ // the trace. Even if this is not the case, we do not know
+ // what to do with the request as it makes no sense.
+ if (trace.good()) {
+ // Trace is good, therefore we are not at the end of the
+ // file. This means that the input trace cannot be read
+ // correctly or it contains data that makes no sense.
+ warn("Unable to read the trace file format\n");
+ warn("%s", buffer);
+ }
+
+ traceComplete = true;
+ return MaxTick;
+ }
+
+ DPRINTF(TrafficGen, "currElement: %c addr %d size %d tick %d (%d)\n",
+ currElement.cmd.isRead() ? 'r' : 'w',
+ currElement.addr,
+ currElement.blocksize,
+ currElement.tick + tickOffset,
+ currElement.tick);
+
+ DPRINTF(TrafficGen, "nextElement: %c addr %d size %d tick %d (%d)\n",
+ nextElement.cmd.isRead() ? 'r' : 'w',
+ nextElement.addr,
+ nextElement.blocksize,
+ nextElement.tick + tickOffset,
+ nextElement.tick);
+
+ return tickOffset + nextElement.tick;
+}
+
+void
+TrafficGen::StateGraph::TraceGen::enter() {
+ // update the trace offset to the time where the state was entered.
+ tickOffset = curTick();
+
+ // seek to the start of the input trace file
+ trace.seekg(0, ifstream::beg);
+ trace.clear();
+
+ // clear everything
+ nextElement.clear();
+ currElement.clear();
+
+ traceComplete = false;
+}
+
+void
+TrafficGen::StateGraph::TraceGen::execute() {
+ // it is the responsibility of nextExecuteTick to prevent the
+ // state graph from executing the state if it should not
+ assert(currElement.isValid());
+
+ DPRINTF(TrafficGen, "TraceGen::execute: %c %d %d %d\n",
+ currElement.cmd.isRead() ? 'r' : 'w',
+ currElement.addr,
+ currElement.blocksize,
+ currElement.tick);
+
+ Request::Flags flags;
+ Request *req = new Request(currElement.addr + addrOffset,
+ currElement.blocksize, flags, masterID);
+
+ PacketPtr pkt = new Packet(req, currElement.cmd);
+
+ uint8_t* pkt_data = new uint8_t[req->getSize()];
+ pkt->dataDynamicArray(pkt_data);
+
+ if (currElement.cmd.isWrite()) {
+ memset(pkt_data, 0xA, req->getSize());
+ }
+
+ port.schedTimingReq(pkt, curTick());
+}
+
+void
+TrafficGen::StateGraph::TraceGen::exit() {
+ // Check if we reached the end of the trace file. If we did not
+ // then we want to generate a warning stating that not the entire
+ // trace was played.
+ if (!trace.eof()) {
+ warn("Trace player %s was unable to replay the entire trace!\n",
+ name());
+ }
+
+ // clear any previous error flags for the input trace file
+ trace.clear();
+}
+
+bool
+TrafficGen::TrafficGenPort::recvTimingResp(PacketPtr pkt)
+{
+ delete pkt->req;
+ delete pkt;
+
+ return true;
+}