<commit_before>/*
 * Copyright (c) 2015, <NAME>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 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.
 *
 * 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.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include <iomanip>
#include <sstream>

#include "debug/Dtu.hh"
#include "debug/DtuDetail.hh"
#include "debug/DtuPackets.hh"
#include "mem/dtu/dtu.hh"
#include "mem/page_table.hh"
#include "sim/system.hh"
#include "sim/process.hh"

Dtu::Dtu(DtuParams* p)
  : BaseDtu(p),
    atomicMode(p->system->isAtomicMode()),
    system(p->system),
    regFile(name() + ".regFile", p->num_endpoints),
    numEndpoints(p->num_endpoints),
    masterId(p->system->getMasterId(name())),
    maxNocPacketSize(p->max_noc_packet_size),
    numCmdEpidBits(p->num_cmd_epid_bits),
    numCmdOffsetBits(p->num_cmd_offset_bits),
    registerAccessLatency(p->register_access_latency),
    commandToSpmRequestLatency(p->command_to_spm_request_latency),
    commandToNocRequestLatency(p->command_to_noc_request_latency),
    spmResponseToNocRequestLatency(p->spm_response_to_noc_request_latency),
    nocMessageToSpmRequestLatency(p->noc_message_to_spm_request_latency),
    nocResponseToSpmRequestLatency(p->noc_message_to_spm_request_latency),
    nocRequestToSpmRequestLatency(p->noc_request_to_spm_request_latency),
    spmResponseToNocResponseLatency(p->spm_response_to_noc_response_latency),
    usePTable(p->use_ptable),
    executeCommandEvent(*this),
    finishOperationEvent(*this),
    incrementWritePtrEvent(*this)
{}

Addr
Dtu::translate(Addr vaddr)
{
    Addr paddr = vaddr;
    if (usePTable)
    {
        M5_VAR_USED auto pTable = system->threadContexts[coreId]->getProcessPtr()->pTable;
        assert(pTable != nullptr);
        assert(pTable->translate(vaddr, paddr));
    }

    return paddr;
}

PacketPtr
Dtu::generateRequest(Addr paddr, Addr size, MemCmd cmd)
{
    assert(numCmdEpidBits + numCmdOffsetBits + numCmdOpcodeBits <=
            sizeof(RegFile::reg_t) * 8);

    Request::Flags flags;

    auto req = new Request(paddr, size, flags, masterId);

    auto pkt = new Packet(req, cmd);
    auto pktData = new uint8_t[size];
    pkt->dataDynamic(pktData);

    return pkt;
}

Dtu::Command
Dtu::getCommand()
{
    using reg_t = RegFile::reg_t;

    /*
     *   COMMAND                        0
     * |--------------------------------|
     * |  offset  |   epid   |  opcode  |
     * |--------------------------------|
     */
    reg_t opcodeMask = (1 << numCmdOpcodeBits) - 1;
    reg_t epidMask   = ((1 << numCmdEpidBits) - 1) << numCmdOpcodeBits;
    reg_t offsetMask = ((1 << numCmdOffsetBits) - 1) << (numCmdOpcodeBits + numCmdEpidBits);

    auto reg = regFile.readDtuReg(DtuReg::COMMAND);

    Command cmd;

    cmd.opcode = static_cast<CommandOpcode>(reg & opcodeMask);

    cmd.epId = (reg & epidMask) >> numCmdOpcodeBits;

    cmd.offset = (reg & offsetMask) >> (numCmdEpidBits + numCmdOpcodeBits);

    return cmd;
}

void
Dtu::executeCommand()
{
    Command cmd = getCommand();

    assert(cmd.epId < numEndpoints);

    switch (cmd.opcode)
    {
    case CommandOpcode::IDLE:
        break;
    case CommandOpcode::START_OPERATION:
        startOperation(cmd);
        break;
    case CommandOpcode::INC_READ_PTR:
        incrementReadPtr(cmd.epId);
        break;
    default:
        // TODO error handling
        panic("Invalid opcode %#x\n", static_cast<RegFile::reg_t>(cmd.opcode));
    }
}

