| /* | |
| * Copyright (c) 2013 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. | |
| */ | |
| #include "mem/dramsim2.hh" | |
| #include "DRAMSim2/Callback.h" | |
| #include "base/callback.hh" | |
| #include "base/trace.hh" | |
| #include "debug/DRAMSim2.hh" | |
| #include "debug/Drain.hh" | |
| #include "sim/system.hh" | |
| namespace gem5 | |
| { | |
| namespace memory | |
| { | |
| DRAMSim2::DRAMSim2(const Params &p) : | |
| AbstractMemory(p), | |
| port(name() + ".port", *this), | |
| wrapper(p.deviceConfigFile, p.systemConfigFile, p.filePath, | |
| p.traceFile, p.range.size() / 1024 / 1024, p.enableDebug), | |
| retryReq(false), retryResp(false), startTick(0), | |
| nbrOutstandingReads(0), nbrOutstandingWrites(0), | |
| sendResponseEvent([this]{ sendResponse(); }, name()), | |
| tickEvent([this]{ tick(); }, name()) | |
| { | |
| DPRINTF(DRAMSim2, | |
| "Instantiated DRAMSim2 with clock %d ns and queue size %d\n", | |
| wrapper.clockPeriod(), wrapper.queueSize()); | |
| DRAMSim::TransactionCompleteCB* read_cb = | |
| new DRAMSim::Callback<DRAMSim2, void, unsigned, uint64_t, uint64_t>( | |
| this, &DRAMSim2::readComplete); | |
| DRAMSim::TransactionCompleteCB* write_cb = | |
| new DRAMSim::Callback<DRAMSim2, void, unsigned, uint64_t, uint64_t>( | |
| this, &DRAMSim2::writeComplete); | |
| wrapper.setCallbacks(read_cb, write_cb); | |
| // Register a callback to compensate for the destructor not | |
| // being called. The callback prints the DRAMSim2 stats. | |
| registerExitCallback([this]() { wrapper.printStats(); }); | |
| } | |
| void | |
| DRAMSim2::init() | |
| { | |
| AbstractMemory::init(); | |
| if (!port.isConnected()) { | |
| fatal("DRAMSim2 %s is unconnected!\n", name()); | |
| } else { | |
| port.sendRangeChange(); | |
| } | |
| if (system()->cacheLineSize() != wrapper.burstSize()) | |
| fatal("DRAMSim2 burst size %d does not match cache line size %d\n", | |
| wrapper.burstSize(), system()->cacheLineSize()); | |
| } | |
| void | |
| DRAMSim2::startup() | |
| { | |
| startTick = curTick(); | |
| // kick off the clock ticks | |
| schedule(tickEvent, clockEdge()); | |
| } | |
| void | |
| DRAMSim2::sendResponse() | |
| { | |
| assert(!retryResp); | |
| assert(!responseQueue.empty()); | |
| DPRINTF(DRAMSim2, "Attempting to send response\n"); | |
| bool success = port.sendTimingResp(responseQueue.front()); | |
| if (success) { | |
| responseQueue.pop_front(); | |
| DPRINTF(DRAMSim2, "Have %d read, %d write, %d responses outstanding\n", | |
| nbrOutstandingReads, nbrOutstandingWrites, | |
| responseQueue.size()); | |
| if (!responseQueue.empty() && !sendResponseEvent.scheduled()) | |
| schedule(sendResponseEvent, curTick()); | |
| if (nbrOutstanding() == 0) | |
| signalDrainDone(); | |
| } else { | |
| retryResp = true; | |
| DPRINTF(DRAMSim2, "Waiting for response retry\n"); | |
| assert(!sendResponseEvent.scheduled()); | |
| } | |
| } | |
| unsigned int | |
| DRAMSim2::nbrOutstanding() const | |
| { | |
| return nbrOutstandingReads + nbrOutstandingWrites + responseQueue.size(); | |
| } | |
| void | |
| DRAMSim2::tick() | |
| { | |
| wrapper.tick(); | |
| // is the connected port waiting for a retry, if so check the | |
| // state and send a retry if conditions have changed | |
| if (retryReq && nbrOutstanding() < wrapper.queueSize()) { | |
| retryReq = false; | |
| port.sendRetryReq(); | |
| } | |
| schedule(tickEvent, | |
| curTick() + wrapper.clockPeriod() * sim_clock::as_int::ns); | |
| } | |
| Tick | |
| DRAMSim2::recvAtomic(PacketPtr pkt) | |
| { | |
| access(pkt); | |
| // 50 ns is just an arbitrary value at this point | |
| return pkt->cacheResponding() ? 0 : 50000; | |
| } | |
| void | |
| DRAMSim2::recvFunctional(PacketPtr pkt) | |
| { | |
| pkt->pushLabel(name()); | |
| functionalAccess(pkt); | |
| // potentially update the packets in our response queue as well | |
| for (auto i = responseQueue.begin(); i != responseQueue.end(); ++i) | |
| pkt->trySatisfyFunctional(*i); | |
| pkt->popLabel(); | |
| } | |
| bool | |
| DRAMSim2::recvTimingReq(PacketPtr pkt) | |
| { | |
| // if a cache is responding, sink the packet without further action | |
| if (pkt->cacheResponding()) { | |
| pendingDelete.reset(pkt); | |
| return true; | |
| } | |
| // we should not get a new request after committing to retry the | |
| // current one, but unfortunately the CPU violates this rule, so | |
| // simply ignore it for now | |
| if (retryReq) | |
| return false; | |
| // if we cannot accept we need to send a retry once progress can | |
| // be made | |
| bool can_accept = nbrOutstanding() < wrapper.queueSize(); | |
| // keep track of the transaction | |
| if (pkt->isRead()) { | |
| if (can_accept) { | |
| outstandingReads[pkt->getAddr()].push(pkt); | |
| // we count a transaction as outstanding until it has left the | |
| // queue in the controller, and the response has been sent | |
| // back, note that this will differ for reads and writes | |
| ++nbrOutstandingReads; | |
| } | |
| } else if (pkt->isWrite()) { | |
| if (can_accept) { | |
| outstandingWrites[pkt->getAddr()].push(pkt); | |
| ++nbrOutstandingWrites; | |
| // perform the access for writes | |
| accessAndRespond(pkt); | |
| } | |
| } else { | |
| // keep it simple and just respond if necessary | |
| accessAndRespond(pkt); | |
| return true; | |
| } | |
| if (can_accept) { | |
| // we should never have a situation when we think there is space, | |
| // and there isn't | |
| assert(wrapper.canAccept()); | |
| DPRINTF(DRAMSim2, "Enqueueing address %lld\n", pkt->getAddr()); | |
| // @todo what about the granularity here, implicit assumption that | |
| // a transaction matches the burst size of the memory (which we | |
| // cannot determine without parsing the ini file ourselves) | |
| wrapper.enqueue(pkt->isWrite(), pkt->getAddr()); | |
| return true; | |
| } else { | |
| retryReq = true; | |
| return false; | |
| } | |
| } | |
| void | |
| DRAMSim2::recvRespRetry() | |
| { | |
| DPRINTF(DRAMSim2, "Retrying\n"); | |
| assert(retryResp); | |
| retryResp = false; | |
| sendResponse(); | |
| } | |
| void | |
| DRAMSim2::accessAndRespond(PacketPtr pkt) | |
| { | |
| DPRINTF(DRAMSim2, "Access for address %lld\n", pkt->getAddr()); | |
| bool needsResponse = pkt->needsResponse(); | |
| // do the actual memory access which also turns the packet into a | |
| // response | |
| access(pkt); | |
| // turn packet around to go back to requestor if response expected | |
| if (needsResponse) { | |
| // access already turned the packet into a response | |
| assert(pkt->isResponse()); | |
| // Here we pay for xbar additional delay and to process the payload | |
| // of the packet. | |
| Tick time = curTick() + pkt->headerDelay + pkt->payloadDelay; | |
| // Reset the timings of the packet | |
| pkt->headerDelay = pkt->payloadDelay = 0; | |
| DPRINTF(DRAMSim2, "Queuing response for address %lld\n", | |
| pkt->getAddr()); | |
| // queue it to be sent back | |
| responseQueue.push_back(pkt); | |
| // if we are not already waiting for a retry, or are scheduled | |
| // to send a response, schedule an event | |
| if (!retryResp && !sendResponseEvent.scheduled()) | |
| schedule(sendResponseEvent, time); | |
| } else { | |
| // queue the packet for deletion | |
| pendingDelete.reset(pkt); | |
| } | |
| } | |
| void DRAMSim2::readComplete(unsigned id, uint64_t addr, uint64_t cycle) | |
| { | |
| assert(cycle == divCeil(curTick() - startTick, | |
| wrapper.clockPeriod() * sim_clock::as_int::ns)); | |
| DPRINTF(DRAMSim2, "Read to address %lld complete\n", addr); | |
| // get the outstanding reads for the address in question | |
| auto p = outstandingReads.find(addr); | |
| assert(p != outstandingReads.end()); | |
| // first in first out, which is not necessarily true, but it is | |
| // the best we can do at this point | |
| PacketPtr pkt = p->second.front(); | |
| p->second.pop(); | |
| if (p->second.empty()) | |
| outstandingReads.erase(p); | |
| // no need to check for drain here as the next call will add a | |
| // response to the response queue straight away | |
| assert(nbrOutstandingReads != 0); | |
| --nbrOutstandingReads; | |
| // perform the actual memory access | |
| accessAndRespond(pkt); | |
| } | |
| void DRAMSim2::writeComplete(unsigned id, uint64_t addr, uint64_t cycle) | |
| { | |
| assert(cycle == divCeil(curTick() - startTick, | |
| wrapper.clockPeriod() * sim_clock::as_int::ns)); | |
| DPRINTF(DRAMSim2, "Write to address %lld complete\n", addr); | |
| // get the outstanding reads for the address in question | |
| auto p = outstandingWrites.find(addr); | |
| assert(p != outstandingWrites.end()); | |
| // we have already responded, and this is only to keep track of | |
| // what is outstanding | |
| p->second.pop(); | |
| if (p->second.empty()) | |
| outstandingWrites.erase(p); | |
| assert(nbrOutstandingWrites != 0); | |
| --nbrOutstandingWrites; | |
| if (nbrOutstanding() == 0) | |
| signalDrainDone(); | |
| } | |
| Port & | |
| DRAMSim2::getPort(const std::string &if_name, PortID idx) | |
| { | |
| if (if_name != "port") { | |
| return AbstractMemory::getPort(if_name, idx); | |
| } else { | |
| return port; | |
| } | |
| } | |
| DrainState | |
| DRAMSim2::drain() | |
| { | |
| // check our outstanding reads and writes and if any they need to | |
| // drain | |
| return nbrOutstanding() != 0 ? DrainState::Draining : DrainState::Drained; | |
| } | |
| DRAMSim2::MemoryPort::MemoryPort(const std::string& _name, | |
| DRAMSim2& _memory) | |
| : ResponsePort(_name, &_memory), mem(_memory) | |
| { } | |
| AddrRangeList | |
| DRAMSim2::MemoryPort::getAddrRanges() const | |
| { | |
| AddrRangeList ranges; | |
| ranges.push_back(mem.getAddrRange()); | |
| return ranges; | |
| } | |
| Tick | |
| DRAMSim2::MemoryPort::recvAtomic(PacketPtr pkt) | |
| { | |
| return mem.recvAtomic(pkt); | |
| } | |
| void | |
| DRAMSim2::MemoryPort::recvFunctional(PacketPtr pkt) | |
| { | |
| mem.recvFunctional(pkt); | |
| } | |
| bool | |
| DRAMSim2::MemoryPort::recvTimingReq(PacketPtr pkt) | |
| { | |
| // pass it to the memory controller | |
| return mem.recvTimingReq(pkt); | |
| } | |
| void | |
| DRAMSim2::MemoryPort::recvRespRetry() | |
| { | |
| mem.recvRespRetry(); | |
| } | |
| } // namespace memory | |
| } // namespace gem5 |