#include "mem/hmc_controller.hh"

#include "base/random.hh"
#include "debug/HMCController.hh"

HMCController::HMCController(const HMCControllerParams* p) :
    NoncoherentXBar(p),
    n_master_ports(p->port_master_connection_count),
    rr_counter(0)
{
    assert(p->port_slave_connection_count == 1);
}

HMCController*
HMCControllerParams::create()
{
    return new HMCController(this);
}

// Since this module is a load distributor, all its master ports have the same
//  range so we should keep only one of the ranges and ignore the others
void HMCController::recvRangeChange(PortID master_port_id)
{
    if (master_port_id == 0)
    {
       gotAllAddrRanges = true;
       BaseXBar::recvRangeChange(master_port_id);
    }
    else
        gotAddrRanges[master_port_id] = true;
}

int HMCController::rotate_counter()
{
    int current_value = rr_counter;
    rr_counter++;
    if (rr_counter == n_master_ports)
        rr_counter = 0;
    return current_value;
}

bool HMCController::recvTimingReq(PacketPtr pkt, PortID slave_port_id)
{
    // determine the source port based on the id
    SlavePort *src_port = slavePorts[slave_port_id];

    // we should never see express snoops on a non-coherent component
    assert(!pkt->isExpressSnoop());

    // For now, this is a simple round robin counter, for distribution the
    //  load among the serial links
    PortID master_port_id = rotate_counter();

    // test if the layer should be considered occupied for the current
    // port
    if (!reqLayers[master_port_id]->tryTiming(src_port)) {
        DPRINTF(HMCController, "recvTimingReq: src %s %s 0x%x BUSY\n",
                src_port->name(), pkt->cmdString(), pkt->getAddr());
        return false;
    }

    DPRINTF(HMCController, "recvTimingReq: src %s %s 0x%x\n",
            src_port->name(), pkt->cmdString(), pkt->getAddr());

    // store size and command as they might be modified when
    // forwarding the packet
    unsigned int pkt_size = pkt->hasData() ? pkt->getSize() : 0;
    unsigned int pkt_cmd = pkt->cmdToIndex();

    // store the old header delay so we can restore it if needed
    Tick old_header_delay = pkt->headerDelay;

    // a request sees the frontend and forward latency
    Tick xbar_delay = (frontendLatency + forwardLatency) * clockPeriod();

    // set the packet header and payload delay
    calcPacketTiming(pkt, xbar_delay);

    // determine how long to be layer is busy
    Tick packetFinishTime = clockEdge(Cycles(1)) + pkt->payloadDelay;

    // before forwarding the packet (and possibly altering it),
    // remember if we are expecting a response
    const bool expect_response = pkt->needsResponse() &&
        !pkt->cacheResponding();

    // since it is a normal request, attempt to send the packet
    bool success = masterPorts[master_port_id]->sendTimingReq(pkt);

    if (!success)  {
        DPRINTF(HMCController, "recvTimingReq: src %s %s 0x%x RETRY\n",
                src_port->name(), pkt->cmdString(), pkt->getAddr());

        // restore the header delay as it is additive
        pkt->headerDelay = old_header_delay;

        // occupy until the header is sent
        reqLayers[master_port_id]->failedTiming(src_port,
                                                clockEdge(Cycles(1)));

        return false;
    }

    // remember where to route the response to
    if (expect_response) {
        assert(routeTo.find(pkt->req) == routeTo.end());
        routeTo[pkt->req] = slave_port_id;
    }

    reqLayers[master_port_id]->succeededTiming(packetFinishTime);

    // stats updates
    pktCount[slave_port_id][master_port_id]++;
    pktSize[slave_port_id][master_port_id] += pkt_size;
    transDist[pkt_cmd]++;

    return true;
}