void
Dtu::startOperation(Command& cmd)
{
    // set busy flag
    regFile.setDtuReg(DtuReg::STATUS, 1);

    EpMode mode = static_cast<EpMode>(regFile.readEpReg(cmd.epId, EpReg::MODE));

    switch (mode)
    {
    case EpMode::RECEIVE_MESSAGE: // do a reply
    case EpMode::TRANSMIT_MESSAGE:
        startMessageTransmission(cmd);
        break;
    case EpMode::READ_MEMORY:
        startMemoryRead(cmd);
        break;
    case EpMode::WRITE_MEMORY:
        startMemoryWrite(cmd);
        break;
    default:
        // TODO Error handling
        panic("EP%u: Invalid mode\n", cmd.epId);
    }
}

void
Dtu::finishOperation()
{
    DPRINTF(DtuDetail, "Operation finished\n");
    // reset command register and unset busy flag
    regFile.setDtuReg(DtuReg::COMMAND, 0);
    regFile.setDtuReg(DtuReg::STATUS, 0);
}

void
Dtu::sendSpmRequest(PacketPtr pkt,
                    unsigned epId,
                    Cycles delay,
                    SpmPacketType packetType)
{
    pkt->setAddr(translate(pkt->getAddr()));

    auto senderState = new SpmSenderState();
    senderState->epId = epId;
    senderState->packetType = packetType;

    pkt->pushSenderState(senderState);

    if (atomicMode)
    {
        sendAtomicSpmRequest(pkt);
        completeSpmRequest(pkt);
    }
    else
    {
        schedSpmRequest(pkt, clockEdge(delay));
    }
}

void
Dtu::startMessageTransmission(const Command& cmd)
{
    unsigned targetCoreId = regFile.readEpReg(cmd.epId, EpReg::TGT_COREID);
    Addr messageAddr = regFile.readEpReg(cmd.epId, EpReg::MSG_ADDR);
    Addr messageSize = regFile.readEpReg(cmd.epId, EpReg::MSG_SIZE);
    unsigned maxMessageSize = regFile.readEpReg(cmd.epId, EpReg::MAX_MSG_SIZE);
    unsigned credits = regFile.readEpReg(cmd.epId, EpReg::CREDITS);

    EpMode mode = static_cast<EpMode>(regFile.readEpReg(cmd.epId, EpReg::MODE));

    /*
     * If this endpoint is configured to send messages, we need to check
     * credits. If it is configured to receive messages we do a reply and don't
     * need to check credits.
     */
    if (mode == EpMode::TRANSMIT_MESSAGE)
    {
        if (credits < maxMessageSize)
        {
            warn("pe%u.ep%u: Ignore send message commad because there are not "
                 "enough credits", coreId, cmd.epId);
            schedule(finishOperationEvent, clockEdge(Cycles(1)));
            return;
        }

        DPRINTF(DtuDetail, "EP%u pays %u credits\n", cmd.epId, maxMessageSize);

        // Pay some credits
        credits -= maxMessageSize;
        regFile.setEpReg(cmd.epId, EpReg::CREDITS, credits);
    }

    DPRINTF(Dtu, "\e[1m[%s -> %u]\e[0m with EP%u of %#018lx:%lu\n",
        mode == EpMode::TRANSMIT_MESSAGE ? "sd" : "rp",
        targetCoreId, cmd.epId, messageAddr, messageSize);

    DPRINTF(DtuDetail, "Read message of %lu Bytes at address %#018lx from local scratchpad.\n",
                 messageSize,
                 messageAddr);

    // TODO error handling
    assert(messageSize > 0);
    assert(messageSize + sizeof(MessageHeader) <= maxMessageSize);
    assert(messageSize + sizeof(MessageHeader) <= maxNocPacketSize);

    auto pkt = generateRequest(messageAddr, messageSize, MemCmd::ReadReq);

    sendSpmRequest(pkt,
                   cmd.epId,
                   commandToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::startMemoryRead(const Command& cmd)
{
    unsigned targetCoreId = regFile.readEpReg(cmd.epId, EpReg::TGT_COREID);
    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);
    Addr remoteAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_REM_ADDR);
    remoteAddr += cmd.offset;

    DPRINTF(Dtu, "\e[1m[rd -> %u]\e[0m with EP%u into %#018lx:%lu\n",
        targetCoreId, cmd.epId, localAddr, requestSize);

    // TODO error handling
    assert(requestSize > 0);
    assert(requestSize < maxNocPacketSize);

    auto pkt = generateRequest(getNocAddr(targetCoreId) | remoteAddr, requestSize, MemCmd::ReadReq);

    sendNocRequest(pkt,
                   commandToNocRequestLatency,
                   false);
}

