/* -*- c++ -*-
  * File : vci_vdspin_target_wrapper.cpp
  * Copyright (c) UPMC, Lip6
  * Author : Alain Greiner
  *
  * SOCLIB_LGPL_HEADER_BEGIN
  *
  * This file is part of SoCLib, GNU LGPLv2.1.
  *
  * SoCLib is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published
  * by the Free Software Foundation; version 2.1 of the License.
  *
  * SoCLib is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with SoCLib; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  * 02110-1301 USA
  *
  * SOCLIB_LGPL_HEADER_END
  */

#include "../include/vci_vdspin_target_wrapper.h"

using namespace soclib::common;

namespace soclib { namespace caba {

#define tmpl(x) template<typename vci_param, int dspin_cmd_width, int dspin_rsp_width> x VciVdspinTargetWrapper<vci_param, dspin_cmd_width, dspin_rsp_width>

//////////////////////////////////////////////////////////:////////////////////////////////
tmpl(/**/)::VciVdspinTargetWrapper(sc_module_name             name,
                        size_t                cmd_fifo_depth,
                   size_t                rsp_fifo_depth)
           : soclib::caba::BaseModule(name),
                 p_clk("p_clk"),
                 p_resetn("p_resetn"),
                 p_dspin_out("p_dspin_out"),
                 p_dspin_in("p_dspin_in"),
                 p_vci("p_vci"),
                 r_cmd_fsm("r_cmd_fsm"),
                 r_rsp_fsm("r_rsp_fsm"),
             r_fifo_cmd("r_fifo_cmd", cmd_fifo_depth),
             r_fifo_rsp("r_fifo_rsp", rsp_fifo_depth)
    {
    SC_METHOD (transition);
    dont_initialize();
    sensitive << p_clk.pos();
    SC_METHOD (genMoore);
    dont_initialize();
    sensitive  << p_clk.neg();

        assert( (dspin_cmd_width == 40) && "The DSPIN CMD flit width must have 40 bits");
        assert( (dspin_rsp_width == 33) && "The DSPIN RSP flit width must have 33 bits");
        assert( (vci_param::N    <= 40) && "The VCI ADDRESS field cannot have more than 40 bits");
        assert( (vci_param::B    == 4) && "The VCI DATA filds must have 32 bits");
        assert( (vci_param::K    == 8) && "The VCI PLEN field cannot have more than 8 bits");
        assert( (vci_param::S    <= 14) && "The VCI SRCID field cannot have more than 8 bits");
        assert( (vci_param::T    <= 8) && "The VCI TRDID field cannot have more than 8 bits");
        assert( (vci_param::E    == 2) && "The VCI RERROR field cannot have more than 2 bits");

    } //  end constructor

/////////////////////////
tmpl(void)::transition()
{
    sc_uint<dspin_cmd_width>        cmd_fifo_data;
    bool                    cmd_fifo_write;
    bool                    cmd_fifo_read;

    sc_uint<dspin_rsp_width>        rsp_fifo_data;
    bool                    rsp_fifo_write;
    bool                    rsp_fifo_read;

    if (p_resetn == false)
        {
        r_fifo_cmd.init();
        r_fifo_rsp.init();
        r_cmd_fsm = CMD_IDLE;
        r_rsp_fsm = RSP_IDLE;
        return;
    } // end reset

    /////////////////////////////////////////////////////////////
    // VCI response packet to DSPIN response packet.
    // The VCI packet is analysed, translated,
    // and the DSPIN packet is stored in the fifo_rsp
    /////////////////////////////////////////////////////////////
    // - A single flit VCI write response packet is translated
    //   to a single flit DSPIN response.
    // - A N flits VCI read response packet is translated
    //   to a N+1 flits DSPIN response
    // In the RSP_IDLE state, the first DSPIN flit is written
    // in fifo_rsp , but no VCI flit is consumed. The VCI flits
    // are consumed in the RSP_READ or RSP_WRITE states.
    //////////////////////////////////////////////////////////////

    // rsp_fifo_read
    rsp_fifo_read = p_dspin_out.read.read();

    // r_rsp_fsm, rsp_fifo_write and rsp_fifo_data
        rsp_fifo_write = false;        // default value

    switch(r_rsp_fsm) {
        case RSP_IDLE:        // write first DSPIN flit into rsp_fifo
            {
                if( p_vci.rspval && r_fifo_rsp.wok() )
                {
                    bool is_read = ( (p_vci.rerror.read() & 0x2) == 0);

                    rsp_fifo_write = true;
                    rsp_fifo_data = (((sc_uint<dspin_rsp_width>)p_vci.rsrcid.read()) << 18) |
                                    (((sc_uint<dspin_rsp_width>)p_vci.rerror.read()) << 16) |
                                    (((sc_uint<dspin_rsp_width>)p_vci.rtrdid.read()) << 12) |
                                    (((sc_uint<dspin_rsp_width>)p_vci.rpktid.read()) << 8);
                    if ( is_read )
                    {
                        r_rsp_fsm = RSP_READ;
                    }
                    else
                    {
                        rsp_fifo_data = rsp_fifo_data | 0x100000000LL;
                        r_rsp_fsm = RSP_WRITE;
                    }
                }
                break;
            }
        case RSP_READ:        // write DSPIN data flit in case of read
            {
                if( p_vci.rspval && r_fifo_rsp.wok() )
                {
                    rsp_fifo_write   = true;
                    rsp_fifo_data = ((sc_uint<dspin_rsp_width>)p_vci.rdata.read());
                    if ( p_vci.reop )
                    {
                        rsp_fifo_data = rsp_fifo_data | 0x100000000LL;
                        r_rsp_fsm = RSP_IDLE;
                    }
                }
                break;
            }
            case RSP_WRITE:
            {
                rsp_fifo_write = false;
                if ( r_fifo_rsp.wok() ) r_rsp_fsm = RSP_IDLE;
                break;
            }
    } // end switch r_cmd_fsm

    // fifo_rsp
    if((rsp_fifo_write == true)  && (rsp_fifo_read == false)) { r_fifo_rsp.simple_put(rsp_fifo_data); }
    if((rsp_fifo_write == true)  && (rsp_fifo_read == true))  { r_fifo_rsp.put_and_get(rsp_fifo_data); }
    if((rsp_fifo_write == false) && (rsp_fifo_read == true))  { r_fifo_rsp.simple_get(); }

        //////////////////////////////////////////////////////////////
    // DSPIN command packet to VCI command packet
    // The DSPIN packet is stored in the fifo_rsp
    // The FIFO output is analysed and translated to a VCI packet
        //////////////////////////////////////////////////////////////
        // - A 2 flits DSPIN broadcast command is translated
        //   to a 1 flit VCI broadcast command.
        // - A 2 flits DSPIN read command is translated
        //   to a 1 flit VCI read command.
        // - A N+2 flits DSPIN write command is translated
        //   to a N flits VCI write command.
        // The VCI flits are sent in the CMD_READ, CMD_WDATA
        // & CMD_BROADCAST states.
        // The r_cmd_buf0 et r_cmd_buf1 buffers are used to store
        // the two first DSPIN flits (in case of write).
        //////////////////////////////////////////////////////////////

    // cmd_fifo_write, cmd_fifo_data
    cmd_fifo_write = p_dspin_in.write.read();
    cmd_fifo_data  = p_dspin_in.data.read();

    // r_cmd_fsm, cmd_fifo_read
        cmd_fifo_read = false;         // default value

    switch(r_cmd_fsm) {
        case CMD_IDLE:
            {
        if( r_fifo_cmd.rok() )
                {
                    bool is_broadcast = ( (r_fifo_cmd.read() & 0x1) == 0x1);

            cmd_fifo_read = true;
                    r_cmd_buf0    = r_fifo_cmd.read();         // save address
                    if ( is_broadcast ) r_cmd_fsm = CMD_BROADCAST;
                    else        r_cmd_fsm = CMD_RW;
        }
        break;
            }
        case CMD_BROADCAST:
        {
        if( r_fifo_cmd.rok() && p_vci.cmdack )
                {
                    cmd_fifo_read = true;
                    r_cmd_fsm = CMD_IDLE;
                }
                break;
            }
            case CMD_RW:
            {
        if( r_fifo_cmd.rok() )
                {
                    cmd_fifo_read = true;
                    r_cmd_buf1 = r_fifo_cmd.read();        // save command parameters
                    // read command if EOP
                    if ( (r_fifo_cmd.read() & 0x8000000000LL) )   r_cmd_fsm = CMD_READ;
                    else                      r_cmd_fsm = CMD_WDATA;
                    r_flit_count = 0;
        }
        break;
            }
            case CMD_READ:
            {
                if ( p_vci.cmdack.read() ) r_cmd_fsm = CMD_IDLE;
                break;
            }
            case CMD_WDATA:
            {
        if( r_fifo_cmd.rok() && p_vci.cmdack.read() )
                {
                    if ( (r_cmd_buf1.read() & 0x0000200000LL) == 0 )     r_flit_count = r_flit_count + 1;
                    cmd_fifo_read = true;
                    if ( (r_fifo_cmd.read() & 0x8000000000LL) )     r_cmd_fsm = CMD_IDLE;
        }
                break;
            }
    } // end switch r_cmd_fsm

    // fifo_cmd
    if((cmd_fifo_write == true)  && (cmd_fifo_read == false)) { r_fifo_cmd.simple_put(cmd_fifo_data); }
    if((cmd_fifo_write == true)  && (cmd_fifo_read == true))  { r_fifo_cmd.put_and_get(cmd_fifo_data); }
    if((cmd_fifo_write == false) && (cmd_fifo_read == true))  { r_fifo_cmd.simple_get(); }

}; // end transition

//////////////////////
tmpl(void)::genMoore()
{
    // VCI RSP interface
        if ( r_rsp_fsm.read() == RSP_IDLE )    p_vci.rspack = false;
    else                                       p_vci.rspack = r_fifo_rsp.wok();

    // VCI CMD interface
    if ( (r_cmd_fsm.read() == CMD_IDLE) || (r_cmd_fsm.read() == CMD_RW) )
        {
            p_vci.cmdval = false;
        }
        else if ( r_cmd_fsm.read() == CMD_BROADCAST )    // VCI CMD broadcast
        {
            if ( r_fifo_cmd.rok() )
            {
                sc_uint<dspin_cmd_width>  minmax = r_cmd_buf0.read() & 0x7FFFF80000LL;
                if ( vci_param::N == 40 ) minmax = (minmax << 1);
                else              minmax = (minmax >> (39 - vci_param::N) );
                p_vci.cmdval  = true;
                p_vci.address = (sc_uint<vci_param::N>)minmax | 0x3;
                p_vci.cmd     = vci_param::CMD_WRITE;
                p_vci.wdata   = (sc_uint<8*vci_param::B>)(r_fifo_cmd.read() & 0x00FFFFFFFFLL);
                p_vci.be      = (sc_uint<vci_param::B>)((r_fifo_cmd.read()  & 0x0F00000000LL) >> 32);
                p_vci.srcid   = (sc_uint<vci_param::S>)((r_cmd_buf0.read()  & 0x000007FFE0LL) >> 5);
                p_vci.trdid   = (sc_uint<vci_param::T>)((r_cmd_buf0.read()  & 0x000000001ELL) >> 1);
                p_vci.pktid   = 0;
                p_vci.plen    = vci_param::B;
                p_vci.contig  = true;
                p_vci.cons    = false;
                p_vci.eop     = true;
            }
            else
            {
                p_vci.cmdval = false;
            }
        }
        else if ( r_cmd_fsm.read() == CMD_READ )    // VCI CMD read
        {
            sc_uint<vci_param::N> address;
            if ( vci_param::N == 40 ) address = (r_cmd_buf0.read() << 1);
            else                      address = (r_cmd_buf0.read() >> (39 - vci_param::N) );
            p_vci.cmdval  = true;
            p_vci.address = address;
            p_vci.cmd     = (sc_uint<2>)((r_cmd_buf1.read()            & 0x0001800000LL) >> 23);
            p_vci.wdata   = 0;
            p_vci.be      = (sc_uint<vci_param::B>)((r_cmd_buf1.read() & 0x000000001ELL) >> 1);
            p_vci.srcid   = (sc_uint<vci_param::S>)((r_cmd_buf1.read() & 0x7FFE000000LL) >> 25);
            p_vci.pktid   = (sc_uint<vci_param::P>)((r_cmd_buf1.read() & 0x00000001E0LL) >> 5);
            p_vci.trdid   = (sc_uint<vci_param::T>)((r_cmd_buf1.read() & 0x0000001E00LL) >> 9);
            p_vci.plen    = (sc_uint<vci_param::K>)((r_cmd_buf1.read() & 0x00001FE000LL) >> 13);
            p_vci.contig  = ((r_cmd_buf1.read() & 0x0000400000LL) != 0);
            p_vci.cons    = ((r_cmd_buf1.read() & 0x0000200000LL) != 0);
            p_vci.eop     = true;
    }
        else if ( r_cmd_fsm.read() == CMD_WDATA )    // VCI write command
        {
            if ( r_fifo_cmd.rok() )
            {
                sc_uint<vci_param::N> address;
                if ( vci_param::N == 40 ) address = (r_cmd_buf0.read() << 1);
                else                      address = (r_cmd_buf0.read() >> (39 - vci_param::N) );
                p_vci.cmdval  = true;
                p_vci.address = address + (r_flit_count.read()*vci_param::B);
                p_vci.cmd     = (sc_uint<2>)((r_cmd_buf1.read()             & 0x0001800000LL) >> 23);
                p_vci.wdata   = (sc_uint<8*vci_param::B>)(r_fifo_cmd.read() & 0x00FFFFFFFFLL);
                p_vci.be      = (sc_uint<vci_param::B>)((r_fifo_cmd.read()  & 0x0F00000000LL) >> 32);
                p_vci.srcid   = (sc_uint<vci_param::S>)((r_cmd_buf1.read()  & 0x7FFE000000LL) >> 25);
                p_vci.pktid   = (sc_uint<vci_param::P>)((r_cmd_buf1.read()  & 0x00000001E0LL) >> 5);
                p_vci.trdid   = (sc_uint<vci_param::T>)((r_cmd_buf1.read()  & 0x0000001E00LL) >> 9);
                p_vci.plen    = (sc_uint<vci_param::K>)((r_cmd_buf1.read()  & 0x00001FE000LL) >> 13);
                p_vci.contig  = ((r_cmd_buf1.read() & 0x0000400000LL) != 0);
                p_vci.cons    = ((r_cmd_buf1.read() & 0x0000200000LL) != 0);
                p_vci.eop     = ((r_fifo_cmd.read() & 0x8000000000LL) == 0x8000000000LL);
            }
            else
            {
                p_vci.cmdval = false;
            }
        }

    // DSPIN_OUT interface
    p_dspin_out.write = r_fifo_rsp.rok();
    p_dspin_out.data  = r_fifo_rsp.read();

    // DSPIN_IN interface
    p_dspin_in.read = r_fifo_cmd.wok();

}; // end genMoore

/////////////////////////
tmpl(void)::print_trace()
{
    const char* cmd_str[] = {
    "CMD_IDLE     ",
    "CMD_BROADCAST",
    "CMD_RW       ",
    "CMD_READ     ",
    "CMD_WDATA    ",
    };
    const char* rsp_str[] = {
    "RSP_IDLE     ",
    "RSP_READ     ",
    "RSP_WRITE    ",
    };
    std::cout << name() << " : " << cmd_str[r_cmd_fsm.read()]
                        << " | " << rsp_str[r_rsp_fsm.read()]
                        << " | fifo_cmd = " << r_fifo_cmd.filled_status()
                        << " | fifo_rsp = " << r_fifo_rsp.filled_status()
                        << std::endl;
}

}} // end namespace

// Local Variables:
// tab-width: 4
// c-basic-offset: 4
// c-file-offsets:((innamespace . 0)(inline-open . 0))
// indent-tabs-mode: nil
// End:

// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4
