/*
 *
 * 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
 *
 * Copyright (c) UPMC, Lip6, Asim
 *         Alain Greiner <alain.greiner@lip6.fr>, 2008
 *         Christophe Choichillon <choichillon.christophe@gmail.com>, 2011
 *
 * Maintainers: alain, Christophe Choichillon
 */

/////////////////////////////////////////////////////////////////////////
//  This component is a multi-segments Ram controller.
//
//  It supports only the compact VCI packets defined 
//  in the VCI advanced specification:
//  - A READ burst command packet (such a cache line request) 
//    contains one single flit. 
//    The response packet length is defined by the PLEN field.
//    The zero value for the PLEN field is not supported.
//    An ERROR response packets contain one single flit,
//  - WRITE burst command packets at consecutive addresses are supported.
//    The zero value for the PLEN field is not supported.
//    Write response packets contain always one single flit.
//  - The LL & SC command packets are supported, but the packet
//    must contain one single flit.
//  The RAM latency is a parameter, that can have a zero value.
////////////////////////////////////////////////////////////////////////
//  Implementation note: This component does not contain any FIFO,
//  and is controlled by a single FSM.
//  The latency counter is decremented in the IDLE state.
//  The VCI command is analysed and checked in the CMD_GET state.
//  - For read, ll or sc commands, the command is acknowledged in
//  the CMD_STATE. It is executed and the response is sent in the
//  RSP_READ, RSP_LL or RSP_SC states. 
//  - For write commands, the command is acknowledged in the CMD_STATE,
//  or in the CMD_WRITE & CMD_ERROR states in case of bursts.
//  The command is executed in the CMD_WRITE state, or in the RSP_WRITE
//  state for the last flit of a burst. The response packet is sent
//  in the RSP_WRITE state.
/////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <cstring>
#include "vci_synthetic_target.h"

namespace soclib {
namespace caba {

using namespace soclib;

#define tmpl(x) template<typename vci_param> x VciSyntheticTarget<vci_param>

//////////////////////////
tmpl(/**/)::VciSyntheticTarget(
    sc_module_name insname,
    const soclib::common::IntTab index,
    const soclib::common::MappingTable &mt,
    const soclib::common::Loader &loader,
    const uint32_t latency)
	: caba::BaseModule(insname),
      m_loader(loader),
      m_seglist(mt.getSegmentList(index)),
      m_latency(latency),

      r_llsc_buf((size_t)(1<<vci_param::S)),

      r_fsm_state("r_fsm_state"),
      r_flit_count("r_flit_count"),
      r_seg_index("r_seg_index"),
      r_address("r_address"),
      r_wdata("r_wdata"),
      r_be("r_be"),
      r_srcid("r_srcid"),
      r_trdid("r_trdid"),
      r_pktid("r_pktid"),
      r_contig("r_contig"),
      r_latency_count("r_latency_count"),

      m_nbseg(0),

