diff --git a/.gitignore b/.gitignore index f1ef5004..b0259661 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ build*/ *.swo cscope* DRAMSys/analyzer/scripts/__pycache__/ +*.pyc diff --git a/DRAMSys/docs/images/PayloadExtension.svg b/DRAMSys/docs/images/PayloadExtension.svg new file mode 100644 index 00000000..fd0cb966 --- /dev/null +++ b/DRAMSys/docs/images/PayloadExtension.svg @@ -0,0 +1,2449 @@ + + + +Trace +file n +Trace +file +1 +Trace Player +1 +Trace Player n +Arbiter +Controller +Scheduler +ControllerCore +Controller +Scheduler +ControllerCore +DRAM +DRAM +GenerationExtension +sc +_ +time +( +timeOfGeneration +) +Generic Payload +Command +Address +Data pointer +Data length +Byte enable pointer +Byte enable length +Streaming width +DMI hint +Response status +Generic Payload +Command +Address +Data pointer +Data length +Byte enable pointer +Byte enable length +Streaming width +DMI hint +Response status +GenerationExtension +sc +_ +time +( +timeOfGeneration +) +DramExtension +Thread +Chanel +Bank +BankGroup +Row +Column +Burstlength +Generic Payload +Command +Address +Data pointer +Data length +Byte enable pointer +Byte enable length +Streaming width +DMI hint +Response status +GenerationExtension +sc +_ +time +( +timeOfGeneration +) +DramExtension +Thread +Chanel +Bank +BankGroup +Row +Column +Burstlength +Generic Payload +Command +Address +Data pointer +Data length +Byte enable pointer +Byte enable length +Streaming width +DMI hint +Response status +GenerationExtension +sc +_ +time +( +timeOfGeneration +) +DramExtension +Thread +Chanel +Bank +BankGroup +Row +Column +Burstlength + \ No newline at end of file diff --git a/DRAMSys/docs/images/PayloadMemoryManager.svg b/DRAMSys/docs/images/PayloadMemoryManager.svg new file mode 100644 index 00000000..56a0547f --- /dev/null +++ b/DRAMSys/docs/images/PayloadMemoryManager.svg @@ -0,0 +1,1008 @@ + + + +TracePlayer +BEGIN +_ +REQ +BEGIN +_ +REQ +END +_ +REQ +END +_ +REQ +BEGIN +_ +PRE +END +_ +PRE +BEGIN +_ +ACT +END +_ +ACT +BEGIN +_ +RD or BEGIN +_ +WR +END +_ +RD or END +_ +WR +BEGIN +_ +RESP +BEGIN +_ +RESP +END +_ +RESP +END +_ +RESP +Arbiter +Controller +DRAM +Allocate Payload +acquire +() +Set auto extension +( +GenerationExtension +) +acquire +() +Set auto extension +( +DramExtension +) +Get extension DramExtension +: +getBank +, +getRow +Get extension +DramExtension +: +getThread +Get extension +DramExtension +: +getBank +acquire +() +Get extension +DramExtension +: +getRow +release +() +release +() +release +() +free +() + \ No newline at end of file diff --git a/DRAMSys/docs/images/TransactionPhase.svg b/DRAMSys/docs/images/TransactionPhase.svg new file mode 100644 index 00000000..06e2d73b --- /dev/null +++ b/DRAMSys/docs/images/TransactionPhase.svg @@ -0,0 +1,2038 @@ + + + +PEQFrontEnd +Scheduler +rowBufferIsOpen +( +Bank +) +Command +:: +Activate +FALSE +RowInRowBuffer += +getRow +( +Payload +) +TRUE +Command +:: +Precharge +Command +:: +READ +Command +:: +WRITE +TRUE +& +READ +_ +COMMAND +TRUE +& +WRITE +_ +COMMAND +BEGIN +_ +ACT +BEGIN +_ +PRE +BEGIN +_ +RD +BEGIN +_ +WR +ControllerCore +( +PowerDownManager +, +RefreshManager +) +ControllerCorePEQ +Initiator Socket +Target Socket +Receive +nb +_ +transport +_ +fw +END +_ +ACT +END +_ +PRE +END +_ +RD +END +_ +WR +BEGIN +_ +ACT +BEGIN +_ +PRE +BEGIN +_ +RD +BEGIN +_ +WR +DramPEQ +nb +_ +transport +_ +bw +Controller +DRAM +Target Socket +nb +_ +transport +_ +fw +nb +_ +transport +_ +bw +From +/ +to Arbiter +END +_ +RD or END +_ +WR +END +_ +ACT or END +_ +PRE +FALSE + \ No newline at end of file diff --git a/DRAMSys/simulator/src/simulation/Arbiter.h b/DRAMSys/simulator/src/simulation/Arbiter.h index d41273cd..79e6aa97 100644 --- a/DRAMSys/simulator/src/simulation/Arbiter.h +++ b/DRAMSys/simulator/src/simulation/Arbiter.h @@ -101,10 +101,18 @@ private: std::vector tlmRecorders; + //used to map the transaction from devices to the arbiter's target socket ID. + std::map routeMap; + // Initiated by dram side // This function is called when an arbiter's initiator socket receives a transaction from a memory controller tlm_sync_enum nb_transport_bw(int channelId, tlm_generic_payload &payload, tlm_phase &phase, sc_time &bwDelay) { + // Check channel ID + assert((unsigned int)channelId < iSocket.size()); + if ((unsigned int)channelId != DramExtension::getExtension(payload).getChannel().ID()) { + SC_REPORT_FATAL("Arbiter", "Payload extension was corrupted"); + } sc_time recTime = bwDelay + sc_time_stamp(); sc_time notDelay = bwDelay; @@ -115,19 +123,21 @@ private: return TLM_ACCEPTED; } - // TODO: check this id. How the payload extension propagates and is used... - // TODO: check the index mechanism for initiator and target sockets. Assert the index to be valid. - // Initiated by initiator side // This function is called when an arbiter's target socket receives a transaction from a device tlm_sync_enum nb_transport_fw(int id, tlm_generic_payload& payload, tlm_phase& phase, sc_time& fwDelay) { + assert ((unsigned int)id < tSocket.size()); if (phase == BEGIN_REQ) { + // Map the payload with socket id. + routeMap[&payload] = id; // In the begin request phase the socket ID is appended to the payload. // It will extracted from the payload and used later. appendDramExtension(id, payload); payload.acquire(); } else if (phase == END_RESP) { + // Erase before the payload is released. + routeMap.erase(&payload); payload.release(); } @@ -147,7 +157,9 @@ private: unsigned int initiatorSocket = DramExtension::getExtension(payload).getThread().ID()-1; unsigned int channelId = DramExtension::getExtension(payload).getChannel().ID(); - // TODO: here check if the channel and the initiatorSocket ID are valid. If not, the payload extension was corrupted. + // Check the valid range of initiatorSocket ID and channel Id + assert(initiatorSocket < Configuration::getInstance().NumberOfTracePlayers); + assert(channelId < Configuration::getInstance().NumberOfMemChannels); // Phases initiated by the intiator side from arbiter's point of view (devices performing memory requests to the arbiter) if (phase == BEGIN_REQ) { @@ -177,6 +189,10 @@ private: // Phases initiated by the target side from arbiter's point of view (memory side) else if (phase == END_REQ) { channelIsFree[channelId] = true; + // Validate the initiatorSocket ID + if ((int)initiatorSocket != routeMap[&payload]) { + SC_REPORT_FATAL("Arbiter", "Payload extension was corrupted"); + } // The arbiter receives a transaction which phase is END_REQ from memory controller and forwards it to the requester device. sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME); @@ -190,6 +206,10 @@ private: channelIsFree[channelId] = false; } } else if (phase == BEGIN_RESP) { + // Validate the initiatorSocket ID + if ((int)initiatorSocket != routeMap[&payload]) { + SC_REPORT_FATAL("Arbiter", "Payload extension was corrupted"); + } // The arbiter receives a transaction in BEGIN_RESP phase (that came from the memory side) and forwards it to the requester device if (receivedResponses[initiatorSocket].empty()) sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME); @@ -216,12 +236,35 @@ private: void appendDramExtension(int socketId, tlm_generic_payload& payload) { - // TODO: check if channel valid before appending. - // TODO: check if all parts of the decodedAddress are inside the valid range (devices should not perform invalid requests to the arbiter, right?). 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), BankGroup(decodedAddress.bankgroup), Row(decodedAddress.row), Column(decodedAddress.column),burstlength); - payload.set_auto_extension(extension); + // Check the valid range of decodedAddress + if (addressIsValid(decodedAddress)) { + DramExtension* extension = new DramExtension(Thread(socketId+1), Channel(decodedAddress.channel), Bank(decodedAddress.bank), BankGroup(decodedAddress.bankgroup), Row(decodedAddress.row), Column(decodedAddress.column),burstlength); + payload.set_auto_extension(extension); + } else { + SC_REPORT_FATAL("Arbiter", "Decoded Address are not inside the valid range"); + } + } + + bool addressIsValid(DecodedAddress& decodedAddress) + { + if (decodedAddress.channel >= xmlAddressDecoder::getInstance().amount["channel"]) { + return false; + } + if (decodedAddress.bank >= xmlAddressDecoder::getInstance().amount["bank"]) { + return false; + } + if (decodedAddress.bankgroup > xmlAddressDecoder::getInstance().amount["bankgroup"]) { + return false; + } + if (decodedAddress.column >= xmlAddressDecoder::getInstance().amount["column"]) { + return false; + } + if (decodedAddress.row >= xmlAddressDecoder::getInstance().amount["row"]) { + return false; + } + return true; } void printDebugMessage(std::string message) diff --git a/DRAMSys/tests/evaluation/fifoStrict.xml b/DRAMSys/tests/evaluation/fifoStrict.xml new file mode 100644 index 00000000..270d9037 --- /dev/null +++ b/DRAMSys/tests/evaluation/fifoStrict.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/DRAMSys/tests/evaluation/sim-batch.xml b/DRAMSys/tests/evaluation/sim-batch.xml new file mode 100644 index 00000000..b68f43b1 --- /dev/null +++ b/DRAMSys/tests/evaluation/sim-batch.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mediabench-c-ray-1.1_32.stl + + + + + diff --git a/DRAMSys/tests/evaluation/test.pl b/DRAMSys/tests/evaluation/test.pl new file mode 100644 index 00000000..10968545 --- /dev/null +++ b/DRAMSys/tests/evaluation/test.pl @@ -0,0 +1,93 @@ +#!/usr/bin/perl -w +# Copyright (c) 2016, University of Kaiserslautern +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. 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. +# +# 3. Neither the name of the copyright holder 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 HOLDER +# 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: +# Matthias Jung, Felipe S. Prado +# + +# Test Evaluation: +# This test runs the simulation with standard configuration + +# Run Simulation: + +$bankwiseLogicLine = `grep -n '^' ../../DRAMSys/tests/evaluation/sim-batch.xml"); + system("sed -i '" . $bankwiseLogicLine . "s^.*^ ^' ../../DRAMSys/tests/evaluation/fifoStrict.xml"); + system("sed -i '" . $powerDownModeLine . "s^.*^ ^' ../../DRAMSys/tests/evaluation/fifoStrict.xml"); + + `./dramSys ../../DRAMSys/tests/evaluation/sim-batch.xml`; + + @files = `ls sim-batch/evaluation_test_fifoStrict_channel*.tdb`; + chomp @files; + + foreach (@files) + { + `python3.5 ../../DRAMSys/analyzer/scripts/tests.py $_ > ../../DRAMSys/tests/evaluation/output.txt`; + + if("All tests passed\n" ne `grep "All tests passed" ../../DRAMSys/tests/evaluation/output.txt`) + { + exit -1; + } + } + + system("sed -i '" . $powerAnalysisLine . "s^.*^ ^' ../../DRAMSys/tests/evaluation/sim-batch.xml"); + system("sed -i '" . $bankwiseLogicLine . "s^.*^ ^' ../../DRAMSys/tests/evaluation/fifoStrict.xml"); + + `./dramSys ../../DRAMSys/tests/evaluation/sim-batch.xml`; + + foreach (@files) + { + `python3.5 ../../DRAMSys/analyzer/scripts/tests.py $_ > ../../DRAMSys/tests/evaluation/output.txt`; + + if("All tests passed\n" ne `grep "All tests passed" ../../DRAMSys/tests/evaluation/output.txt`) + { + exit -1; + } + } + +} +exit 0; + + diff --git a/DRAMSys/tests/tests.pri b/DRAMSys/tests/tests.pri index 9f07b4a9..78d0014f 100644 --- a/DRAMSys/tests/tests.pri +++ b/DRAMSys/tests/tests.pri @@ -17,8 +17,12 @@ OTHER_FILES += tests/error/fr_fcfs.xml OTHER_FILES += tests/error/generateErrorTest.pl OTHER_FILES += tests/error/WideIO.xml -# timing compliance test -OTHER_FILES += tests/timing_compliance/sim-batch.xml -OTHER_FILES += tests/timing_compliance/fifoStrict.xml -OTHER_FILES += tests/timing_compliance/test.pl +# evaluation test +OTHER_FILES += tests/evaluation/sim-batch.xml +OTHER_FILES += tests/evaluation/fifoStrict.xml +OTHER_FILES += tests/evaluation/test.pl + +# python unit tests +OTHER_FILES += tests/unit/unit_test.py +OTHER_FILES += tests/unit/mem_util.py diff --git a/DRAMSys/tests/unit/mem_util.py b/DRAMSys/tests/unit/mem_util.py new file mode 100644 index 00000000..cb487a37 --- /dev/null +++ b/DRAMSys/tests/unit/mem_util.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016, University of Kaiserslautern +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. 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. +# +# 3. Neither the name of the copyright holder 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 HOLDER +# 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: Éder F. Zulian +# + +import xml.etree.ElementTree as ET + + +class MemConfig(object): + """ Memory Configuration Class + + The format used in memory specification XML files differs from the + format used in memory configuration XML files. Each class uses the + proper format when searching for elements. + """ + def getValue(self, id): + return self.xmlMemConfig.findall(id)[0].attrib['value'] + + def getIntValue(self, id): + return int(self.getValue(id)) + + def __init__(self, xmlfile): + self.xmlMemConfig = ET.parse(xmlfile) + + +class MemSpec(object): + """ Memory Specification Class + + The format used in memory specification XML files differs from the + format used in memory configuration XML files. Each class uses the + proper format when searching for elements. + """ + def getValue(self, id): + return self.xmlMemSpec.findall(".//parameter[@id='{0}']". + format(id))[0].attrib['value'] + + def getIntValue(self, id): + return int(self.getValue(id)) + + def __init__(self, xmlfile): + self.xmlMemSpec = ET.parse(xmlfile) diff --git a/DRAMSys/tests/unit/unit_test.py b/DRAMSys/tests/unit/unit_test.py new file mode 100644 index 00000000..aacc500b --- /dev/null +++ b/DRAMSys/tests/unit/unit_test.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016, University of Kaiserslautern +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. 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. +# +# 3. Neither the name of the copyright holder 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 HOLDER +# 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: Éder F. Zulian +# + +import unittest +import subprocess +import os +import shutil +import multiprocessing +import sys +import tempfile +from mem_util import * + + +devnull = None + +rootdir = '../../..' +tempfile.tempdir = os.getcwd() + '/' + rootdir +builddir = tempfile.mkdtemp() +simdir = builddir + '/simulator' + +memConfigPath = rootdir + '/DRAMSys/simulator/resources/configs/memconfigs' +memSpecsPath = rootdir + '/DRAMSys/simulator/resources/configs/memspecs' + + +def build_project(): + if os.path.exists(builddir): + shutil.rmtree(builddir) + os.makedirs(builddir) + os.chdir(builddir) + qmakeprojfile = '../DRAMSys/dram.vp.system.pro' + subprocess.call(['qmake', qmakeprojfile], stdout=devnull, stderr=devnull) + makejobs = '-j' + str(multiprocessing.cpu_count()) + ret = subprocess.call(['make', makejobs], stdout=devnull, stderr=devnull) + return ret + + +class TestBuild(unittest.TestCase): + def test_build_project(self): + """ The project's build process should succeed """ + self.assertEqual(build_project(), 0) + + def tearDown(self): + shutil.rmtree(builddir) + + +class TestOutput(unittest.TestCase): + def setUp(self): + build_project() + + def test_run_without_arguments(self): + """ running dramSys without arguments returns 0 """ + os.chdir(simdir) + self.assertEqual(subprocess.call(['./dramSys'], stdout=devnull), 0) + + def tearDown(self): + shutil.rmtree(builddir) + + +@unittest.skip("skipping this") +class TestDummy(unittest.TestCase): + def test_list_files(self): + for file in os.listdir(memConfigPath): + if file.endswith(".xml"): + print(file) + for file in os.listdir(memSpecsPath): + if file.endswith(".xml"): + print(file) + + +if __name__ == '__main__': + with open(os.devnull, 'wb') as devnull: + unittest.main() diff --git a/README.md b/README.md index 4712faa9..539b4e51 100644 --- a/README.md +++ b/README.md @@ -809,6 +809,35 @@ A description of the content each directory follows. - **traces**: trace files for simulations. They contain accesses to memory in certain known scenarios. +#### DRAMsys Diagrams + +- **Payload Extension information** + + GenerationExtension is added in TracePlayer and DramExtension is added in Arbiter. + + DramExtension indicates the decoded address (channel, bank, colums, row) and the socket id (thread) of a payload. It is added in the Arbiter and is sent to the Controller. + ![Payload Extension information](DRAMSys/docs/images/PayloadExtension.png) + +- **Transaction object with Memory Manager** + + The TracePlayer allocates the memory for the transaction object by calling allocatePayload method. + + The acquire method is called before passing the transaction object in TracePlayer, Arbiter and Controller. + + The release method is called after each component is done with the transaction object. After the final call of release method, the free method of the memory manager is called to free the transaction object. + + ![Payload Memory Manager](DRAMSys/docs/images/PayloadMemoryManager.png) + +- **Architecture of the backend TLM model** + + The below figure shows our custom TLM protocol between the Controller and the Dram. A new transaction enters the Controller with the BEGIN_REQ phase is stored in frontendPEQ. The callback function of the frontendPEQ is called and send the payload to the Scheduler. + + The Scheduler checks the address of payload and the current state to determine proper command (Active, Precharge, Read or Write). Then the ControllerCore sends the payload with the corresponding phase (BEGIN_ACT, BEGIN_PRE, BEGIN_RD or BEGIN_WR) to the Dram by calling nb_transport_fw method. + + The Dram receives the transaction then send back to the Controller by calling nb_transport_bw with appropriate END phase (END_ACT, END_PRE, END_RD or END_WR). + + ![Architecture backend TLM](DRAMSys/docs/images/TransactionPhase.png) + #### References [1] TLM Modelling of 3D Stacked Wide I/O DRAM Subsystems, A Virtual Platform for Memory Controller Design Space Exploration