/*
 * Copyright (c) 2015, University of Kaiserslautern
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER
 * 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.
 *
 * Authors:
 *    Robert Gernhardt
 *    Matthias Jung
 *    Eder F. Zulian
 */

#include "Arbiter.h"

using namespace std;
using namespace tlm;

Arbiter::Arbiter(sc_module_name) : payloadEventQueue(this, &Arbiter::peqCallback)
{
    // The arbiter communicates with one or more memory unity through one or more sockets (one or more memory channels).
    // Each of the arbiter's initiator sockets is bound to a memory controller's target socket.
    // Anytime an transaction comes from a memory unity to the arbiter the "bw" callback is called.
    iSocket.register_nb_transport_bw(this, &Arbiter::nb_transport_bw);

    for (size_t i = 0; i < Configuration::getInstance().NumberOfMemChannels; ++i)
    {
        channelIsFree.push_back(true);
        pendingRequests.push_back(queue<tlm_generic_payload *>());
    }

    // One or more devices can accesss all the memory units through the arbiter.
    // Devices' initiator sockets are bound to arbiter's target sockets.
    // As soon the arbiter receives a request in any of its target sockets it should treat and forward it to the proper memory channel.
    tSocket.register_nb_transport_fw(this, &Arbiter::nb_transport_fw);

    tSocket.register_transport_dbg(this, &Arbiter::transport_dbg);
}

// Initiated by initiator side
// This function is called when an arbiter's target socket receives a transaction from a device
tlm_sync_enum Arbiter::nb_transport_fw(int id, tlm_generic_payload &payload,
                              tlm_phase &phase, sc_time &fwDelay)
{
    sc_time notDelay = clkAlign(sc_time_stamp() + fwDelay) -
                       (sc_time_stamp() + fwDelay);
    if (phase == BEGIN_REQ)
    {
        // adjust address offset:
        payload.set_address(payload.get_address() -
                            Configuration::getInstance().AddressOffset);

        // In the begin request phase the socket ID is appended to the payload.
        // It will extracted from the payload and used later.
        appendDramExtension(id, payload);
        payload.acquire();
    }
    else if (phase == END_RESP)
    {
        notDelay += Configuration::getInstance().memSpec.clk;
        payload.release();
    }

    printDebugMessage("[fw] " + phaseNameToString(phase) + " notification in " +
                      notDelay.to_string());
    payloadEventQueue.notify(payload, phase, notDelay);
    return TLM_ACCEPTED;
}

// Initiated by dram side
// This function is called when an arbiter's initiator socket receives a transaction from a memory controller
tlm_sync_enum Arbiter::nb_transport_bw(int channelId, tlm_generic_payload &payload,
                              tlm_phase &phase, sc_time &bwDelay)
{
    // Check channel ID
    if ((unsigned int)channelId != DramExtension::getExtension(payload).getChannel().ID())
        SC_REPORT_FATAL("Arbiter", "Payload extension was corrupted");

    printDebugMessage("[bw] " + phaseNameToString(phase) + " notification in " +
                      bwDelay.to_string());
    payloadEventQueue.notify(payload, phase, bwDelay);
    return TLM_ACCEPTED;
}

unsigned int Arbiter::transport_dbg(int /*id*/, tlm::tlm_generic_payload &trans)
{
    // adjust address offset:
    trans.set_address(trans.get_address() -
                      Configuration::getInstance().AddressOffset);

    DecodedAddress decodedAddress = AddressDecoder::getInstance().decodeAddress(
                                        trans.get_address());
    return iSocket[decodedAddress.channel]->transport_dbg(trans);
}

