| /* |
| * 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" |
| |
| 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. |
| Callback* cb = new MakeCallback<DRAMSim2Wrapper, |
| &DRAMSim2Wrapper::printStats>(wrapper); |
| registerExitCallback(cb); |
| } |
| |
| 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() * SimClock::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 requester 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() * SimClock::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() * SimClock::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) |
| : SlavePort(_name, &_memory), memory(_memory) |
| { } |
| |
| AddrRangeList |
| DRAMSim2::MemoryPort::getAddrRanges() const |
| { |
| AddrRangeList ranges; |
| ranges.push_back(memory.getAddrRange()); |
| return ranges; |
| } |
| |
| Tick |
| DRAMSim2::MemoryPort::recvAtomic(PacketPtr pkt) |
| { |
| return memory.recvAtomic(pkt); |
| } |
| |
| void |
| DRAMSim2::MemoryPort::recvFunctional(PacketPtr pkt) |
| { |
| memory.recvFunctional(pkt); |
| } |
| |
| bool |
| DRAMSim2::MemoryPort::recvTimingReq(PacketPtr pkt) |
| { |
| // pass it to the memory controller |
| return memory.recvTimingReq(pkt); |
| } |
| |
| void |
| DRAMSim2::MemoryPort::recvRespRetry() |
| { |
| memory.recvRespRetry(); |
| } |
| |
| DRAMSim2* |
| DRAMSim2Params::create() |
| { |
| return new DRAMSim2(this); |
| } |