blob: 2359eeaec68d0cb4efd128957346ed1c024bfc26 [file] [log] [blame]
/*
* Copyright (c) 2012-2013, 2016-2019 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 "cpu/testers/traffic_gen/base.hh"
#include <sstream>
#include "base/intmath.hh"
#include "base/random.hh"
#include "config/have_protobuf.hh"
#include "cpu/testers/traffic_gen/base_gen.hh"
#include "cpu/testers/traffic_gen/dram_gen.hh"
#include "cpu/testers/traffic_gen/dram_rot_gen.hh"
#include "cpu/testers/traffic_gen/exit_gen.hh"
#include "cpu/testers/traffic_gen/idle_gen.hh"
#include "cpu/testers/traffic_gen/linear_gen.hh"
#include "cpu/testers/traffic_gen/random_gen.hh"
#include "cpu/testers/traffic_gen/stream_gen.hh"
#include "debug/Checkpoint.hh"
#include "debug/TrafficGen.hh"
#include "enums/AddrMap.hh"
#include "params/BaseTrafficGen.hh"
#include "sim/sim_exit.hh"
#include "sim/stats.hh"
#include "sim/system.hh"
#if HAVE_PROTOBUF
#include "cpu/testers/traffic_gen/trace_gen.hh"
#endif
using namespace std;
BaseTrafficGen::BaseTrafficGen(const BaseTrafficGenParams* p)
: ClockedObject(p),
system(p->system),
elasticReq(p->elastic_req),
progressCheck(p->progress_check),
noProgressEvent([this]{ noProgress(); }, name()),
nextTransitionTick(0),
nextPacketTick(0),
maxOutstandingReqs(p->max_outstanding_reqs),
port(name() + ".port", *this),
retryPkt(NULL),
retryPktTick(0), blockedWaitingResp(false),
updateEvent([this]{ update(); }, name()),
stats(this),
masterID(system->getMasterId(this)),
streamGenerator(StreamGen::create(p))
{
}
BaseTrafficGen::~BaseTrafficGen()
{
}
Port &
BaseTrafficGen::getPort(const string &if_name, PortID idx)
{
if (if_name == "port") {
return port;
} else {
return ClockedObject::getPort(if_name, idx);
}
}
void
BaseTrafficGen::init()
{
ClockedObject::init();
if (!port.isConnected())
fatal("The port of %s is not connected!\n", name());
}
DrainState
BaseTrafficGen::drain()
{
if (!updateEvent.scheduled()) {
// no event has been scheduled yet (e.g. switched from atomic mode)
return DrainState::Drained;
}
if (retryPkt == NULL) {
// shut things down
nextPacketTick = MaxTick;
nextTransitionTick = MaxTick;
deschedule(updateEvent);
return DrainState::Drained;
} else {
return DrainState::Draining;
}
}
void
BaseTrafficGen::serialize(CheckpointOut &cp) const
{
warn("%s serialization does not keep all traffic generator"
" internal state\n", name());
DPRINTF(Checkpoint, "Serializing BaseTrafficGen\n");
// save ticks of the graph event if it is scheduled
Tick nextEvent = updateEvent.scheduled() ? updateEvent.when() : 0;
DPRINTF(TrafficGen, "Saving nextEvent=%llu\n", nextEvent);
SERIALIZE_SCALAR(nextEvent);
SERIALIZE_SCALAR(nextTransitionTick);
SERIALIZE_SCALAR(nextPacketTick);
}
void
BaseTrafficGen::unserialize(CheckpointIn &cp)
{
warn("%s serialization does not restore all traffic generator"
" internal state\n", name());
// restore scheduled events
Tick nextEvent;
UNSERIALIZE_SCALAR(nextEvent);
if (nextEvent != 0)
schedule(updateEvent, nextEvent);
UNSERIALIZE_SCALAR(nextTransitionTick);
UNSERIALIZE_SCALAR(nextPacketTick);
}
void
BaseTrafficGen::update()
{
// shift our progress-tracking event forward
reschedule(noProgressEvent, curTick() + progressCheck, true);
// if we have reached the time for the next state transition, then
// perform the transition
if (curTick() >= nextTransitionTick) {
transition();
} else {
assert(curTick() >= nextPacketTick);
// get the next packet and try to send it
PacketPtr pkt = activeGenerator->getNextPacket();
// If generating stream/substream IDs are enabled,
// try to pick and assign them to the new packet
if (streamGenerator) {
auto sid = streamGenerator->pickStreamID();
auto ssid = streamGenerator->pickSubStreamID();
pkt->req->setStreamId(sid);
if (streamGenerator->ssidValid()) {
pkt->req->setSubStreamId(ssid);
}
}
// suppress packets that are not destined for a memory, such as
// device accesses that could be part of a trace
if (pkt && system->isMemAddr(pkt->getAddr())) {
stats.numPackets++;
// Only attempts to send if not blocked by pending responses
blockedWaitingResp = allocateWaitingRespSlot(pkt);
if (blockedWaitingResp || !port.sendTimingReq(pkt)) {
retryPkt = pkt;
retryPktTick = curTick();
}
} else if (pkt) {
DPRINTF(TrafficGen, "Suppressed packet %s 0x%x\n",
pkt->cmdString(), pkt->getAddr());
++stats.numSuppressed;
if (!(static_cast<int>(stats.numSuppressed.value()) % 10000))
warn("%s suppressed %d packets with non-memory addresses\n",
name(), stats.numSuppressed.value());
delete pkt;
pkt = nullptr;
}
}
// if we are waiting for a retry or for a response, do not schedule any
// further events, in the case of a transition or a successful send, go
// ahead and determine when the next update should take place
if (retryPkt == NULL) {
nextPacketTick = activeGenerator->nextPacketTick(elasticReq, 0);
scheduleUpdate();
}
}
void
BaseTrafficGen::transition()
{
if (activeGenerator)
activeGenerator->exit();
activeGenerator = nextGenerator();
if (activeGenerator) {
const Tick duration = activeGenerator->duration;
if (duration != MaxTick && duration != 0) {
// we could have been delayed and not transitioned on the
// exact tick when we were supposed to (due to back
// pressure when sending a packet)
nextTransitionTick = curTick() + duration;
} else {
nextTransitionTick = MaxTick;
}
activeGenerator->enter();
nextPacketTick = activeGenerator->nextPacketTick(elasticReq, 0);
} else {
nextPacketTick = MaxTick;
nextTransitionTick = MaxTick;
assert(!updateEvent.scheduled());
}
}
void
BaseTrafficGen::scheduleUpdate()
{
// Has the generator run out of work? In that case, force a
// transition if a transition period hasn't been configured.
while (activeGenerator &&
nextPacketTick == MaxTick && nextTransitionTick == MaxTick) {
transition();
}
if (!activeGenerator)
return;
// schedule next update event based on either the next execute
// tick or the next transition, which ever comes first
const Tick nextEventTick = std::min(nextPacketTick, nextTransitionTick);
DPRINTF(TrafficGen, "Next event scheduled at %lld\n", nextEventTick);
// The next transition tick may be in the past if there was a
// retry, so ensure that we don't schedule anything in the past.
schedule(updateEvent, std::max(curTick(), nextEventTick));
}
void
BaseTrafficGen::start()
{
transition();
scheduleUpdate();
}
void
BaseTrafficGen::recvReqRetry()
{
DPRINTF(TrafficGen, "Received retry\n");
stats.numRetries++;
retryReq();
}
void
BaseTrafficGen::retryReq()
{
assert(retryPkt != NULL);
assert(retryPktTick != 0);
assert(!blockedWaitingResp);
// attempt to send the packet, and if we are successful start up
// the machinery again
if (port.sendTimingReq(retryPkt)) {
retryPkt = NULL;
// remember how much delay was incurred due to back-pressure
// when sending the request, we also use this to derive
// the tick for the next packet
Tick delay = curTick() - retryPktTick;
retryPktTick = 0;
stats.retryTicks += delay;
if (drainState() != DrainState::Draining) {
// packet is sent, so find out when the next one is due
nextPacketTick = activeGenerator->nextPacketTick(elasticReq,
delay);
scheduleUpdate();
} else {
// shut things down
nextPacketTick = MaxTick;
nextTransitionTick = MaxTick;
signalDrainDone();
}
}
}
void
BaseTrafficGen::noProgress()
{
fatal("BaseTrafficGen %s spent %llu ticks without making progress",
name(), progressCheck);
}
BaseTrafficGen::StatGroup::StatGroup(Stats::Group *parent)
: Stats::Group(parent),
ADD_STAT(numSuppressed,
"Number of suppressed packets to non-memory space"),
ADD_STAT(numPackets, "Number of packets generated"),
ADD_STAT(numRetries, "Number of retries"),
ADD_STAT(retryTicks, "Time spent waiting due to back-pressure (ticks)"),
ADD_STAT(bytesRead, "Number of bytes read"),
ADD_STAT(bytesWritten, "Number of bytes written"),
ADD_STAT(totalReadLatency, "Total latency of read requests"),
ADD_STAT(totalWriteLatency, "Total latency of write requests"),
ADD_STAT(totalReads, "Total num of reads"),
ADD_STAT(totalWrites, "Total num of writes"),
ADD_STAT(avgReadLatency, "Avg latency of read requests",
totalReadLatency / totalReads),
ADD_STAT(avgWriteLatency, "Avg latency of write requests",
totalWriteLatency / totalWrites),
ADD_STAT(readBW, "Read bandwidth in bytes/s",
bytesRead / simSeconds),
ADD_STAT(writeBW, "Write bandwidth in bytes/s",
bytesWritten / simSeconds)
{
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createIdle(Tick duration)
{
return std::shared_ptr<BaseGen>(new IdleGen(*this, masterID, duration));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createExit(Tick duration)
{
return std::shared_ptr<BaseGen>(new ExitGen(*this, masterID, duration));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createLinear(Tick duration,
Addr start_addr, Addr end_addr, Addr blocksize,
Tick min_period, Tick max_period,
uint8_t read_percent, Addr data_limit)
{
return std::shared_ptr<BaseGen>(new LinearGen(*this, masterID,
duration, start_addr,
end_addr, blocksize,
system->cacheLineSize(),
min_period, max_period,
read_percent, data_limit));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createRandom(Tick duration,
Addr start_addr, Addr end_addr, Addr blocksize,
Tick min_period, Tick max_period,
uint8_t read_percent, Addr data_limit)
{
return std::shared_ptr<BaseGen>(new RandomGen(*this, masterID,
duration, start_addr,
end_addr, blocksize,
system->cacheLineSize(),
min_period, max_period,
read_percent, data_limit));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createDram(Tick duration,
Addr start_addr, Addr end_addr, Addr blocksize,
Tick min_period, Tick max_period,
uint8_t read_percent, Addr data_limit,
unsigned int num_seq_pkts, unsigned int page_size,
unsigned int nbr_of_banks_DRAM,
unsigned int nbr_of_banks_util,
Enums::AddrMap addr_mapping,
unsigned int nbr_of_ranks)
{
return std::shared_ptr<BaseGen>(new DramGen(*this, masterID,
duration, start_addr,
end_addr, blocksize,
system->cacheLineSize(),
min_period, max_period,
read_percent, data_limit,
num_seq_pkts, page_size,
nbr_of_banks_DRAM,
nbr_of_banks_util,
addr_mapping,
nbr_of_ranks));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createDramRot(Tick duration,
Addr start_addr, Addr end_addr, Addr blocksize,
Tick min_period, Tick max_period,
uint8_t read_percent, Addr data_limit,
unsigned int num_seq_pkts,
unsigned int page_size,
unsigned int nbr_of_banks_DRAM,
unsigned int nbr_of_banks_util,
Enums::AddrMap addr_mapping,
unsigned int nbr_of_ranks,
unsigned int max_seq_count_per_rank)
{
return std::shared_ptr<BaseGen>(new DramRotGen(*this, masterID,
duration, start_addr,
end_addr, blocksize,
system->cacheLineSize(),
min_period, max_period,
read_percent, data_limit,
num_seq_pkts, page_size,
nbr_of_banks_DRAM,
nbr_of_banks_util,
addr_mapping,
nbr_of_ranks,
max_seq_count_per_rank));
}
std::shared_ptr<BaseGen>
BaseTrafficGen::createTrace(Tick duration,
const std::string& trace_file, Addr addr_offset)
{
#if HAVE_PROTOBUF
return std::shared_ptr<BaseGen>(
new TraceGen(*this, masterID, duration, trace_file, addr_offset));
#else
panic("Can't instantiate trace generation without Protobuf support!\n");
#endif
}
bool
BaseTrafficGen::recvTimingResp(PacketPtr pkt)
{
auto iter = waitingResp.find(pkt->req);
panic_if(iter == waitingResp.end(), "%s: "
"Received unexpected response [%s reqPtr=%x]\n",
pkt->print(), pkt->req);
assert(iter->second <= curTick());
if (pkt->isWrite()) {
++stats.totalWrites;
stats.bytesWritten += pkt->req->getSize();
stats.totalWriteLatency += curTick() - iter->second;
} else {
++stats.totalReads;
stats.bytesRead += pkt->req->getSize();
stats.totalReadLatency += curTick() - iter->second;
}
waitingResp.erase(iter);
delete pkt;
// Sends up the request if we were blocked
if (blockedWaitingResp) {
blockedWaitingResp = false;
retryReq();
}
return true;
}