void
Dtu::startMemoryWrite(const Command& cmd)
{
    unsigned targetCoreId = regFile.readEpReg(cmd.epId, EpReg::TGT_COREID);
    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);

    DPRINTF(Dtu, "\e[1m[wr -> %u]\e[0m with EP%u from %#018lx:%lu\n",
        targetCoreId, cmd.epId, localAddr, requestSize);

    // TODO error handling
    assert(requestSize > 0);
    assert(requestSize <= maxNocPacketSize);

    auto pkt = generateRequest(localAddr, requestSize, MemCmd::ReadReq);

    sendSpmRequest(pkt,
                   cmd.epId,
                   commandToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::incrementReadPtr(unsigned epId)
{
    Addr readPtr    = regFile.readEpReg(epId, EpReg::BUF_RD_PTR);
    Addr bufferAddr = regFile.readEpReg(epId, EpReg::BUF_ADDR);
    Addr bufferSize = regFile.readEpReg(epId, EpReg::BUF_SIZE);
    Addr messageCount = regFile.readEpReg(epId, EpReg::BUF_MSG_CNT);
    unsigned maxMessageSize = regFile.readEpReg(epId, EpReg::MAX_MSG_SIZE);

    readPtr += maxMessageSize;

    if (readPtr >= bufferAddr + bufferSize * maxMessageSize)
        readPtr = bufferAddr;

    DPRINTF(DtuDetail, "EP%u: Increment the read pointer to %#018lx\n",
                 epId,
                 readPtr);

    // TODO error handling
    assert(messageCount != 0);

    /*
     * XXX Actually an additianally cycle is needed to update the register.
     *     We ignore this delay as it should have no or a very small influence
     *     on the performance of the simulated system.
     */

    regFile.setEpReg(epId, EpReg::BUF_RD_PTR, readPtr);
    regFile.setEpReg(epId, EpReg::BUF_MSG_CNT, messageCount - 1);
}

void
Dtu::incrementWritePtr(unsigned epId)
{
    Addr writePtr     = regFile.readEpReg(epId, EpReg::BUF_WR_PTR);
    Addr bufferAddr   = regFile.readEpReg(epId, EpReg::BUF_ADDR);
    Addr bufferSize   = regFile.readEpReg(epId, EpReg::BUF_SIZE);
    Addr messageCount = regFile.readEpReg(epId, EpReg::BUF_MSG_CNT);
    unsigned maxMessageSize = regFile.readEpReg(epId, EpReg::MAX_MSG_SIZE);

    writePtr += maxMessageSize;

    if (writePtr >= bufferAddr + bufferSize * maxMessageSize)
        writePtr = bufferAddr;

    DPRINTF(DtuDetail, "EP%u: Increment the write pointer to %#018lx\n",
                 epId,
                 writePtr);

    assert(messageCount < bufferSize);

    regFile.setEpReg(epId, EpReg::BUF_WR_PTR, writePtr);
    regFile.setEpReg(epId, EpReg::BUF_MSG_CNT, messageCount + 1);
}

void
Dtu::completeNocRequest(PacketPtr pkt)
{
    DPRINTF(DtuDetail, "Received %s response from remote DTU.\n",
                 pkt->isRead() ? "read" : "write");

    if (pkt->isWrite())
        completeNocWriteRequest(pkt);
    else if (pkt->isRead()) {
        printPacket(pkt);
        completeNocReadRequest(pkt);
    }
    else
        panic("unexpected packet type\n");

    // clean up
    delete pkt->req;
    delete pkt;
}

void
Dtu::completeNocWriteRequest(PacketPtr pkt)
{
    Cycles delay = ticksToCycles(pkt->headerDelay + pkt->payloadDelay);

    schedule(finishOperationEvent, clockEdge(delay));
}

void
Dtu::completeNocReadRequest(PacketPtr pkt)
{
    auto cmd = getCommand();

    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);

    DPRINTF(DtuDetail, "Write %lu bytes to local scratchpad at address %#018lx.\n",
                 requestSize,
                 localAddr);

    Cycles delay = ticksToCycles(pkt->headerDelay);

    auto spmPkt = generateRequest(localAddr, requestSize, MemCmd::WriteReq);
    spmPkt->payloadDelay = pkt->payloadDelay;

    memcpy(spmPkt->getPtr<uint8_t>(), pkt->getPtr<uint8_t>(), requestSize);

    sendSpmRequest(spmPkt,
                   cmd.epId,
                   delay + nocResponseToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::completeSpmRequest(PacketPtr pkt)
{
    assert(!pkt->isError());
    assert(pkt->isResponse());

    DPRINTF(DtuDetail, "Received response from scratchpad.\n");

    auto senderState = dynamic_cast<SpmSenderState*>(pkt->popSenderState());

    switch (senderState->packetType)
    {
    case SpmPacketType::LOCAL_REQUEST:
        completeLocalSpmRequest(pkt);
        break;
    case SpmPacketType::FORWARDED_MESSAGE:
        completeForwardedMessage(pkt, senderState->epId);
        break;
    case SpmPacketType::FORWARDED_REQUEST:
        completeForwardedRequest(pkt);
        break;
    default:
        panic("Unexpected SpmPacketType\n");
    }

    delete senderState;
}

void
Dtu::sendNocRequest(PacketPtr pkt, Cycles delay, bool isMessage)
{
    auto senderState = new NocSenderState();
    senderState->packetType = isMessage ? NocPacketType::MESSAGE :
                                          NocPacketType::REQUEST;

    pkt->pushSenderState(senderState);

    if (atomicMode)
    {
        sendAtomicNocRequest(pkt);
        completeNocRequest(pkt);
    }
    else
    {
        schedNocRequest(pkt, clockEdge(delay));
    }
}

void
Dtu::sendNocMessage(const uint8_t* data,
                    Addr messageSize,
                    bool isReply,
                    Tick spmPktHeaderDelay,
                    Tick spmPktPayloadDelay)
{
    unsigned epid = getCommand().epId;

    unsigned targetCoreId;
    unsigned targetEpId;
    unsigned replyEpId;
    uint64_t label;
    uint64_t replyLabel;

    M5_VAR_USED unsigned maxMessageSize = regFile.readEpReg(epid, EpReg::MAX_MSG_SIZE);

    if (isReply)
    {
        /*
         * We need to read the header of the received message from scratchpad
         * to determine target core and enspoint ID. This would introduce a
         * second scratchpad request and would make the control flow more
         * complicated. To simplify things a functional request is used and an
         * additional delay is payed.
         */

        auto pkt = generateRequest(
                translate(regFile.readEpReg(epid, EpReg::BUF_RD_PTR)),
                sizeof(MessageHeader),
                MemCmd::ReadReq);

        spmMasterPort.sendFunctional(pkt);

        auto h = pkt->getPtr<MessageHeader>();

        targetCoreId = h->senderCoreId;
        targetEpId   = h->replyEpId;  // send message to the reply EP
        replyEpId    = h->senderEpId; // and grant credits to the sender

        // the receiver of the reply should get the label that he has set
        label        = h->replyLabel;
        // replies don't have replies. so, we don't need that
        replyLabel   = 0;
    }
    else
    {
        targetCoreId = regFile.readEpReg(epid, EpReg::TGT_COREID);
        targetEpId   = regFile.readEpReg(epid, EpReg::TGT_EPID);
        replyEpId    = regFile.readEpReg(epid, EpReg::REPLY_EPID);
        label        = regFile.readEpReg(epid, EpReg::LABEL);
        replyLabel   = regFile.readEpReg(epid, EpReg::REPLY_LABEL);
    }

    DPRINTF(Dtu, "  header: tgtEP=%u, lbl=%#018lx, rpLbl=%#018lx, rpEP=%u\n",
        targetEpId, label, replyLabel, replyEpId);

    DPRINTF(DtuDetail, "Send %s of %lu bytes to EP%u at PE%u.\n",
                 isReply ? "reply" : "message",
                 messageSize,
                 targetEpId,
                 targetCoreId);

    assert(regFile.readEpReg(epid, EpReg::MSG_SIZE) == messageSize);
    assert(messageSize + sizeof(MessageHeader) <= maxMessageSize);

    MessageHeader header;
<commit_msg>DTU: moved print of message sending into sendNocMessage.<commit_after>/*
 * Copyright (c) 2015, <NAME>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 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.
 *
 * 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.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include <iomanip>
#include <sstream>

#include "debug/Dtu.hh"
#include "debug/DtuDetail.hh"
#include "debug/DtuPackets.hh"
#include "mem/dtu/dtu.hh"
#include "mem/page_table.hh"
#include "sim/system.hh"
#include "sim/process.hh"

Dtu::Dtu(DtuParams* p)
  : BaseDtu(p),
    atomicMode(p->system->isAtomicMode()),
    system(p->system),
    regFile(name() + ".regFile", p->num_endpoints),
    numEndpoints(p->num_endpoints),
    masterId(p->system->getMasterId(name())),
    maxNocPacketSize(p->max_noc_packet_size),
    numCmdEpidBits(p->num_cmd_epid_bits),
    numCmdOffsetBits(p->num_cmd_offset_bits),
    registerAccessLatency(p->register_access_latency),
    commandToSpmRequestLatency(p->command_to_spm_request_latency),
    commandToNocRequestLatency(p->command_to_noc_request_latency),
    spmResponseToNocRequestLatency(p->spm_response_to_noc_request_latency),
    nocMessageToSpmRequestLatency(p->noc_message_to_spm_request_latency),
    nocResponseToSpmRequestLatency(p->noc_message_to_spm_request_latency),
    nocRequestToSpmRequestLatency(p->noc_request_to_spm_request_latency),
    spmResponseToNocResponseLatency(p->spm_response_to_noc_response_latency),
    usePTable(p->use_ptable),
    executeCommandEvent(*this),
    finishOperationEvent(*this),
    incrementWritePtrEvent(*this)
{}

Addr
Dtu::translate(Addr vaddr)
{
    Addr paddr = vaddr;
    if (usePTable)
    {
        M5_VAR_USED auto pTable = system->threadContexts[coreId]->getProcessPtr()->pTable;
        assert(pTable != nullptr);
        assert(pTable->translate(vaddr, paddr));
    }

    return paddr;
}

PacketPtr
Dtu::generateRequest(Addr paddr, Addr size, MemCmd cmd)
{
    assert(numCmdEpidBits + numCmdOffsetBits + numCmdOpcodeBits <=
            sizeof(RegFile::reg_t) * 8);

    Request::Flags flags;

    auto req = new Request(paddr, size, flags, masterId);

    auto pkt = new Packet(req, cmd);
    auto pktData = new uint8_t[size];
    pkt->dataDynamic(pktData);

    return pkt;
}

Dtu::Command
Dtu::getCommand()
{
    using reg_t = RegFile::reg_t;

    /*
     *   COMMAND                        0
     * |--------------------------------|
     * |  offset  |   epid   |  opcode  |
     * |--------------------------------|
     */
    reg_t opcodeMask = (1 << numCmdOpcodeBits) - 1;
    reg_t epidMask   = ((1 << numCmdEpidBits) - 1) << numCmdOpcodeBits;
    reg_t offsetMask = ((1 << numCmdOffsetBits) - 1) << (numCmdOpcodeBits + numCmdEpidBits);

    auto reg = regFile.readDtuReg(DtuReg::COMMAND);

    Command cmd;

    cmd.opcode = static_cast<CommandOpcode>(reg & opcodeMask);

    cmd.epId = (reg & epidMask) >> numCmdOpcodeBits;

    cmd.offset = (reg & offsetMask) >> (numCmdEpidBits + numCmdOpcodeBits);

    return cmd;
}

void
Dtu::executeCommand()
{
    Command cmd = getCommand();

    assert(cmd.epId < numEndpoints);

    switch (cmd.opcode)
    {
    case CommandOpcode::IDLE:
        break;
    case CommandOpcode::START_OPERATION:
        startOperation(cmd);
        break;
    case CommandOpcode::INC_READ_PTR:
        incrementReadPtr(cmd.epId);
        break;
    default:
        // TODO error handling
        panic("Invalid opcode %#x\n", static_cast<RegFile::reg_t>(cmd.opcode));
    }
}

void
Dtu::startOperation(Command& cmd)
{
    // set busy flag
    regFile.setDtuReg(DtuReg::STATUS, 1);

    EpMode mode = static_cast<EpMode>(regFile.readEpReg(cmd.epId, EpReg::MODE));

    switch (mode)
    {
    case EpMode::RECEIVE_MESSAGE: // do a reply
    case EpMode::TRANSMIT_MESSAGE:
        startMessageTransmission(cmd);
        break;
    case EpMode::READ_MEMORY:
        startMemoryRead(cmd);
        break;
    case EpMode::WRITE_MEMORY:
        startMemoryWrite(cmd);
        break;
    default:
        // TODO Error handling
        panic("EP%u: Invalid mode\n", cmd.epId);
    }
}

void
Dtu::finishOperation()
{
    DPRINTF(DtuDetail, "Operation finished\n");
    // reset command register and unset busy flag
    regFile.setDtuReg(DtuReg::COMMAND, 0);
    regFile.setDtuReg(DtuReg::STATUS, 0);
}

void
Dtu::sendSpmRequest(PacketPtr pkt,
                    unsigned epId,
                    Cycles delay,
                    SpmPacketType packetType)
{
    pkt->setAddr(translate(pkt->getAddr()));

    auto senderState = new SpmSenderState();
    senderState->epId = epId;
    senderState->packetType = packetType;

    pkt->pushSenderState(senderState);

    if (atomicMode)
    {
        sendAtomicSpmRequest(pkt);
        completeSpmRequest(pkt);
    }
    else
    {
        schedSpmRequest(pkt, clockEdge(delay));
    }
}

void
Dtu::startMessageTransmission(const Command& cmd)
{
    Addr messageAddr = regFile.readEpReg(cmd.epId, EpReg::MSG_ADDR);
    Addr messageSize = regFile.readEpReg(cmd.epId, EpReg::MSG_SIZE);
    unsigned maxMessageSize = regFile.readEpReg(cmd.epId, EpReg::MAX_MSG_SIZE);
    unsigned credits = regFile.readEpReg(cmd.epId, EpReg::CREDITS);

    EpMode mode = static_cast<EpMode>(regFile.readEpReg(cmd.epId, EpReg::MODE));

    /*
     * If this endpoint is configured to send messages, we need to check
     * credits. If it is configured to receive messages we do a reply and don't
     * need to check credits.
     */
    if (mode == EpMode::TRANSMIT_MESSAGE)
    {
        if (credits < maxMessageSize)
        {
            warn("pe%u.ep%u: Ignore send message commad because there are not "
                 "enough credits", coreId, cmd.epId);
            schedule(finishOperationEvent, clockEdge(Cycles(1)));
            return;
        }

        DPRINTF(DtuDetail, "EP%u pays %u credits\n", cmd.epId, maxMessageSize);

        // Pay some credits
        credits -= maxMessageSize;
        regFile.setEpReg(cmd.epId, EpReg::CREDITS, credits);
    }

    DPRINTF(DtuDetail, "Read message of %lu Bytes at address %#018lx from local scratchpad.\n",
                 messageSize,
                 messageAddr);

    // TODO error handling
    assert(messageSize > 0);
    assert(messageSize + sizeof(MessageHeader) <= maxMessageSize);
    assert(messageSize + sizeof(MessageHeader) <= maxNocPacketSize);

    auto pkt = generateRequest(messageAddr, messageSize, MemCmd::ReadReq);

    sendSpmRequest(pkt,
                   cmd.epId,
                   commandToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::startMemoryRead(const Command& cmd)
{
    unsigned targetCoreId = regFile.readEpReg(cmd.epId, EpReg::TGT_COREID);
    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);
    Addr remoteAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_REM_ADDR);
    remoteAddr += cmd.offset;

    DPRINTF(Dtu, "\e[1m[rd -> %u]\e[0m with EP%u into %#018lx:%lu\n",
        targetCoreId, cmd.epId, localAddr, requestSize);

    // TODO error handling
    assert(requestSize > 0);
    assert(requestSize < maxNocPacketSize);

    auto pkt = generateRequest(getNocAddr(targetCoreId) | remoteAddr, requestSize, MemCmd::ReadReq);

    sendNocRequest(pkt,
                   commandToNocRequestLatency,
                   false);
}

void
Dtu::startMemoryWrite(const Command& cmd)
{
    unsigned targetCoreId = regFile.readEpReg(cmd.epId, EpReg::TGT_COREID);
    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);

    DPRINTF(Dtu, "\e[1m[wr -> %u]\e[0m with EP%u from %#018lx:%lu\n",
        targetCoreId, cmd.epId, localAddr, requestSize);

    // TODO error handling
    assert(requestSize > 0);
    assert(requestSize <= maxNocPacketSize);

    auto pkt = generateRequest(localAddr, requestSize, MemCmd::ReadReq);

    sendSpmRequest(pkt,
                   cmd.epId,
                   commandToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::incrementReadPtr(unsigned epId)
{
    Addr readPtr    = regFile.readEpReg(epId, EpReg::BUF_RD_PTR);
    Addr bufferAddr = regFile.readEpReg(epId, EpReg::BUF_ADDR);
    Addr bufferSize = regFile.readEpReg(epId, EpReg::BUF_SIZE);
    Addr messageCount = regFile.readEpReg(epId, EpReg::BUF_MSG_CNT);
    unsigned maxMessageSize = regFile.readEpReg(epId, EpReg::MAX_MSG_SIZE);

    readPtr += maxMessageSize;

    if (readPtr >= bufferAddr + bufferSize * maxMessageSize)
        readPtr = bufferAddr;

    DPRINTF(DtuDetail, "EP%u: Increment the read pointer to %#018lx\n",
                 epId,
                 readPtr);

    // TODO error handling
    assert(messageCount != 0);

    /*
     * XXX Actually an additianally cycle is needed to update the register.
     *     We ignore this delay as it should have no or a very small influence
     *     on the performance of the simulated system.
     */

    regFile.setEpReg(epId, EpReg::BUF_RD_PTR, readPtr);
    regFile.setEpReg(epId, EpReg::BUF_MSG_CNT, messageCount - 1);
}

void
Dtu::incrementWritePtr(unsigned epId)
{
    Addr writePtr     = regFile.readEpReg(epId, EpReg::BUF_WR_PTR);
    Addr bufferAddr   = regFile.readEpReg(epId, EpReg::BUF_ADDR);
    Addr bufferSize   = regFile.readEpReg(epId, EpReg::BUF_SIZE);
    Addr messageCount = regFile.readEpReg(epId, EpReg::BUF_MSG_CNT);
    unsigned maxMessageSize = regFile.readEpReg(epId, EpReg::MAX_MSG_SIZE);

    writePtr += maxMessageSize;

    if (writePtr >= bufferAddr + bufferSize * maxMessageSize)
        writePtr = bufferAddr;

    DPRINTF(DtuDetail, "EP%u: Increment the write pointer to %#018lx\n",
                 epId,
                 writePtr);

    assert(messageCount < bufferSize);

    regFile.setEpReg(epId, EpReg::BUF_WR_PTR, writePtr);
    regFile.setEpReg(epId, EpReg::BUF_MSG_CNT, messageCount + 1);
}

void
Dtu::completeNocRequest(PacketPtr pkt)
{
    DPRINTF(DtuDetail, "Received %s response from remote DTU.\n",
                 pkt->isRead() ? "read" : "write");

    if (pkt->isWrite())
        completeNocWriteRequest(pkt);
    else if (pkt->isRead()) {
        printPacket(pkt);
        completeNocReadRequest(pkt);
    }
    else
        panic("unexpected packet type\n");

    // clean up
    delete pkt->req;
    delete pkt;
}

void
Dtu::completeNocWriteRequest(PacketPtr pkt)
{
    Cycles delay = ticksToCycles(pkt->headerDelay + pkt->payloadDelay);

    schedule(finishOperationEvent, clockEdge(delay));
}

void
Dtu::completeNocReadRequest(PacketPtr pkt)
{
    auto cmd = getCommand();

    Addr localAddr = regFile.readEpReg(cmd.epId, EpReg::REQ_LOC_ADDR);
    Addr requestSize = regFile.readEpReg(cmd.epId, EpReg::REQ_SIZE);

    DPRINTF(DtuDetail, "Write %lu bytes to local scratchpad at address %#018lx.\n",
                 requestSize,
                 localAddr);

    Cycles delay = ticksToCycles(pkt->headerDelay);

    auto spmPkt = generateRequest(localAddr, requestSize, MemCmd::WriteReq);
    spmPkt->payloadDelay = pkt->payloadDelay;

    memcpy(spmPkt->getPtr<uint8_t>(), pkt->getPtr<uint8_t>(), requestSize);

    sendSpmRequest(spmPkt,
                   cmd.epId,
                   delay + nocResponseToSpmRequestLatency,
                   SpmPacketType::LOCAL_REQUEST);
}

void
Dtu::completeSpmRequest(PacketPtr pkt)
{
    assert(!pkt->isError());
    assert(pkt->isResponse());

    DPRINTF(DtuDetail, "Received response from scratchpad.\n");

    auto senderState = dynamic_cast<SpmSenderState*>(pkt->popSenderState());

    switch (senderState->packetType)
    {
    case SpmPacketType::LOCAL_REQUEST:
        completeLocalSpmRequest(pkt);
        break;
    case SpmPacketType::FORWARDED_MESSAGE:
        completeForwardedMessage(pkt, senderState->epId);
        break;
    case SpmPacketType::FORWARDED_REQUEST:
        completeForwardedRequest(pkt);
        break;
    default:
        panic("Unexpected SpmPacketType\n");
    }

    delete senderState;
}

void
Dtu::sendNocRequest(PacketPtr pkt, Cycles delay, bool isMessage)
{
    auto senderState = new NocSenderState();
    senderState->packetType = isMessage ? NocPacketType::MESSAGE :
                                          NocPacketType::REQUEST;

    pkt->pushSenderState(senderState);

    if (atomicMode)
    {
        sendAtomicNocRequest(pkt);
        completeNocRequest(pkt);
    }
    else
    {
        schedNocRequest(pkt, clockEdge(delay));
    }
}

void
Dtu::sendNocMessage(const uint8_t* data,
                    Addr messageSize,
                    bool isReply,
                    Tick spmPktHeaderDelay,
                    Tick spmPktPayloadDelay)
{
    unsigned epid = getCommand().epId;

    unsigned targetCoreId;
    unsigned targetEpId;
    unsigned replyEpId;
    uint64_t label;
    uint64_t replyLabel;

    M5_VAR_USED unsigned maxMessageSize = regFile.readEpReg(epid, EpReg::MAX_MSG_SIZE);

    if (isReply)
    {
        /*
         * We need to read the header of the received message from scratchpad
         * to determine target core and enspoint ID. This would introduce a
         * second scratchpad request and would make the control flow more
         * complicated. To simplify things a functional request is used and an
         * additional delay is payed.
         */

        auto pkt = generateRequest(
                translate(regFile.readEpReg(epid, EpReg::BUF_RD_PTR)),
                sizeof(MessageHeader),
                MemCmd::ReadReq);

        spmMasterPort.sendFunctional(pkt);

        auto h = pkt->getPtr<MessageHeader>();

        targetCoreId = h->senderCoreId;
        targetEpId   = h->replyEpId;  // send message to the reply EP
        replyEpId    = h->senderEpId; // and grant credits to the sender

        // the receiver of the reply should get the label that he has set
        label        = h->replyLabel;
        // replies don't have replies. so, we don't need that
        replyLabel   = 0;
    }
    else
    {
        targetCoreId = regFile.readEpReg(epid, EpReg::TGT_COREID);
        targetEpId   = regFile.readEpReg(epid, EpReg::TGT_EPID);
        replyEpId    = regFile.readEpReg(epid, EpReg::REPLY_EPID);
        label        = regFile.readEpReg(epid, EpReg::LABEL);
        replyLabel   = regFile.readEpReg(epid, EpReg::REPLY_LABEL);
    }

    DPRINTF(Dtu, "\e[1m[%s -> %u]\e[0m with EP%u of %#018lx:%lu\n",
        isReply ? "rp" : "sd",
        targetCoreId, epid, regFile.readEpReg(epid, EpReg::MSG_ADDR), messageSize);
    DPRINTF(Dtu, "  header: tgtEP=%u, lbl=%#018lx, rpLbl=%#018lx, rpEP=%u\n",
        targetEpId, label, replyLabel, replyEpId);

    DPRINTF(DtuDetail, "Send %s of %lu bytes to EP%u at PE%u.\n",
                 isReply ? "reply" : "message",
                 messageSize,
                 targetEpId,
                 targetCoreId);

    assert(regFile.readEpReg(epid, EpReg::MSG_SIZE) == messageSize);
    assert(messageSize + sizeof(MessageHeader) <= maxMessageSize);

    MessageHeader header;

{'fraction_non_alphanumeric': 0.11054141488127937, 'fraction_numerical': 0.004150643960202649, 'mean_word_length': 2.9862530413625303, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 153, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '612940', 'n_tokens_mistral': 10745, 'n_tokens_neox': 10302, 'n_words': 2329}