Merge branch 'master' of git.rhrk.uni-kl.de:EIT-Wehn/dram.vp.system
This commit is contained in:
@@ -8,8 +8,8 @@ CONFIG(qwt){
|
||||
|
||||
|
||||
CONFIG(python){
|
||||
# LIBS += -L/opt/python/lib -lpython3.4m
|
||||
# INCLUDEPATH += /opt/python/include/python3.4m
|
||||
LIBS += -lpython3.3m
|
||||
INCLUDEPATH += /usr/include/python3.3
|
||||
LIBS += -L/opt/python/lib -lpython3.4m
|
||||
INCLUDEPATH += /opt/python/include/python3.4m
|
||||
# LIBS += -lpython3.3m
|
||||
# INCLUDEPATH += /usr/include/python3.3
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
TEMPLATE = app
|
||||
TEMPLATE = app
|
||||
CONFIG += console
|
||||
CONFIG -= app_bundle
|
||||
CONFIG -= qt
|
||||
@@ -8,7 +8,7 @@ LIBS += -L/opt/systemc/lib-linux64 -lsystemc
|
||||
LIBS += -L/opt/boost/lib -lboost_filesystem -lboost_system
|
||||
LIBS += -L/opt/sqlite3/lib -lsqlite3
|
||||
LIBS += -lpthread
|
||||
LIBS += -L/opt/xerces-c-3.1.1/lib -lxerces-c
|
||||
LIBS += -L/opt/xerces/lib -lxerces-c
|
||||
LIBS += -L../src/common/third_party/DRAMPower/src/ -ldrampowerxml
|
||||
LIBS += -L../src/common/third_party/DRAMPower/src/ -ldrampower
|
||||
|
||||
@@ -122,5 +122,6 @@ HEADERS += \
|
||||
../src/simulation/Arbiter.h \
|
||||
../src/common/libDRAMPower.h \
|
||||
../src/controller/core/RowBufferStates.h \
|
||||
../src/controller/scheduler/readwritegrouper.h
|
||||
../src/controller/scheduler/readwritegrouper.h \
|
||||
../src/simulation/ReorderBuffer.h
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!--
|
||||
<dramconfig>
|
||||
<addressmap length="29">
|
||||
<channel from="27" to="28" />
|
||||
<bank from="24" to="26" />
|
||||
<row from="11" to="23" />
|
||||
<colum from="4" to="10" />
|
||||
<bytes from="0" to="3" />
|
||||
</addressmap>
|
||||
</dramconfig>
|
||||
-->
|
||||
<dramconfig>
|
||||
<addressmap length="29">
|
||||
<channel from="27" to="28" />
|
||||
<row from="15" to="26" />
|
||||
<bank from="11" to="14" />
|
||||
<colum from="4" to="10" />
|
||||
<bytes from="0" to="3" />
|
||||
<!-- <channel from="27" to="28" />
|
||||
<row from="14" to="26" />
|
||||
<bytes from="10" to="13" />
|
||||
<colum from="3" to="9" />
|
||||
<bank from="0" to="2" /> -->
|
||||
|
||||
</addressmap>
|
||||
</dramconfig>
|
||||
@@ -1,6 +1,6 @@
|
||||
<memspec>
|
||||
<memconfig>
|
||||
<parameter id="bankwiseLogic" type="bool" value="1" />
|
||||
<parameter id="bankwiseLogic" type="bool" value="0" />
|
||||
<parameter id="openPagePolicy" type="bool" value="1" />
|
||||
<parameter id="adaptiveOpenPagePolicy" type="bool" value="0" />
|
||||
<parameter id="refreshAwareScheduling" type="bool" value="1" />
|
||||
@@ -9,7 +9,6 @@
|
||||
<parameter id="capsize" type="uint" value="5" />
|
||||
<parameter id="powerDownMode" type="string" value="Staggered" />
|
||||
<parameter id="powerDownTimeout" type="uint" value="100" />
|
||||
|
||||
<parameter id="databaseRecordingEnabled" type="bool" value="1" />
|
||||
</memconfig>
|
||||
</memspec>
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
<simulation id = "simbatch3">
|
||||
<memspec>WideIO.xml</memspec>
|
||||
|
||||
<!-- <addressmapping>am_highHits.xml</addressmapping>
|
||||
<addressmapping>am_highPara.xml</addressmapping> -->
|
||||
<!-- <addressmapping>am_lowPara.xml</addressmapping>
|
||||
-->
|
||||
<addressmapping>am_wideio.xml</addressmapping>
|
||||
|
||||
<memconfigs>
|
||||
<memconfig>fifo.xml</memconfig>
|
||||
<!-- <memconfig>fr_fcfs.xml</memconfig>
|
||||
<memconfig>par_bs.xml</memconfig>
|
||||
--> </memconfigs>
|
||||
<trace-setups>
|
||||
<memconfigs>
|
||||
<!-- <memconfig>fr_fcfs.xml</memconfig>
|
||||
--> <memconfig>grouper.xml</memconfig>
|
||||
</memconfigs>
|
||||
<trace-setups>
|
||||
|
||||
|
||||
<trace-setup id="media">
|
||||
<device clkMhz="800">small.stl</device>
|
||||
<device clkMhz="800">mediabench-fractal_32.stl</device>
|
||||
</trace-setup>
|
||||
|
||||
</trace-setups>
|
||||
|
||||
@@ -119,10 +119,7 @@ void Controller<BUSWIDTH>::buildScheduler()
|
||||
|
||||
if (selectedScheduler == "FR_FCFS")
|
||||
{
|
||||
|
||||
Scheduler* s = new FR_FCFS(*controllerCore, Configuration::getInstance().RefreshAwareScheduling,
|
||||
Configuration::getInstance().AdaptiveOpenPagePolicy);
|
||||
scheduler = new ReadWriteGrouper(s);
|
||||
scheduler = new FR_FCFS(*controllerCore, Configuration::getInstance().RefreshAwareScheduling,Configuration::getInstance().AdaptiveOpenPagePolicy);
|
||||
}
|
||||
else if (selectedScheduler == "PAR_BS")
|
||||
{
|
||||
@@ -130,7 +127,13 @@ void Controller<BUSWIDTH>::buildScheduler()
|
||||
Configuration::getInstance().Capsize);
|
||||
}
|
||||
else if (selectedScheduler == "FIFO")
|
||||
{
|
||||
scheduler = new Fifo(*controllerCore);
|
||||
}
|
||||
else if (selectedScheduler == "Grouper")
|
||||
{
|
||||
scheduler = new ReadWriteGrouper(*controllerCore);
|
||||
}
|
||||
else
|
||||
reportFatal(name(), "unsupported scheduler: " + selectedScheduler);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace core{
|
||||
|
||||
struct RefreshTiming
|
||||
{
|
||||
RefreshTiming() {};
|
||||
RefreshTiming(sc_time tRFC, sc_time tREFI) : tRFC(tRFC), tREFI(tREFI) {};
|
||||
RefreshTiming() {}
|
||||
RefreshTiming(sc_time tRFC, sc_time tREFI) : tRFC(tRFC), tREFI(tREFI) {}
|
||||
sc_time tRFC;
|
||||
sc_time tREFI;
|
||||
};
|
||||
|
||||
@@ -53,6 +53,12 @@ void PrechargeAllChecker::delayToSatisfyConstraints(ScheduledCommand& command) c
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledCommand lastActivate = state.getLastCommand(Command::Activate, command.getBank());
|
||||
if (lastActivate.isValidCommand())
|
||||
{
|
||||
command.delayToMeetConstraint(lastActivate.getStart(), config.Timings.tRAS);
|
||||
}
|
||||
|
||||
state.bus.moveCommandToNextFreeSlot(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,19 @@ unsigned int FR_FCFS::getNumberOfQueuedPayloads()
|
||||
return numberOfQueuedPaylods;
|
||||
}
|
||||
|
||||
bool FR_FCFS::containsPayloadTragetingSameAddress(gp *payload)
|
||||
{
|
||||
Bank bank = DramExtension::getExtension(payload).getBank();
|
||||
Row row = DramExtension::getExtension(payload).getRow();
|
||||
|
||||
for(gp* bufferedPayload: buffer[bank])
|
||||
{
|
||||
if(DramExtension::getExtension(bufferedPayload).getRow() == row)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<gp*> FR_FCFS::findRowHits(Bank bank, Row row)
|
||||
{
|
||||
vector<gp*> found;
|
||||
|
||||
@@ -25,6 +25,8 @@ public:
|
||||
gp* popOldest(Bank bank);
|
||||
unsigned int getNumberOfQueuedPayloads();
|
||||
|
||||
//used by read/write grouper
|
||||
bool containsPayloadTragetingSameAddress(gp* payload);
|
||||
private:
|
||||
std::vector<gp*> findRowHits(Bank bank, Row row);
|
||||
std::map<Bank,std::list<gp*>> buffer;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#include "Scheduler.h"
|
||||
#include "../../common/DebugManager.h"
|
||||
#include <iostream>
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace scheduler;
|
||||
|
||||
std::string Scheduler::sendername = "scheduler";
|
||||
|
||||
void Scheduler::printDebugMessage(std::string message)
|
||||
{
|
||||
//cout << "scheduler: " << message << std::endl;
|
||||
DebugManager::getInstance().printDebugMessage(Scheduler::sendername, message);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,121 +1,150 @@
|
||||
#include "readwritegrouper.h"
|
||||
#include "../../common/DebugManager.h"
|
||||
|
||||
namespace scheduler{
|
||||
|
||||
using namespace tlm;
|
||||
using namespace std;
|
||||
|
||||
ReadWriteGrouper::ReadWriteGrouper(Scheduler *scheduler):
|
||||
scheduler(scheduler), mode(Mode::read)
|
||||
ReadWriteGrouper::ReadWriteGrouper(core::ControllerCore& controllerCore): controllerCore(controllerCore)
|
||||
{
|
||||
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
}
|
||||
|
||||
ReadWriteGrouper::~ReadWriteGrouper()
|
||||
{
|
||||
delete scheduler;
|
||||
|
||||
}
|
||||
|
||||
void ReadWriteGrouper::schedule(gp *payload)
|
||||
{
|
||||
tlm_command command = payload->get_command();
|
||||
|
||||
if(batches.size() > 2)
|
||||
{
|
||||
if(command == TLM_READ_COMMAND)
|
||||
{
|
||||
//printDebugMessage("Scheduling read");
|
||||
|
||||
if(schedulingReadCausesHazardWithQueuedWrite(payload))
|
||||
{
|
||||
printDebugMessage("Scheduling read causes hazard with queued write");
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
}
|
||||
|
||||
getLatestReadBatch().schedule(payload);
|
||||
}
|
||||
else if(command == TLM_WRITE_COMMAND)
|
||||
{
|
||||
//printDebugMessage("Scheduling write");
|
||||
getLatestWriteBatch().schedule(payload);
|
||||
}
|
||||
}
|
||||
else if(batches.size() == 2)
|
||||
{
|
||||
if(command == TLM_READ_COMMAND)
|
||||
{
|
||||
//printDebugMessage("Scheduling read");
|
||||
|
||||
if(getLatestReadBatch().hasPayloads() && schedulingReadCausesHazardWithQueuedWrite(payload))
|
||||
{
|
||||
printDebugMessage("Scheduling read causes hazard with queued write");
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
}
|
||||
else if(!getLatestReadBatch().hasPayloads() && getLatestWriteBatch().hasPayloads())
|
||||
{
|
||||
printDebugMessage("Scheduling read, but there are writes to be processed first");
|
||||
batches.erase(batches.begin());
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
batches.push_back(shared_ptr<FR_FCFS>(new FR_FCFS(controllerCore,true,false)));
|
||||
}
|
||||
getLatestReadBatch().schedule(payload);
|
||||
|
||||
}
|
||||
else if(command == TLM_WRITE_COMMAND)
|
||||
{
|
||||
//printDebugMessage("Scheduling write");
|
||||
getLatestWriteBatch().schedule(payload);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sc_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
gp *ReadWriteGrouper::getNextPayload()
|
||||
{
|
||||
if(batches.size() > 2)
|
||||
{
|
||||
return batches.front()->getNextPayload();
|
||||
}
|
||||
else if(batches.size() == 2)
|
||||
{
|
||||
if(getLatestReadBatch().hasPayloads())
|
||||
return getLatestReadBatch().getNextPayload();
|
||||
else if(getLatestWriteBatch().hasPayloads())
|
||||
return getLatestWriteBatch().getNextPayload();
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
sc_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ReadWriteGrouper::removePayload(gp *payload)
|
||||
{
|
||||
scheduler->removePayload(payload);
|
||||
|
||||
//if scheduler is empty now
|
||||
if(!scheduler->hasPayloads())
|
||||
if(batches.size() > 2)
|
||||
{
|
||||
if(mode == Mode::read && !writeQueue.empty())
|
||||
switchToWriteMode();
|
||||
else if(mode == Mode::readToWrite)
|
||||
switchToWriteMode();
|
||||
batches.front()->removePayload(payload);
|
||||
if(!batches.front()->hasPayloads())
|
||||
batches.erase(batches.begin());
|
||||
}
|
||||
else if(batches.size() == 2)
|
||||
{
|
||||
if(payload->is_read())
|
||||
getLatestReadBatch().removePayload(payload);
|
||||
else
|
||||
switchToReadMode();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadWriteGrouper::schedule(gp *payload)
|
||||
{
|
||||
tlm_command command = payload->get_command();
|
||||
|
||||
//in read mode hazards could occur by letting a read pass a queued write
|
||||
if(mode == Mode::read)
|
||||
{
|
||||
if(command == TLM_READ_COMMAND)
|
||||
{
|
||||
//if scheduling the read would cause a hazard switch to readToWriteMode and put the read into the readQueue
|
||||
if(schedulingReadCausesHazard(payload))
|
||||
{
|
||||
switchToReadToWriteMode();
|
||||
readQueue.push_back(payload);
|
||||
}
|
||||
else
|
||||
scheduler->schedule(payload);
|
||||
getLatestWriteBatch().removePayload(payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeQueue.push_back(payload);
|
||||
if(!scheduler->hasPayloads())
|
||||
//there are no reads in the scheduler, so switch directly to write mode
|
||||
switchToWriteMode();
|
||||
sc_assert(false);
|
||||
}
|
||||
}
|
||||
else if(mode == Mode::readToWrite)
|
||||
{
|
||||
if(command == TLM_READ_COMMAND)
|
||||
readQueue.push_back(payload);
|
||||
else
|
||||
writeQueue.push_back(payload);
|
||||
}
|
||||
else if(mode == Mode::write)
|
||||
{
|
||||
if(command == TLM_READ_COMMAND)
|
||||
readQueue.push_back(payload);
|
||||
else
|
||||
scheduler->schedule(payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gp *ReadWriteGrouper::getNextPayload()
|
||||
{
|
||||
return scheduler->getNextPayload();
|
||||
}
|
||||
|
||||
bool ReadWriteGrouper::hasPayloads()
|
||||
{
|
||||
return scheduler->hasPayloads();
|
||||
if(batches.size() > 2)
|
||||
return true;
|
||||
else if(batches.size() == 2)
|
||||
return (getLatestReadBatch().hasPayloads() || getLatestWriteBatch().hasPayloads());
|
||||
else
|
||||
sc_assert(false);
|
||||
}
|
||||
|
||||
|
||||
bool ReadWriteGrouper::schedulingReadCausesHazard(gp *payload)
|
||||
bool ReadWriteGrouper::schedulingReadCausesHazardWithQueuedWrite(gp *payload)
|
||||
{
|
||||
sc_assert(payload->is_read());
|
||||
for(gp* write: writeQueue)
|
||||
{
|
||||
if(payload->get_address()==write->get_address())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return getLatestWriteBatch().containsPayloadTragetingSameAddress(payload);
|
||||
}
|
||||
|
||||
void ReadWriteGrouper::switchToReadMode()
|
||||
FR_FCFS &ReadWriteGrouper::getLatestWriteBatch()
|
||||
{
|
||||
mode = Mode::read;
|
||||
for(gp* read: readQueue)
|
||||
scheduler->schedule(read);
|
||||
readQueue.clear();
|
||||
return *batches[batches.size()-1];
|
||||
}
|
||||
|
||||
void ReadWriteGrouper::switchToWriteMode()
|
||||
FR_FCFS &ReadWriteGrouper::getLatestReadBatch()
|
||||
{
|
||||
mode = Mode::write;
|
||||
for(gp* write: writeQueue)
|
||||
scheduler->schedule(write);
|
||||
writeQueue.clear();
|
||||
}
|
||||
|
||||
void ReadWriteGrouper::switchToReadToWriteMode()
|
||||
{
|
||||
mode = Mode::readToWrite;
|
||||
return *batches[batches.size()-2];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#ifndef READWRITEGROUPER_H
|
||||
#define READWRITEGROUPER_H
|
||||
#include "Scheduler.h"
|
||||
#include "Fr_Fcfs.h"
|
||||
#include "../core/ControllerCore.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace scheduler{
|
||||
|
||||
/* Scheduler that batches reads and writes to reduce the overhead when switching from
|
||||
* read to write mode (read-to-write constraints)
|
||||
* The scheduler prioritizes reads. It switches to write mode, when stalling a write would induce a data-hazard
|
||||
* or when there are no reads to process.
|
||||
*/
|
||||
class ReadWriteGrouper : public Scheduler
|
||||
{
|
||||
public:
|
||||
ReadWriteGrouper(Scheduler *scheduler);
|
||||
ReadWriteGrouper(core::ControllerCore& controllerCore);
|
||||
~ReadWriteGrouper();
|
||||
virtual void schedule(gp* payload) override;
|
||||
virtual bool hasPayloads() override;
|
||||
@@ -21,17 +20,19 @@ public:
|
||||
virtual void removePayload(gp* payload) override;
|
||||
|
||||
private:
|
||||
Scheduler *scheduler;
|
||||
std::vector<gp*> readQueue, writeQueue;
|
||||
//Mode readToWrite is used to process the remaining reads in the readScheduler before
|
||||
//switching to write mode (this is used when a potential hazard causes the scheduler to switch modes)
|
||||
enum class Mode{read,readToWrite, write};
|
||||
Mode mode;
|
||||
// contains batches of requests
|
||||
// last element always contains writes
|
||||
// next-to-last element always contains reads
|
||||
// there are always at least two batches
|
||||
// if there are more than two batches, batches[0] is never empty and
|
||||
// getNextPayload and removePayload are forwarded to batches[0]
|
||||
std::vector<std::shared_ptr<FR_FCFS>> batches;
|
||||
core::ControllerCore& controllerCore;
|
||||
|
||||
bool schedulingReadCausesHazardWithQueuedWrite(gp* payload);
|
||||
FR_FCFS& getLatestWriteBatch();
|
||||
FR_FCFS& getLatestReadBatch();
|
||||
|
||||
bool schedulingReadCausesHazard(gp* payload);
|
||||
void switchToReadMode();
|
||||
void switchToWriteMode();
|
||||
void switchToReadToWriteMode();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -45,10 +45,12 @@ public:
|
||||
private:
|
||||
tlm_utils::peq_with_cb_and_phase<Arbiter> payloadEventQueue;
|
||||
bool channelIsFree;
|
||||
deque<tlm_generic_payload* > backpressure;
|
||||
//used to account for the request_accept_delay in the dram controllers
|
||||
deque<tlm_generic_payload* > pendingRequests;
|
||||
//used to account for the response_accept_delay in the initiators (traceplayer,core etc.)
|
||||
deque<tlm_generic_payload* > receivedResponses[NUMBER_OF_THREADS];
|
||||
|
||||
|
||||
// Initiated by dram
|
||||
// Initiated by dram side
|
||||
tlm_sync_enum nb_transport_bw(tlm_generic_payload& payload, tlm_phase& phase, sc_time& bwDelay)
|
||||
{
|
||||
TlmRecorder::getInstance().recordPhase(payload, phase, bwDelay + sc_time_stamp());
|
||||
@@ -56,7 +58,7 @@ private:
|
||||
return TLM_ACCEPTED;
|
||||
}
|
||||
|
||||
// Initiated by senders
|
||||
// Initiated by initiator side
|
||||
tlm_sync_enum nb_transport_fw(int socketId, tlm_generic_payload& payload, tlm_phase& phase,
|
||||
sc_time& fwDelay)
|
||||
{
|
||||
@@ -66,7 +68,9 @@ private:
|
||||
payload.acquire();
|
||||
}
|
||||
else if(phase == END_RESP)
|
||||
{
|
||||
payload.release();
|
||||
}
|
||||
|
||||
payloadEventQueue.notify(payload, phase, fwDelay);
|
||||
return TLM_ACCEPTED;
|
||||
@@ -74,6 +78,9 @@ private:
|
||||
|
||||
void peqCallback(tlm_generic_payload& payload, const tlm_phase& phase)
|
||||
{
|
||||
unsigned int initiatorSocket = DramExtension::getExtension(payload).getThread().ID()-1;
|
||||
|
||||
|
||||
//Phases initiated by intiator side
|
||||
if (phase == BEGIN_REQ)
|
||||
{
|
||||
@@ -84,32 +91,40 @@ private:
|
||||
}
|
||||
else
|
||||
{
|
||||
backpressure.push_back(&payload);
|
||||
pendingRequests.push_back(&payload);
|
||||
}
|
||||
}
|
||||
|
||||
else if (phase == END_RESP)
|
||||
{
|
||||
sendToChannel(payload, phase, SC_ZERO_TIME );
|
||||
sendToChannel(payload, phase, SC_ZERO_TIME);
|
||||
receivedResponses[initiatorSocket].pop_front();
|
||||
if(!receivedResponses[initiatorSocket].empty())
|
||||
{
|
||||
tlm_generic_payload* payloadToSend = receivedResponses[initiatorSocket].front();
|
||||
sendToInitiator(initiatorSocket,*payloadToSend,BEGIN_RESP,SC_ZERO_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
//Phases initiated by dram side
|
||||
else if (phase == END_REQ)
|
||||
{
|
||||
channelIsFree = true;
|
||||
sendToInitiator(DramExtension::getExtension(payload).getThread().ID()-1, payload, phase, SC_ZERO_TIME);
|
||||
sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME);
|
||||
|
||||
if(!backpressure.empty())
|
||||
if(!pendingRequests.empty())
|
||||
{
|
||||
tlm_generic_payload* payloadToSend = backpressure.front();
|
||||
backpressure.pop_front();
|
||||
tlm_generic_payload* payloadToSend = pendingRequests.front();
|
||||
pendingRequests.pop_front();
|
||||
sendToChannel(*payloadToSend, BEGIN_REQ, SC_ZERO_TIME );
|
||||
channelIsFree = false;
|
||||
}
|
||||
}
|
||||
else if (phase == BEGIN_RESP)
|
||||
{
|
||||
sendToInitiator(DramExtension::getExtension(payload).getThread().ID()-1, payload, phase, SC_ZERO_TIME);
|
||||
{
|
||||
if(receivedResponses[initiatorSocket].empty())
|
||||
sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME);
|
||||
receivedResponses[initiatorSocket].push_back(&payload);
|
||||
}
|
||||
|
||||
else
|
||||
@@ -136,7 +151,7 @@ private:
|
||||
{
|
||||
unsigned int burstlength = payload.get_streaming_width();
|
||||
DecodedAddress decodedAddress = xmlAddressDecoder::getInstance().decodeAddress(payload.get_address());
|
||||
DramExtension* extension = new DramExtension(Thread(socketId+1), Channel(decodedAddress.channel), Bank(decodedAddress.bank),
|
||||
DramExtension* extension = new DramExtension(Thread(socketId+1), Channel(0), Bank(decodedAddress.bank),
|
||||
BankGroup(decodedAddress.bankgroup), Row(decodedAddress.row), Column(decodedAddress.column),burstlength);
|
||||
payload.set_auto_extension(extension);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ using namespace core;
|
||||
using namespace Data;
|
||||
|
||||
|
||||
#define POWER
|
||||
#define POWER //not better to define in simulation xml? also flag for storage simulation
|
||||
|
||||
#ifdef POWER
|
||||
#define IFPOW(x) x
|
||||
@@ -39,43 +39,6 @@ using namespace Data;
|
||||
#endif
|
||||
|
||||
|
||||
class column
|
||||
{
|
||||
private:
|
||||
|
||||
unsigned char * data;
|
||||
unsigned int bytes;
|
||||
|
||||
public:
|
||||
|
||||
column()
|
||||
{
|
||||
bytes = 0;
|
||||
data = NULL;
|
||||
}
|
||||
|
||||
column(int bytes)
|
||||
{
|
||||
this->bytes = bytes;
|
||||
data = new unsigned char[bytes];
|
||||
}
|
||||
|
||||
~column()
|
||||
{
|
||||
delete data;
|
||||
}
|
||||
|
||||
void set(unsigned char * payloadDataPtr)
|
||||
{
|
||||
memcpy(data, payloadDataPtr, bytes);
|
||||
}
|
||||
|
||||
void get(unsigned char * payloadDataPtr)
|
||||
{
|
||||
memcpy(payloadDataPtr, data, bytes);
|
||||
}
|
||||
};
|
||||
|
||||
template<unsigned int BUSWIDTH = 128, unsigned int WORDS = 4096, bool STORE = true, bool FIXED_BL = false,
|
||||
unsigned int FIXED_BL_VALUE = 0>
|
||||
struct Dram: sc_module
|
||||
@@ -83,7 +46,7 @@ struct Dram: sc_module
|
||||
tlm_utils::simple_target_socket<Dram, BUSWIDTH, tlm::tlm_base_protocol_types> tSocket;
|
||||
IFPOW(libDRAMPower *DRAMPower);
|
||||
|
||||
map< unsigned long int, column * > memory;
|
||||
map< unsigned long int, unsigned char[BUSWIDTH/2] > memory;
|
||||
|
||||
SC_CTOR(Dram) : tSocket("socket")
|
||||
{
|
||||
@@ -128,28 +91,24 @@ struct Dram: sc_module
|
||||
else if (phase == BEGIN_WR)
|
||||
{
|
||||
IFPOW(DRAMPower->doCommand(MemCommand::WR, bank, cycle));
|
||||
//save data:
|
||||
memcpy(&memory[payload.get_address()], payload.get_data_ptr(), BUSWIDTH/8);
|
||||
sendToController(payload, END_WR, delay + getExecutionTime(Command::Write, payload));
|
||||
|
||||
// Save:
|
||||
//column * c = new column(16);
|
||||
//c->set(payload.get_data_ptr());
|
||||
//memory[payload.get_address()] = c;
|
||||
}
|
||||
else if (phase == BEGIN_RD)
|
||||
{
|
||||
IFPOW(DRAMPower->doCommand(MemCommand::RD, bank, cycle));
|
||||
sendToController(payload, END_RD, delay + getExecutionTime(Command::Read, payload));
|
||||
|
||||
// Load:
|
||||
//if(memory.count(payload.get_address()) == 1)
|
||||
//{
|
||||
// column * c = memory[payload.get_address()];
|
||||
// c->get(payload.get_data_ptr());
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// SC_REPORT_WARNING ("DRAM", "Reading from an empty memory location");
|
||||
//}
|
||||
// Load data:
|
||||
if(memory.count(payload.get_address()) == 1)
|
||||
{
|
||||
memcpy(payload.get_data_ptr(), &memory[payload.get_address()], BUSWIDTH/8);
|
||||
}
|
||||
else
|
||||
{
|
||||
//SC_REPORT_WARNING ("DRAM", "Reading from an empty memory location.");
|
||||
}
|
||||
}
|
||||
else if (phase == BEGIN_WRA)
|
||||
{
|
||||
|
||||
133
dram/src/simulation/ReorderBuffer.h
Normal file
133
dram/src/simulation/ReorderBuffer.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#ifndef REORDERBUFFER_H
|
||||
#define REORDERBUFFER_H
|
||||
|
||||
#include <deque>
|
||||
#include <systemc.h>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
using namespace tlm;
|
||||
|
||||
template<unsigned int BUSWIDTH = 128>
|
||||
struct ReorderBuffer: public sc_module
|
||||
{
|
||||
public:
|
||||
tlm_utils::simple_initiator_socket<ReorderBuffer,BUSWIDTH, tlm::tlm_base_protocol_types> iSocket;
|
||||
tlm_utils::simple_target_socket<ReorderBuffer, BUSWIDTH, tlm::tlm_base_protocol_types> tSocket;
|
||||
|
||||
SC_CTOR(ReorderBuffer) :
|
||||
payloadEventQueue(this, &ReorderBuffer::peqCallback), responseIsPendingInInitator(false)
|
||||
{
|
||||
iSocket.register_nb_transport_bw(this, &ReorderBuffer::nb_transport_bw);
|
||||
tSocket.register_nb_transport_fw(this, &ReorderBuffer::nb_transport_fw);
|
||||
}
|
||||
|
||||
private:
|
||||
tlm_utils::peq_with_cb_and_phase<ReorderBuffer> payloadEventQueue;
|
||||
deque<tlm_generic_payload*> pendingRequestsInOrder;
|
||||
set<tlm_generic_payload*> receivedResponses;
|
||||
|
||||
bool responseIsPendingInInitator;
|
||||
|
||||
|
||||
// Initiated by dram side
|
||||
tlm_sync_enum nb_transport_bw(tlm_generic_payload& payload, tlm_phase& phase, sc_time& bwDelay)
|
||||
{
|
||||
payloadEventQueue.notify(payload, phase, bwDelay);
|
||||
return TLM_ACCEPTED;
|
||||
}
|
||||
|
||||
// Initiated by initator side (players)
|
||||
tlm_sync_enum nb_transport_fw(tlm_generic_payload& payload, tlm_phase& phase,
|
||||
sc_time& fwDelay)
|
||||
{
|
||||
if (phase == BEGIN_REQ)
|
||||
{
|
||||
payload.acquire();
|
||||
}
|
||||
else if (phase == END_RESP)
|
||||
{
|
||||
payload.release();
|
||||
}
|
||||
|
||||
payloadEventQueue.notify(payload, phase, fwDelay);
|
||||
return TLM_ACCEPTED;
|
||||
}
|
||||
|
||||
void peqCallback(tlm_generic_payload& payload, const tlm_phase& phase)
|
||||
{
|
||||
//Phases initiated by initiator side
|
||||
if (phase == BEGIN_REQ)
|
||||
{
|
||||
pendingRequestsInOrder.push_back(&payload);
|
||||
sendToTarget(payload, phase, SC_ZERO_TIME );
|
||||
}
|
||||
|
||||
else if (phase == END_RESP)
|
||||
{
|
||||
responseIsPendingInInitator = false;
|
||||
pendingRequestsInOrder.pop_front();
|
||||
receivedResponses.erase(&payload);
|
||||
sendNextResponse();
|
||||
}
|
||||
|
||||
//Phases initiated by dram side
|
||||
else if (phase == END_REQ)
|
||||
{
|
||||
sendToInitiator(payload, phase, SC_ZERO_TIME);
|
||||
}
|
||||
else if (phase == BEGIN_RESP)
|
||||
{
|
||||
sendToTarget(payload, END_RESP, SC_ZERO_TIME);
|
||||
receivedResponses.emplace(&payload);
|
||||
sendNextResponse();
|
||||
}
|
||||
|
||||
|
||||
else
|
||||
{
|
||||
SC_REPORT_FATAL(0, "Payload event queue in arbiter was triggered with unknown phase");
|
||||
}
|
||||
}
|
||||
|
||||
void sendToTarget(tlm_generic_payload& payload, const tlm_phase& phase, const sc_time& delay)
|
||||
{
|
||||
tlm_phase TPhase = phase;
|
||||
sc_time TDelay = delay;
|
||||
iSocket->nb_transport_fw(payload, TPhase, TDelay);
|
||||
}
|
||||
|
||||
void sendToInitiator(tlm_generic_payload& payload, const tlm_phase& phase, const sc_time& delay)
|
||||
{
|
||||
|
||||
|
||||
sc_assert(phase == END_REQ ||
|
||||
(phase == BEGIN_RESP && pendingRequestsInOrder.front() == &payload && receivedResponses.count(&payload)));
|
||||
|
||||
tlm_phase TPhase = phase;
|
||||
sc_time TDelay = delay;
|
||||
tSocket->nb_transport_bw(payload, TPhase, TDelay);
|
||||
}
|
||||
|
||||
void sendNextResponse()
|
||||
{
|
||||
//only send the next response when there response for the oldest pending request (requestsInOrder.front())
|
||||
//has been received
|
||||
if(!responseIsPendingInInitator && receivedResponses.count(pendingRequestsInOrder.front()))
|
||||
{
|
||||
tlm_generic_payload* payloadToSend = pendingRequestsInOrder.front();
|
||||
responseIsPendingInInitator = true;
|
||||
sendToInitiator(*payloadToSend,BEGIN_RESP,SC_ZERO_TIME);
|
||||
}
|
||||
// else if(!responseIsPendingInInitator && receivedResponses.size()>0 && !receivedResponses.count(pendingRequestsInOrder.front())>0)
|
||||
// {
|
||||
// cout << "cant send this response, because we are still waiting for response of oldest pending request. Elemts in buffer: " << receivedResponses.size() << endl;
|
||||
// }
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // REORDERBUFFER_H
|
||||
@@ -44,14 +44,14 @@ void Simulation::setupDebugManager(const string& traceName)
|
||||
{
|
||||
auto& dbg = DebugManager::getInstance();
|
||||
|
||||
// dbg.addToWhiteList(controller->name());
|
||||
// dbg.addToWhiteList(player2->name());
|
||||
// dbg.addToWhiteList(player1->name());
|
||||
// dbg.addToWhiteList(this->name());
|
||||
// dbg.addToWhiteList(Scheduler::sendername);
|
||||
// dbg.addToWhiteList(TlmRecorder::senderName);
|
||||
// dbg.addToWhiteList(ControllerCore::senderName);
|
||||
// dbg.addToWhiteList(PowerDownManagerBankwise::senderName);
|
||||
dbg.addToWhiteList(controller->name());
|
||||
dbg.addToWhiteList(player2->name());
|
||||
dbg.addToWhiteList(player1->name());
|
||||
dbg.addToWhiteList(this->name());
|
||||
dbg.addToWhiteList(Scheduler::sendername);
|
||||
dbg.addToWhiteList(TlmRecorder::senderName);
|
||||
dbg.addToWhiteList(ControllerCore::senderName);
|
||||
dbg.addToWhiteList(PowerDownManagerBankwise::senderName);
|
||||
|
||||
dbg.writeToConsole = true;
|
||||
dbg.writeToFile = true;
|
||||
@@ -82,6 +82,7 @@ void Simulation::instantiateModules(const string &pathToResources, const std::ve
|
||||
dram = new Dram<>("dram");
|
||||
arbiter = new Arbiter<NumberOfTracePlayers, 128>("arbiter");
|
||||
controller = new Controller<>("controller");
|
||||
reorder = new ReorderBuffer<>("reorder");
|
||||
|
||||
player1 = new TracePlayer<>("player1", pathToResources + string("traces/") + devices[0].trace, devices[0].burstLength, devices[0].clkMhz, this);
|
||||
player2 = new TracePlayer<>("player2", pathToResources + string("traces/") + devices[1].trace, devices[1].burstLength, devices[1].clkMhz, this);
|
||||
@@ -91,7 +92,10 @@ void Simulation::instantiateModules(const string &pathToResources, const std::ve
|
||||
|
||||
void Simulation::bindSockets()
|
||||
{
|
||||
player1->iSocket.bind(arbiter->tSockets[0]);
|
||||
//player1->iSocket.bind(arbiter->tSockets[0]);
|
||||
player1->iSocket.bind(reorder->tSocket);
|
||||
reorder->iSocket.bind(arbiter->tSockets[0]);
|
||||
|
||||
player2->iSocket.bind(arbiter->tSockets[1]);
|
||||
player3->iSocket.bind(arbiter->tSockets[2]);
|
||||
player4->iSocket.bind(arbiter->tSockets[3]);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Dram.h"
|
||||
#include "Arbiter.h"
|
||||
#include "TracePlayer.h"
|
||||
#include "ReorderBuffer.h"
|
||||
#include "../controller/Controller.h"
|
||||
#include "ISimulation.h"
|
||||
#include <string>
|
||||
@@ -61,6 +62,7 @@ private:
|
||||
Dram<> *dram;
|
||||
Arbiter<NumberOfTracePlayers, 128> *arbiter;
|
||||
Controller<> *controller;
|
||||
ReorderBuffer<> *reorder;
|
||||
|
||||
TracePlayer<> *player1;
|
||||
TracePlayer<> *player2;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* SimulationManager.cpp
|
||||
*
|
||||
* Created on: Apr 12, 2014
|
||||
|
||||
@@ -33,7 +33,7 @@ struct TracePlayer: public sc_module
|
||||
public:
|
||||
tlm_utils::simple_initiator_socket<TracePlayer, BUSWIDTH, tlm::tlm_base_protocol_types> iSocket;
|
||||
TracePlayer(sc_module_name /*name*/, string pathToTrace, unsigned int burstLength, unsigned int clkMhz,
|
||||
simulation::ISimulation* simulationManager);
|
||||
simulation::ISimulation* simulationManager);
|
||||
|
||||
void start();
|
||||
|
||||
@@ -85,72 +85,148 @@ void TracePlayer<BUSWIDTH>::start()
|
||||
template<unsigned int BUSWIDTH>
|
||||
void TracePlayer<BUSWIDTH>::generateNextPayload()
|
||||
{
|
||||
if (file)
|
||||
|
||||
if(file)
|
||||
{
|
||||
string time, command, address, data;
|
||||
file >> time >> command >> address;
|
||||
|
||||
//if there is a newline at the end of the .stl
|
||||
if (time.empty() || command.empty() || address.empty() )
|
||||
return;
|
||||
|
||||
long parsedAdress = std::stoi(address.c_str(), 0, 16);
|
||||
|
||||
gp* payload = memoryManager.allocate();
|
||||
payload->set_address(parsedAdress);
|
||||
|
||||
// Set data pointer
|
||||
unsigned char * dataElement = new unsigned char[16]; // TODO: column / burst breite
|
||||
payload->set_data_length(16); // TODO: column / burst breite
|
||||
payload->set_data_ptr(dataElement);
|
||||
for(int i = 0; i < 16; i++) // TODO: column / burst breite
|
||||
dataElement[i] = 0;
|
||||
|
||||
if (command == "read")
|
||||
string line;
|
||||
if (std::getline(file, line))
|
||||
{
|
||||
payload->set_command(TLM_READ_COMMAND);
|
||||
}
|
||||
else if (command == "write")
|
||||
{
|
||||
payload->set_command(TLM_WRITE_COMMAND);
|
||||
std::istringstream iss(line);
|
||||
string time, command, address;
|
||||
iss >> time >> command >> address;
|
||||
if (time.empty() || command.empty() || address.empty() )
|
||||
return;
|
||||
long parsedAdress = std::stoi(address.c_str(), 0, 16);
|
||||
|
||||
// Parse and set data
|
||||
file >> data;
|
||||
unsigned int counter = 0;
|
||||
for(int i = 0; i < 16*2-2; i=i+2) // TODO column / burst breite
|
||||
gp* payload = memoryManager.allocate();
|
||||
payload->set_address(parsedAdress);
|
||||
|
||||
// Set data pointer
|
||||
unsigned char * dataElement = new unsigned char[16]; // TODO: column / burst breite
|
||||
payload->set_data_length(16); // TODO: column / burst breite
|
||||
payload->set_data_ptr(dataElement);
|
||||
for(int i = 0; i < 16; i++) // TODO: column / burst breite
|
||||
dataElement[i] = 0;
|
||||
|
||||
if (command == "read")
|
||||
{
|
||||
std::string byteString = "0x";
|
||||
byteString.append(data.substr(i+2, 2));
|
||||
//cout << byteString << " " << std::stoi(byteString.c_str(), 0, 16) << endl;
|
||||
dataElement[counter++] = std::stoi(byteString.c_str(), 0, 16);
|
||||
payload->set_command(TLM_READ_COMMAND);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SC_REPORT_FATAL(0,
|
||||
(string("Corrupted tracefile, command ") + command + string(" unknown")).c_str());
|
||||
}
|
||||
else if (command == "write")
|
||||
{
|
||||
payload->set_command(TLM_WRITE_COMMAND);
|
||||
|
||||
payload->set_response_status(TLM_INCOMPLETE_RESPONSE);
|
||||
payload->set_dmi_allowed(false);
|
||||
payload->set_byte_enable_length(0);
|
||||
payload->set_streaming_width(burstlenght);
|
||||
// Parse and set data
|
||||
string data;
|
||||
iss >> data;
|
||||
|
||||
sc_time sendingTime = std::stoi(time.c_str())*clk;
|
||||
GenerationExtension* genExtension = new GenerationExtension(sendingTime);
|
||||
payload->set_auto_extension(genExtension);
|
||||
if(!data.empty())
|
||||
{
|
||||
//cout << "parsing write data: " << data << std::endl;
|
||||
|
||||
for(int i = 0; i < 16; i++) // TODO column / burst breite
|
||||
{
|
||||
std::string byteString = "0x";
|
||||
byteString.append(data.substr(2*(i+1), 2));
|
||||
//cout << byteString << " " << std::stoi(byteString.c_str(), 0, 16) << endl;
|
||||
dataElement[i] = std::stoi(byteString.c_str(), 0, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SC_REPORT_FATAL(0,
|
||||
(string("Corrupted tracefile, command ") + command + string(" unknown")).c_str());
|
||||
}
|
||||
|
||||
payload->set_response_status(TLM_INCOMPLETE_RESPONSE);
|
||||
payload->set_dmi_allowed(false);
|
||||
payload->set_byte_enable_length(0);
|
||||
payload->set_streaming_width(burstlenght);
|
||||
|
||||
sc_time sendingTime = std::stoi(time.c_str())*clk;
|
||||
GenerationExtension* genExtension = new GenerationExtension(sendingTime);
|
||||
payload->set_auto_extension(genExtension);
|
||||
|
||||
if (sendingTime <= sc_time_stamp())
|
||||
{
|
||||
payloadEventQueue.notify(*payload, BEGIN_REQ, SC_ZERO_TIME);
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadEventQueue.notify(*payload, BEGIN_REQ, sendingTime - sc_time_stamp());
|
||||
}
|
||||
numberOfPendingTransactions++;
|
||||
|
||||
if (sendingTime <= sc_time_stamp())
|
||||
{
|
||||
payloadEventQueue.notify(*payload, BEGIN_REQ, SC_ZERO_TIME);
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadEventQueue.notify(*payload, BEGIN_REQ, sendingTime - sc_time_stamp());
|
||||
}
|
||||
numberOfPendingTransactions++;
|
||||
}
|
||||
}
|
||||
// if (file)
|
||||
// {
|
||||
// string time, command, address, data;
|
||||
// file >> time >> command >> address;
|
||||
|
||||
// //if there is a newline at the end of the .stl
|
||||
// if (time.empty() || command.empty() || address.empty() )
|
||||
// return;
|
||||
|
||||
// long parsedAdress = std::stoi(address.c_str(), 0, 16);
|
||||
|
||||
// gp* payload = memoryManager.allocate();
|
||||
// payload->set_address(parsedAdress);
|
||||
|
||||
// // Set data pointer
|
||||
// unsigned char * dataElement = new unsigned char[16]; // TODO: column / burst breite
|
||||
// payload->set_data_length(16); // TODO: column / burst breite
|
||||
// payload->set_data_ptr(dataElement);
|
||||
// for(int i = 0; i < 16; i++) // TODO: column / burst breite
|
||||
// dataElement[i] = 0;
|
||||
|
||||
// if (command == "read")
|
||||
// {
|
||||
// payload->set_command(TLM_READ_COMMAND);
|
||||
// }
|
||||
// else if (command == "write")
|
||||
// {
|
||||
// payload->set_command(TLM_WRITE_COMMAND);
|
||||
|
||||
// // Parse and set data
|
||||
//// file >> data;
|
||||
//// unsigned int counter = 0;
|
||||
//// for(int i = 0; i < 16*2-2; i=i+2) // TODO column / burst breite
|
||||
//// {
|
||||
//// std::string byteString = "0x";
|
||||
//// byteString.append(data.substr(i+2, 2));
|
||||
//// //cout << byteString << " " << std::stoi(byteString.c_str(), 0, 16) << endl;
|
||||
//// dataElement[counter++] = std::stoi(byteString.c_str(), 0, 16);
|
||||
//// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// SC_REPORT_FATAL(0,
|
||||
// (string("Corrupted tracefile, command ") + command + string(" unknown")).c_str());
|
||||
// }
|
||||
|
||||
// payload->set_response_status(TLM_INCOMPLETE_RESPONSE);
|
||||
// payload->set_dmi_allowed(false);
|
||||
// payload->set_byte_enable_length(0);
|
||||
// payload->set_streaming_width(burstlenght);
|
||||
|
||||
// sc_time sendingTime = std::stoi(time.c_str())*clk;
|
||||
// GenerationExtension* genExtension = new GenerationExtension(sendingTime);
|
||||
// payload->set_auto_extension(genExtension);
|
||||
|
||||
// if (sendingTime <= sc_time_stamp())
|
||||
// {
|
||||
// payloadEventQueue.notify(*payload, BEGIN_REQ, SC_ZERO_TIME);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// payloadEventQueue.notify(*payload, BEGIN_REQ, sendingTime - sc_time_stamp());
|
||||
// }
|
||||
// numberOfPendingTransactions++;
|
||||
// }
|
||||
//}
|
||||
|
||||
template<unsigned int BUSWIDTH>
|
||||
tlm_sync_enum TracePlayer<BUSWIDTH>::nb_transport_bw(tlm_generic_payload &payload, tlm_phase &phase, sc_time &bwDelay)
|
||||
|
||||
Reference in New Issue
Block a user