      p_resetn("p_resetn"),
      p_clk("p_clk"),
      p_vci("p_vci")
{
    SC_METHOD(transition);
    dont_initialize();
    sensitive << p_clk.pos();

    SC_METHOD(genMoore);
    dont_initialize();
    sensitive << p_clk.neg();

    std::list<soclib::common::Segment>::iterator seg;
    for ( seg = m_seglist.begin() ; seg != m_seglist.end() ; seg++ )  m_nbseg++;
 
    m_ram = new ram_t*[m_nbseg];
    m_seg = new soclib::common::Segment*[m_nbseg];

    size_t i = 0;
    size_t word_size = vci_param::B; // B is VCI's cell size
    for ( seg = m_seglist.begin() ; seg != m_seglist.end() ; seg++ ) 
    { 
        m_ram[i] = new ram_t[(seg->size()+word_size-1)/word_size];
        m_seg[i] = &(*seg);
        i++;
    }
}

///////////////////////////
tmpl(/**/)::~VciSyntheticTarget()
{
    for (size_t i=0 ; i<m_nbseg ; ++i) delete [] m_ram[i];
    delete [] m_ram;
    delete [] m_seg;
}

/////////////////////
tmpl(void)::reload()
{
    for ( size_t i=0 ; i<m_nbseg ; ++i ) 
    {
        m_loader.load(&m_ram[i][0], m_seg[i]->baseAddress(), m_seg[i]->size());
        for ( size_t addr = 0 ; addr < m_seg[i]->size()/vci_param::B ; ++addr )
            m_ram[i][addr] = le_to_machine(m_ram[i][addr]);
    }
}

////////////////////
tmpl(void)::reset()
{
    for ( size_t i=0 ; i<m_nbseg ; ++i ) std::memset(&m_ram[i][0], 0, m_seg[i]->size()); 
    m_cpt_read = 0;
    m_cpt_write = 0;
    if (m_latency) {
 	r_fsm_state = FSM_IDLE;
        r_latency_count = m_latency - 1;
    } else {
	r_fsm_state = FSM_CMD_GET;
        r_latency_count = 0;
    }
    r_llsc_buf.clearAll();
}

/////////////////////////////////////////////////////////////////////////////
tmpl(bool)::write(size_t seg, vci_addr_t addr, vci_data_t wdata, vci_be_t be)
{
    if ( m_seg[seg]->contains(addr) ) 
    {
        size_t index = (size_t)((addr - m_seg[seg]->baseAddress()) / vci_param::B);
        vci_data_t cur = m_ram[seg][index];
        vci_data_t mask = vci_param::be2mask(be);
        m_ram[seg][index] = (cur & ~mask) | (wdata & mask);
        m_cpt_write++;
        return true;
    } 
    return false;
}

/////////////////////////////////////////////////////////////////
tmpl(bool)::read(size_t seg, vci_addr_t addr, vci_data_t &rdata )
{
    if ( m_seg[seg]->contains(addr) ) 
    {
        size_t index = (size_t)((addr - m_seg[seg]->baseAddress()) / vci_param::B);
        rdata = m_ram[seg][index];
        m_cpt_read++;
        return true;
    }
    return false;
}

//////////////////////////
tmpl(void)::print_trace()
{
    const char* state_str[] = { "IDLE", 
                                "CMD_GET",
                                "CMD_WRITE",
                                "CMD_ERROR",
                                "RSP_READ",
                                "RSP_WRITE",
                                "RSP_LL",
                                "RSP_SC",
                                "RSP_ERROR" };
    std::cout << "Simple_ram " << name() 
              << " : state = " << state_str[r_fsm_state] 
              << " / latency_count = " << r_latency_count 
              << " / flit_count = " << r_flit_count << std::endl;
}

/////////////////////////
tmpl(void)::transition()
{
    if (!p_resetn) 
    {
        reset();
        reload();
        return;
    }

#ifdef SOCLIB_MODULE_DEBUG
std::cout << "Synthetic_target : " << name() << std::endl;
std::cout << " fsm_state = " << r_fsm_state 
          << " latency_count = " << r_latency_count << std::endl;
#endif

    switch ( r_fsm_state ) {
    case FSM_IDLE: 	// unreachable state if m_latency == 0 
    {
        if ( p_vci.cmdval.read() ) 
        {
            if (r_latency_count.read() == 0) 
            {
                r_fsm_state = FSM_CMD_GET;
                r_latency_count = m_latency - 1;
            } 
            else 
            {
                r_latency_count = r_latency_count.read() - 1;
            }
        }				   
        break;
    }
    case FSM_CMD_GET:
    {
        if ( !p_vci.cmdval.read() ) break;

        vci_addr_t   address = p_vci.address.read();
        bool         error = true;
        for ( size_t index = 0 ; index<m_nbseg  && error ; ++index) 
        {
            if(address & 0x3){
                    error = false;
            } else {
                if ( (m_seg[index]->contains(address)) &&
                     (m_seg[index]->contains(address + p_vci.plen.read() - vci_param::B)) ) 
                {
                    error = false;
                    r_seg_index = index;
                }
            }
        } 

        r_address    = address;
        r_be         = p_vci.be.read();
        r_wdata      = p_vci.wdata.read();
        r_srcid      = p_vci.srcid.read();
        r_trdid      = p_vci.trdid.read();
        r_pktid      = p_vci.pktid.read();

        if ( error ) 
        {
            if( p_vci.eop.read() )	r_fsm_state = FSM_RSP_ERROR;
            else			r_fsm_state = FSM_CMD_ERROR;
        }
        else
        {
            assert( (p_vci.plen.read() != 0) && "VCI command packets should have plen != 0");
            if ( p_vci.cmd.read() == vci_param::CMD_WRITE ) 
            {
                r_contig     = p_vci.contig.read();
                if( p_vci.eop.read() )  r_fsm_state = FSM_RSP_WRITE;
                else 			r_fsm_state = FSM_CMD_WRITE;
            }
            else if ( p_vci.cmd.read() == vci_param::CMD_READ )
            {
                r_flit_count = p_vci.plen.read()/vci_param::B;
                r_contig     = p_vci.contig.read();
                r_fsm_state = FSM_RSP_READ;
                assert( p_vci.eop.read() && "VCI read command packets should be one flit");
            }
            else if ( p_vci.cmd.read() == vci_param::CMD_STORE_COND )
            {
                r_fsm_state = FSM_RSP_SC;
                assert( p_vci.eop.read() && "VCI sc command packets should be one flit");
            }
            else if ( p_vci.cmd.read() == vci_param::CMD_LOCKED_READ )
            {
                r_fsm_state = FSM_RSP_LL;
                assert( p_vci.eop.read() && "VCI ll command packets should be one flit");
            }
        }
        break;
    }
    case FSM_CMD_WRITE:
    {
        assert( write (r_seg_index, r_address , r_wdata, r_be ) && "out of bounds access in a write burst" );
        if ( p_vci.cmdval.read() ) 
        {
            vci_addr_t next_address = r_address.read() + (vci_addr_t)vci_param::B;
            assert( ((r_contig && (next_address == p_vci.address.read())) ||
                     (!r_contig && (r_address.read() == p_vci.address.read()))) &&
                        "addresses must be contiguous or constant in a VCI write burst" );
            r_address   = p_vci.address.read();
            r_be        = p_vci.be.read();
            r_wdata     = p_vci.wdata.read();
            if ( p_vci.eop.read() ) 	 r_fsm_state = FSM_RSP_WRITE;
        }
        break;
    }
    case FSM_RSP_WRITE:
    {
        if (r_address.read() != 0x3)
            assert( write (r_seg_index, r_address , r_wdata, r_be ) && "out of bounds access in a write burst" );
        if( p_vci.rspack.read() )
        { 
            if( m_latency )	r_fsm_state = FSM_IDLE;
            else           	r_fsm_state = FSM_CMD_GET;
        }
        break;
    }
    case FSM_RSP_READ:
    {
        if ( p_vci.rspack.read() )
        {
            r_flit_count = r_flit_count - 1;
            if ( r_contig ) 	r_address  = r_address.read() + vci_param::B;
            if ( r_flit_count == 1) 	// last flit 
            {
                if( m_latency )	r_fsm_state = FSM_IDLE;
                else           	r_fsm_state = FSM_CMD_GET;
            }
        }
        break;
    }
    case FSM_CMD_ERROR:
    {
        if ( p_vci.cmdval.read() && p_vci.eop.read() )
        {
            r_fsm_state = FSM_RSP_ERROR;
        }
        break;
    }
    case FSM_RSP_ERROR:
    {
        if ( p_vci.rspack.read() ) 
        {
            if( m_latency )	r_fsm_state = FSM_IDLE;
            else           	r_fsm_state = FSM_CMD_GET;
        }
        break;
    }
    case FSM_RSP_LL:
    {
        if ( p_vci.rspack.read() ) 
        {   
            r_llsc_buf.doLoadLinked(r_address.read(), r_srcid.read());
            if( m_latency )	r_fsm_state = FSM_IDLE;
            else           	r_fsm_state = FSM_CMD_GET;
        }
        break;
    }
    case FSM_RSP_SC:
    {
        if ( p_vci.rspack.read() ) 
        {    
            if ( r_llsc_buf.isAtomic(r_address.read(), r_srcid.read()) ) 
            {
                r_llsc_buf.accessDone(r_address.read());
                write (r_seg_index, r_address , r_wdata, r_be);
            }
            if( m_latency )	r_fsm_state = FSM_IDLE;
            else           	r_fsm_state = FSM_CMD_GET;
        }
        break;
    }
    } // end switch fsm_state

} // end transition()

///////////////////////
tmpl(void)::genMoore()
{
    switch ( r_fsm_state ) {
    case FSM_IDLE:
    {
        p_vci.cmdack  = false;
        p_vci.rspval  = false;
        p_vci.rdata   = 0;
        p_vci.rsrcid  = 0;
        p_vci.rtrdid  = 0;
        p_vci.rpktid  = 0;
        p_vci.rerror  = 0;
        p_vci.reop    = false;
        break;
    }
    case FSM_CMD_GET:
    case FSM_CMD_WRITE:
    case FSM_CMD_ERROR:
    {
        p_vci.cmdack  = true;
        p_vci.rspval  = false;
        p_vci.rdata   = 0;
        p_vci.rsrcid  = 0;
        p_vci.rtrdid  = 0;
        p_vci.rpktid  = 0;
        p_vci.rerror  = 0;
        p_vci.reop    = false;
        break;
    }
    case FSM_RSP_WRITE:
    {
        p_vci.cmdack  = false;
        p_vci.rspval  = true;
        p_vci.rdata   = 0;
        p_vci.rsrcid  = r_srcid.read();
        p_vci.rtrdid  = r_trdid.read();
        p_vci.rpktid  = r_pktid.read();
        p_vci.rerror  = vci_param::ERR_NORMAL;
        p_vci.reop    = true;
        break;
    }
    case FSM_RSP_READ:
    {
        vci_data_t   rdata;
        assert( read(r_seg_index, r_address, rdata) && "out of bounds access in a read burst" );
        p_vci.cmdack = false;
        p_vci.rspval = true;
        p_vci.rdata  = rdata;
        p_vci.rsrcid = r_srcid.read();
        p_vci.rtrdid = r_trdid.read();
        p_vci.rpktid = r_pktid.read();
        p_vci.rerror = vci_param::ERR_NORMAL;
        p_vci.reop   = (r_flit_count.read() == 1);
        break;
    }
    case FSM_RSP_LL:
    {
        vci_data_t   rdata;
        assert( read(r_seg_index, r_address, rdata) && "out of bounds access in a ll access" );
        p_vci.cmdack = false;
        p_vci.rspval = true;
        p_vci.rdata  = rdata;
        p_vci.rsrcid = r_srcid.read();
        p_vci.rtrdid = r_trdid.read();
        p_vci.rpktid = r_pktid.read();
        p_vci.rerror = vci_param::ERR_NORMAL;
        p_vci.reop   = true;
        break;
    }
    case FSM_RSP_SC:
    {
        p_vci.cmdack = false;
        p_vci.rspval = true;
        if ( r_llsc_buf.isAtomic(r_address.read(), r_srcid.read()) ) p_vci.rdata = 0;
        else                                                         p_vci.rdata = 1;
        p_vci.rsrcid = r_srcid.read();
        p_vci.rtrdid = r_trdid.read();
        p_vci.rpktid = r_pktid.read();
        p_vci.rerror = vci_param::ERR_NORMAL;
        p_vci.reop   = true;
        break;
    }
    case FSM_RSP_ERROR:
    {
        p_vci.cmdack  = false;
        p_vci.rspval  = true;
        p_vci.rdata   = 0;
        p_vci.rsrcid  = r_srcid.read();
        p_vci.rtrdid  = r_trdid.read();
        p_vci.rpktid  = r_pktid.read();
        p_vci.rerror  = vci_param::ERR_GENERAL_DATA_ERROR;
        p_vci.reop    = true;
        break;
    }
    } // end switch fsm_state
} // end genMoore()

}} 


