| /* |
| * Copyright (c) 2021 The Regents of the University of California. |
| * All rights reserved. |
| * |
| * 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 "cpu/testers/traffic_gen/gups_gen.hh" |
| |
| #include <cstring> |
| #include <string> |
| |
| #include "base/random.hh" |
| #include "debug/GUPSGen.hh" |
| #include "sim/sim_exit.hh" |
| |
| namespace gem5 |
| { |
| |
| GUPSGen::GUPSGen(const GUPSGenParams& params): |
| ClockedObject(params), |
| nextCreateEvent([this]{ createNextReq(); }, name()), |
| nextSendEvent([this]{ sendNextReq(); }, name()), |
| system(params.system), |
| requestorId(system->getRequestorId(this)), |
| port(name() + ".port", this), |
| startAddr(params.start_addr), |
| memSize(params.mem_size), |
| updateLimit(params.update_limit), |
| elementSize(sizeof(uint64_t)), // every element in the table is a uint64_t |
| reqQueueSize(params.request_queue_size), |
| initMemory(params.init_memory), |
| stats(this) |
| {} |
| |
| Port& |
| GUPSGen::getPort(const std::string &if_name, PortID idx) |
| { |
| if (if_name != "port") { |
| return ClockedObject::getPort(if_name, idx); |
| } else { |
| return port; |
| } |
| } |
| |
| void |
| GUPSGen::init() |
| { |
| doneReading = false; |
| onTheFlyRequests = 0; |
| readRequests = 0; |
| |
| tableSize = memSize / elementSize; |
| numUpdates = 4 * tableSize; |
| } |
| |
| void |
| GUPSGen::startup() |
| { |
| int block_size = 64; // Write the initial values in 64 byte blocks. |
| uint64_t stride_size = block_size / elementSize; |
| if (initMemory) { |
| for (uint64_t start_index = 0; start_index < tableSize; |
| start_index += stride_size) { |
| uint8_t write_data[block_size]; |
| for (uint64_t offset = 0; offset < stride_size; offset++) { |
| uint64_t value = start_index + offset; |
| std::memcpy(write_data + offset * elementSize, |
| &value, elementSize); |
| } |
| Addr addr = indexToAddr(start_index); |
| PacketPtr pkt = getWritePacket(addr, block_size, write_data); |
| port.sendFunctionalPacket(pkt); |
| delete pkt; |
| } |
| } |
| schedule(nextCreateEvent, nextCycle()); |
| } |
| |
| Addr |
| GUPSGen::indexToAddr (uint64_t index) |
| { |
| Addr ret = index * elementSize + startAddr; |
| return ret; |
| } |
| |
| PacketPtr |
| GUPSGen::getReadPacket(Addr addr, unsigned int size) |
| { |
| RequestPtr req = std::make_shared<Request>(addr, size, 0, requestorId); |
| // Dummy PC to have PC-based prefetchers latch on; get entropy into higher |
| // bits |
| req->setPC(((Addr)requestorId) << 2); |
| |
| // Embed it in a packet |
| PacketPtr pkt = new Packet(req, MemCmd::ReadReq); |
| pkt->allocate(); |
| |
| return pkt; |
| } |
| |
| PacketPtr |
| GUPSGen::getWritePacket(Addr addr, unsigned int size, uint8_t *data) |
| { |
| RequestPtr req = std::make_shared<Request>(addr, size, 0, |
| requestorId); |
| // Dummy PC to have PC-based prefetchers latch on; get entropy into higher |
| // bits |
| req->setPC(((Addr)requestorId) << 2); |
| |
| PacketPtr pkt = new Packet(req, MemCmd::WriteReq); |
| pkt->allocate(); |
| pkt->setData(data); |
| |
| return pkt; |
| } |
| |
| void |
| GUPSGen::handleResponse(PacketPtr pkt) |
| { |
| onTheFlyRequests--; |
| DPRINTF(GUPSGen, "%s: onTheFlyRequests: %d.\n", |
| __func__, onTheFlyRequests); |
| if (pkt->isWrite()) { |
| DPRINTF(GUPSGen, "%s: received a write resp. pkt->addr_range: %s," |
| " pkt->data: %d\n", __func__, |
| pkt->getAddrRange().to_string(), |
| *pkt->getPtr<uint64_t>()); |
| stats.totalUpdates++; |
| stats.totalWrites++; |
| stats.totalBytesWritten += elementSize; |
| stats.totalWriteLat += curTick() - exitTimes[pkt->req]; |
| |
| exitTimes.erase(pkt->req); |
| delete pkt; |
| } else { |
| DPRINTF(GUPSGen, "%s: received a read resp. pkt->addr_range: %s\n", |
| __func__, pkt->getAddrRange().to_string()); |
| |
| stats.totalReads++; |
| stats.totalBytesRead += elementSize; |
| stats.totalReadLat += curTick() - exitTimes[pkt->req]; |
| |
| exitTimes.erase(pkt->req); |
| |
| responsePool.push(pkt); |
| } |
| if (doneReading && requestPool.empty() && onTheFlyRequests == 0) { |
| exitSimLoop(name() + " is finished updating the memory.\n"); |
| return; |
| } |
| |
| if ((requestPool.size() < reqQueueSize) && |
| (!doneReading || !responsePool.empty()) && |
| !nextCreateEvent.scheduled()) |
| { |
| schedule(nextCreateEvent, nextCycle()); |
| } |
| } |
| |
| void |
| GUPSGen::wakeUp() |
| { |
| if (!nextSendEvent.scheduled() && !requestPool.empty()) { |
| schedule(nextSendEvent, nextCycle()); |
| } |
| } |
| |
| void |
| GUPSGen::createNextReq() |
| { |
| // Prioritize pending writes over reads |
| // Write as soon as the data is read |
| if (!responsePool.empty()) { |
| PacketPtr pkt = responsePool.front(); |
| responsePool.pop(); |
| |
| uint64_t *updated_value = pkt->getPtr<uint64_t>(); |
| DPRINTF(GUPSGen, "%s: Read value %lu from address %s", __func__, |
| *updated_value, pkt->getAddrRange().to_string()); |
| *updated_value ^= updateTable[pkt->req]; |
| updateTable.erase(pkt->req); |
| Addr addr = pkt->getAddr(); |
| PacketPtr new_pkt = getWritePacket(addr, |
| elementSize, (uint8_t*) updated_value); |
| delete pkt; |
| requestPool.push(new_pkt); |
| } else if (!doneReading) { |
| // If no writes then read |
| // Check to make sure we're not reading more than we should. |
| assert (readRequests < numUpdates); |
| |
| uint64_t value = readRequests; |
| uint64_t index = random_mt.random((int64_t) 0, tableSize); |
| Addr addr = indexToAddr(index); |
| PacketPtr pkt = getReadPacket(addr, elementSize); |
| updateTable[pkt->req] = value; |
| requestPool.push(pkt); |
| readRequests++; |
| |
| if (readRequests >= numUpdates) { |
| DPRINTF(GUPSGen, "%s: Done creating reads.\n", __func__); |
| doneReading = true; |
| } |
| else if (readRequests == updateLimit && updateLimit != 0) { |
| DPRINTF(GUPSGen, "%s: Update limit reached.\n", __func__); |
| doneReading = true; |
| } |
| } |
| |
| if (!nextCreateEvent.scheduled() && |
| (requestPool.size() < reqQueueSize) && |
| (!doneReading || !responsePool.empty())) |
| { |
| schedule(nextCreateEvent, nextCycle()); |
| } |
| |
| if (!nextSendEvent.scheduled() && !requestPool.empty()) { |
| schedule(nextSendEvent, nextCycle()); |
| } |
| } |
| |
| void |
| GUPSGen::sendNextReq() |
| { |
| if (!port.blocked()) { |
| PacketPtr pkt = requestPool.front(); |
| |
| exitTimes[pkt->req] = curTick(); |
| if (pkt->isWrite()) { |
| DPRINTF(GUPSGen, "%s: Sent write pkt, pkt->addr_range: " |
| "%s, pkt->data: %lu.\n", __func__, |
| pkt->getAddrRange().to_string(), |
| *pkt->getPtr<uint64_t>()); |
| } else { |
| DPRINTF(GUPSGen, "%s: Sent read pkt, pkt->addr_range: %s.\n", |
| __func__, pkt->getAddrRange().to_string()); |
| } |
| port.sendTimingPacket(pkt); |
| onTheFlyRequests++; |
| DPRINTF(GUPSGen, "%s: onTheFlyRequests: %d.\n", |
| __func__, onTheFlyRequests); |
| requestPool.pop(); |
| } |
| |
| if (!nextCreateEvent.scheduled() && |
| (requestPool.size() < reqQueueSize) && |
| (!doneReading || !responsePool.empty())) |
| { |
| schedule(nextCreateEvent, nextCycle()); |
| } |
| |
| if (!nextSendEvent.scheduled()) { |
| if (!requestPool.empty()) { |
| schedule(nextSendEvent, nextCycle()); |
| } |
| } |
| } |
| |
| |
| void |
| GUPSGen::GenPort::sendTimingPacket(PacketPtr pkt) |
| { |
| panic_if(_blocked, "Should never try to send if blocked MemSide!"); |
| |
| // If we can't send the packet across the port, store it for later. |
| if (!sendTimingReq(pkt)) { |
| DPRINTF(GUPSGen, "GenPort::%s: Packet blocked\n", __func__); |
| blockedPacket = pkt; |
| _blocked = true; |
| } else { |
| DPRINTF(GUPSGen, "GenPort::%s: Packet sent\n", __func__); |
| } |
| } |
| |
| void |
| GUPSGen::GenPort::sendFunctionalPacket(PacketPtr pkt) |
| { |
| sendFunctional(pkt); |
| } |
| |
| void |
| GUPSGen::GenPort::recvReqRetry() |
| { |
| // We should have a blocked packet if this function is called. |
| DPRINTF(GUPSGen, "GenPort::%s: Received a retry.\n", __func__); |
| |
| assert(_blocked && (blockedPacket != nullptr)); |
| // Try to resend it. It's possible that it fails again. |
| _blocked = false; |
| sendTimingPacket(blockedPacket); |
| if (!_blocked){ |
| blockedPacket = nullptr; |
| } |
| |
| owner->wakeUp(); |
| } |
| |
| bool |
| GUPSGen::GenPort::recvTimingResp(PacketPtr pkt) |
| { |
| owner->handleResponse(pkt); |
| return true; |
| } |
| |
| GUPSGen::GUPSGenStat::GUPSGenStat(GUPSGen* parent) : |
| statistics::Group(parent), |
| ADD_STAT(totalUpdates, statistics::units::Count::get(), |
| "Total number of updates the generator made in the memory"), |
| ADD_STAT(GUPS, statistics::units::Rate<statistics::units::Count, |
| statistics::units::Second>::get(), |
| "Rate of billion updates per second"), |
| ADD_STAT(totalReads, statistics::units::Count::get(), |
| "Total number of read requests"), |
| ADD_STAT(totalBytesRead, statistics::units::Byte::get(), |
| "Total number of bytes read"), |
| ADD_STAT(avgReadBW, statistics::units::Rate<statistics::units::Byte, |
| statistics::units::Second>::get(), |
| "Average read bandwidth received from memory"), |
| ADD_STAT(totalReadLat, statistics::units::Tick::get(), |
| "Total latency of read requests."), |
| ADD_STAT(avgReadLat, statistics::units::Tick::get(), |
| "Average latency for read requests"), |
| ADD_STAT(totalWrites, statistics::units::Count::get(), |
| "Total number of write requests"), |
| ADD_STAT(totalBytesWritten, statistics::units::Byte::get(), |
| "Total number of bytes written"), |
| ADD_STAT(avgWriteBW, statistics::units::Rate<statistics::units::Byte, |
| statistics::units::Second>::get(), |
| "Average write bandwidth received from memory"), |
| ADD_STAT(totalWriteLat, statistics::units::Tick::get(), |
| "Total latency of write requests."), |
| ADD_STAT(avgWriteLat, statistics::units::Tick::get(), |
| "Average latency for write requests") |
| {} |
| |
| void |
| GUPSGen::GUPSGenStat::regStats() |
| { |
| GUPS.precision(8); |
| avgReadBW.precision(2); |
| avgReadLat.precision(2); |
| avgWriteBW.precision(2); |
| avgWriteLat.precision(2); |
| |
| GUPS = (totalUpdates / 1e9) / simSeconds; |
| |
| avgReadBW = totalBytesRead / simSeconds; |
| avgReadLat = (totalReadLat) / totalReads; |
| |
| avgWriteBW = totalBytesWritten / simSeconds; |
| avgWriteLat = (totalWriteLat) / totalWrites; |
| } |
| |
| } |