void Arbiter::peqCallback(tlm_generic_payload &payload, const tlm_phase &phase)
{
    unsigned int initiatorSocket = DramExtension::getExtension(
                                       payload).getThread().ID();
    unsigned int channelId = DramExtension::getExtension(payload).getChannel().ID();

    // Check the valid range of initiatorSocket ID and channel Id
    assert(channelId < Configuration::getInstance().NumberOfMemChannels);

    // Phases initiated by the intiator side from arbiter's point of view (devices performing memory requests to the arbiter)
    if (phase == BEGIN_REQ)
    {
        if (channelIsFree[channelId]) {
            // This channel was available. Forward the new transaction to the memory controller.
            channelIsFree[channelId] = false;
            sendToChannel(channelId, payload, phase, SC_ZERO_TIME);
        } else {
            // This channel is busy. Enqueue the new transaction which phase is BEGIN_REQ.
            pendingRequests[channelId].push(&payload);
        }
    }
    // Phases initiated by the target side from arbiter's point of view (memory side)
    else if (phase == END_REQ) {
        channelIsFree[channelId] = true;

        // The arbiter receives a transaction which phase is END_REQ from memory controller and forwards it to the requester device.
        sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME);

        // This channel is now free! Dispatch a new transaction (phase is BEGIN_REQ) from the queue, if any. Send it to the memory controller.
        if (!pendingRequests[channelId].empty()) {
            tlm_generic_payload *payloadToSend = pendingRequests[channelId].front();
            pendingRequests[channelId].pop();
            // Send ONE of the enqueued new transactions (phase is BEGIN_REQ) through this channel.
            sendToChannel(channelId, *payloadToSend, BEGIN_REQ, SC_ZERO_TIME);
            // Mark the channel as busy again.
            channelIsFree[channelId] = false;
        }
    }
    else if (phase == BEGIN_RESP)
    {
        // The arbiter receives a transaction in BEGIN_RESP phase
        // (that came from the memory side) and forwards it to the requester
        // device
        if (receivedResponses[initiatorSocket].empty()) {
            sendToInitiator(initiatorSocket, payload, phase, SC_ZERO_TIME);
        }
        // Enqueue the transaction in BEGIN_RESP phase until the initiator
        // device acknowledge it (phase changes to END_RESP).
        receivedResponses[initiatorSocket].push(&payload);
    }
    else if (phase == END_RESP)
    {
        // Send the END_RESP message to the memory
        sendToChannel(channelId, payload, phase, SC_ZERO_TIME);
        // Drop one element of the queue of BEGIN_RESP from memory to this device
        receivedResponses[initiatorSocket].pop();

        // Check if there are queued transactoins with phase BEGIN_RESP from memory to this device
        if (!receivedResponses[initiatorSocket].empty()) {
            // The queue is not empty.
            tlm_generic_payload *payloadToSend = receivedResponses[initiatorSocket].front();
            // Send ONE extra BEGIN_RESP to the device
            sendToInitiator(initiatorSocket, *payloadToSend, BEGIN_RESP, SC_ZERO_TIME);
        }
    }
    else
        SC_REPORT_FATAL(0,
            "Payload event queue in arbiter was triggered with unknown phase");
}

void Arbiter::sendToChannel(unsigned int channelId, tlm_generic_payload &payload,
                   const tlm_phase &phase, const sc_time &delay)
{
    tlm_phase TPhase = phase;
    sc_time TDelay = delay;
    iSocket[channelId]->nb_transport_fw(payload, TPhase, TDelay);
}

void Arbiter::sendToInitiator(unsigned int id, tlm_generic_payload &payload,
                     const tlm_phase &phase, const sc_time &delay)
{
    tlm_phase TPhase = phase;
    sc_time TDelay = delay;
    tSocket[id]->nb_transport_bw(payload, TPhase, TDelay);
}

void Arbiter::appendDramExtension(int socketId, tlm_generic_payload &payload)
{
    // Append Generation Extension
    GenerationExtension *genExtension = new GenerationExtension(sc_time_stamp());
    payload.set_auto_extension(genExtension);

    unsigned int burstlength = payload.get_streaming_width();
    DecodedAddress decodedAddress = AddressDecoder::getInstance().decodeAddress(payload.get_address());
    // Check the valid range of decodedAddress
    if (addressIsValid(decodedAddress)) {
        DramExtension *extension = new DramExtension(Thread(socketId),
                                                     Channel(decodedAddress.channel), Bank(decodedAddress.bank),
                                                     BankGroup(decodedAddress.bankgroup), Row(decodedAddress.row),
                                                     Column(decodedAddress.column), burstlength);
        payload.set_auto_extension(extension);
    } else {
        SC_REPORT_FATAL("Arbiter", "Decoded Address is not inside the valid range");
    }
}

bool Arbiter::addressIsValid(DecodedAddress  &decodedAddress)
{
    if (decodedAddress.channel >= AddressDecoder::getInstance().amount["channel"]) {
        return false;
    }
    if (decodedAddress.bank >= AddressDecoder::getInstance().amount["bank"]) {
        return false;
    }
    if (decodedAddress.bankgroup >
            AddressDecoder::getInstance().amount["bankgroup"]) {
        return false;
    }
    if (decodedAddress.column >= AddressDecoder::getInstance().amount["column"]) {
        return false;
    }
    if (decodedAddress.row >= AddressDecoder::getInstance().amount["row"]) {
        return false;
    }
    return true;
}

void Arbiter::printDebugMessage(std::string message)
{
    DebugManager::getInstance().printDebugMessage(this->name(), message);
}
