/* -*- c++ -*-
 *
 * 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@lip6.fr april 2011
 *
 * Maintainers: alain
 */

#include <stdint.h>
#include <iostream>
#include <fcntl.h>
#include "vci_block_device_tsar_v4.h"

namespace soclib { namespace caba {

#define tmpl(t) template<typename vci_param> t VciBlockDeviceTsarV4<vci_param>

using namespace soclib::caba;
using namespace soclib::common;

////////////////////////
tmpl(void)::transition()
{
    if(p_resetn.read() == false) 
    {
        r_initiator_fsm = M_IDLE;
        r_target_fsm = T_IDLE;
        r_irq_enable = true;
        r_go         = false;
	return;
    } 

    //////////////////////////////////////////////////////////////////////////////
    // The Target FSM controls the following registers:
    // r_target_fsm, r_irq_enable, r_nblocks, r_buf adress, r_lba, r_go, r_read
    //////////////////////////////////////////////////////////////////////////////

    switch(r_target_fsm) {
    case T_IDLE:
    {
        if ( p_vci_target.cmdval.read() ) 
        { 
            r_srcid = p_vci_target.srcid.read();
            r_trdid = p_vci_target.trdid.read();
            r_pktid = p_vci_target.pktid.read();
	    sc_dt::sc_uint<vci_param::N> address = p_vci_target.address.read();
            bool                  read    = (p_vci_target.cmd.read() == vci_param::CMD_READ);
            uint32_t              cell    = (uint32_t)((address & 0x1F)>>2);

	    if     ( !read && !m_segment.contains(address) )		r_target_fsm = T_WRITE_ERROR;
	    else if(  read && !m_segment.contains(address) )		r_target_fsm = T_READ_ERROR;
	    else if( !read && !p_vci_target.eop.read() ) 		r_target_fsm = T_WRITE_ERROR;
	    else if(  read && !p_vci_target.eop.read() ) 		r_target_fsm = T_READ_ERROR;
            else if( !read && (cell == BLOCK_DEVICE_BUFFER) ) 		r_target_fsm = T_WRITE_BUFFER;
            else if(  read && (cell == BLOCK_DEVICE_BUFFER) ) 		r_target_fsm = T_READ_BUFFER;
            else if( !read && (cell == BLOCK_DEVICE_COUNT) ) 		r_target_fsm = T_WRITE_COUNT;
            else if(  read && (cell == BLOCK_DEVICE_COUNT) ) 		r_target_fsm = T_READ_COUNT;
            else if( !read && (cell == BLOCK_DEVICE_LBA) ) 		r_target_fsm = T_WRITE_LBA;
            else if(  read && (cell == BLOCK_DEVICE_LBA) ) 		r_target_fsm = T_READ_LBA;
            else if( !read && (cell == BLOCK_DEVICE_OP) ) 		r_target_fsm = T_WRITE_OP;
            else if(  read && (cell == BLOCK_DEVICE_STATUS) ) 		r_target_fsm = T_READ_STATUS;
            else if( !read && (cell == BLOCK_DEVICE_IRQ_ENABLE) ) 	r_target_fsm = T_WRITE_IRQEN;
            else if(  read && (cell == BLOCK_DEVICE_IRQ_ENABLE) )	r_target_fsm = T_READ_IRQEN;
            else if(  read && (cell == BLOCK_DEVICE_SIZE) ) 		r_target_fsm = T_READ_SIZE;
            else if(  read && (cell == BLOCK_DEVICE_BLOCK_SIZE) ) 	r_target_fsm = T_READ_BLOCK;
        }
        break;
    }
    case T_WRITE_BUFFER:
    {
        if ( r_initiator_fsm == M_IDLE )  r_buf_address = (uint32_t)p_vci_target.wdata.read();
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_WRITE_COUNT:
    {
        if ( r_initiator_fsm == M_IDLE )  r_nblocks = (uint32_t)p_vci_target.wdata.read();
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_WRITE_LBA:
    {
        if ( r_initiator_fsm == M_IDLE )  r_lba = (uint32_t)p_vci_target.wdata.read();
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_WRITE_OP:
    {
        if ( r_initiator_fsm == M_IDLE ) 
        {
            if ( (uint32_t)p_vci_target.wdata.read() == BLOCK_DEVICE_READ )
            {
                r_read = true;
                r_go = true;
            }
            else if ( (uint32_t)p_vci_target.wdata.read() == BLOCK_DEVICE_WRITE)
            {
                r_read = false;
                r_go = true;
            }
        }
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_WRITE_IRQEN:
    {
        r_irq_enable = (p_vci_target.wdata.read() != 0);
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_READ_BUFFER:
    case T_READ_COUNT:
    case T_READ_LBA:
    case T_READ_IRQEN:
    case T_READ_SIZE:
    case T_READ_BLOCK:
    case T_READ_ERROR:
    case T_WRITE_ERROR:
    {
        if ( p_vci_target.rspack.read() ) r_target_fsm = T_IDLE;
        break;
    }
    case T_READ_STATUS:
    {
        if ( p_vci_target.rspack.read() ) 
        {
            r_target_fsm = T_IDLE;
            if( (r_initiator_fsm == M_READ_SUCCESS ) ||
                (r_initiator_fsm == M_READ_ERROR   ) ||
                (r_initiator_fsm == M_WRITE_SUCCESS) ||
                (r_initiator_fsm == M_WRITE_ERROR  ) ) r_go = false;
        }
        break;
    }
    } // end switch target fsm
	
    /////////////////////////////////////////////////////////////////////////
    // The initiator FSM controls the following registers :
    // r_initiator_fsm, r_flit_count, r_block_count, m_local_buffer 
    /////////////////////////////////////////////////////////////////////////
    switch(r_initiator_fsm) {
    case M_IDLE : 	// waiting for activation
    {
        if ( r_go ) 
        {
            r_block_count   = 0;
            r_burst_count   = 0;
            r_flit_count    = 0;
            r_latency_count = m_latency;

            if ( r_read ) 	r_initiator_fsm = M_READ_BLOCK;
            else            r_initiator_fsm = M_WRITE_CMD;
        }
        break;
    }
    case M_READ_BLOCK:  // read one block from disk after waiting m_latency cycles
    {
        if ( r_latency_count == 0 )
        {
            r_latency_count = m_latency;
            ::lseek(m_fd, (r_lba + r_block_count)*m_flits_per_block*vci_param::B, SEEK_SET);
            if( ::read(m_fd, m_local_buffer, m_flits_per_block*vci_param::B) < 0 )  
            {
                 r_initiator_fsm = M_READ_ERROR;
            }
            else   
            {
                 r_initiator_fsm = M_READ_CMD;
            }
        }
        else
        {
            r_latency_count = r_latency_count - 1;
        }
        break;
    }
    case M_READ_CMD:	// This is actually a multi-flits VCI WRITE command
    {
        if ( p_vci_initiator.cmdack.read() )
        {
            if ( r_flit_count == (m_flits_per_burst - 1) )
            {
                r_initiator_fsm = M_READ_RSP;
                r_flit_count = 0;
            }
            else
            {
                r_flit_count = r_flit_count + 1;
            }
        }
        break;
    }
    case M_READ_RSP: 	// This is actually a single flit VCI WRITE response
    {
        if ( p_vci_initiator.rspval.read() )
        {
            if ( (p_vci_initiator.rerror.read()&0x1) == 0 ) r_initiator_fsm = M_READ_TEST;
            else                                            r_initiator_fsm = M_READ_ERROR;
        }
        break;
    }
    case M_READ_TEST:
    {
        if ( r_burst_count.read() == (m_bursts_per_block - 1) )
        {
            if ( r_block_count.read() == (r_nblocks.read() - 1) ) // last burst of the last block 
            {
                r_burst_count = 0;
                r_block_count = 0;
                r_initiator_fsm = M_READ_SUCCESS;
            }
            else	// last burst but not last block
            {
                r_burst_count = 0;
                r_block_count = r_block_count.read() + 1;
                r_initiator_fsm  = M_READ_BLOCK;
            }
        }
        else  // not the last burst of the block
        {
            r_burst_count = r_burst_count.read() + 1;
            r_initiator_fsm = M_READ_CMD;
        }
        break;
    }
    case M_READ_SUCCESS:
    {
        if( !r_go ) r_initiator_fsm = M_IDLE;
        break;
    }
    case M_READ_ERROR:
    {
        if( !r_go ) r_initiator_fsm = M_IDLE;
        break;
    }
    case M_WRITE_CMD:	// This is actually a single flit VCI READ command
    {
	    if ( p_vci_initiator.cmdack.read() ) r_initiator_fsm = M_WRITE_RSP;
        break;
    }
    case M_WRITE_RSP:	// This is actually a multi-flits VCI READ response
    {
        if ( p_vci_initiator.rspval.read() )
        {
            uint32_t index = (r_burst_count.read()*m_flits_per_burst) + r_flit_count.read();
            m_local_buffer[index] = (uint32_t)p_vci_initiator.rdata.read();
            if ( p_vci_initiator.reop.read() )
            {
	            r_flit_count = 0;
		        if( (p_vci_initiator.rerror.read()&0x1) == 0 ) r_initiator_fsm = M_WRITE_TEST;
                else                                           r_initiator_fsm = M_WRITE_ERROR;
            }
            else
            {
	            r_flit_count = r_flit_count.read() + 1;
            }
        }
        break;
    }
    case M_WRITE_TEST:
    {
        if ( r_burst_count.read() == (m_bursts_per_block - 1) ) // last burst of the block
        {
            r_burst_count = 0;
            r_block_count = r_block_count.read() + 1;
            r_initiator_fsm = M_WRITE_BLOCK;
        }					
        else                                                    // not the last burst
        {
            r_burst_count = r_burst_count.read() + 1;
            r_initiator_fsm = M_WRITE_CMD;
        }
        break;
    }
    case M_WRITE_BLOCK:		// write a block to disk after waiting m_latency cycles
    {
        if ( r_latency_count == 0 )
        {
            r_latency_count = m_latency;
            ::lseek(m_fd, (r_lba + r_block_count)*m_flits_per_block*vci_param::B, SEEK_SET);
            if( ::write(m_fd, m_local_buffer, m_flits_per_block*vci_param::B) < 0 )
            {
                r_initiator_fsm = M_WRITE_ERROR; 
            }
            else if ( r_block_count.read() == r_nblocks.read() ) 
            {
                r_initiator_fsm = M_WRITE_SUCCESS; 
            }
            else
            {
                r_initiator_fsm = M_WRITE_CMD;
            }
        } 
        else
        {
            r_latency_count = r_latency_count - 1;
        }
        break;
    }
    case M_WRITE_SUCCESS:
    {
        if( !r_go ) r_initiator_fsm = M_IDLE;
        break;
    }
    case M_WRITE_ERROR:
    {
        if( !r_go ) r_initiator_fsm = M_IDLE;
        break;
    }
    } // end switch r_initiator_fsm
}  // end transition

//////////////////////
tmpl(void)::genMoore()
{
    sc_dt::sc_uint<vci_param::N> 	offset;
    uint32_t			index;

    // p_vci_target port   
    p_vci_target.rsrcid = (sc_dt::sc_uint<vci_param::S>)r_srcid.read();
    p_vci_target.rtrdid = (sc_dt::sc_uint<vci_param::T>)r_trdid.read();
    p_vci_target.rpktid = (sc_dt::sc_uint<vci_param::P>)r_pktid.read();
    p_vci_target.reop   = true;

    switch(r_target_fsm) {
    case T_IDLE:
        p_vci_target.cmdack = true;
	p_vci_target.rspval = false;
        break;
    case T_READ_STATUS:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
	if     (r_initiator_fsm == M_IDLE)       	p_vci_target.rdata = BLOCK_DEVICE_IDLE;
        else if(r_initiator_fsm == M_READ_SUCCESS)    	p_vci_target.rdata = BLOCK_DEVICE_READ_SUCCESS;
        else if(r_initiator_fsm == M_WRITE_SUCCESS)   	p_vci_target.rdata = BLOCK_DEVICE_WRITE_SUCCESS;
        else if(r_initiator_fsm == M_READ_ERROR)   	p_vci_target.rdata = BLOCK_DEVICE_READ_ERROR;
        else if(r_initiator_fsm == M_WRITE_ERROR)  	p_vci_target.rdata = BLOCK_DEVICE_WRITE_ERROR;
        else                                    	p_vci_target.rdata = BLOCK_DEVICE_BUSY;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_BUFFER:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata  = (uint32_t)r_buf_address;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_COUNT:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = (uint32_t)r_nblocks;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_LBA:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = (uint32_t)r_lba;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_IRQEN:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = (uint32_t)r_irq_enable;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_SIZE:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = (uint32_t)m_device_size;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_BLOCK:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = (uint32_t)m_flits_per_block*vci_param::B;
        p_vci_target.rerror = VCI_READ_OK;
        break;
    case T_READ_ERROR:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = 0;
        p_vci_target.rerror = VCI_READ_ERROR;
        break;
    case T_WRITE_ERROR:
        p_vci_target.cmdack = false;
	p_vci_target.rspval = true;
        p_vci_target.rdata = 0;
        p_vci_target.rerror = VCI_WRITE_ERROR;
        break;
    default:
        p_vci_target.cmdack = false;
        p_vci_target.rspval = true;
        p_vci_target.rdata = 0;
        p_vci_target.rerror = VCI_WRITE_OK;
        break;
    } // end switch target fsm

    // p_vci_initiator port
    p_vci_initiator.srcid  = (sc_dt::sc_uint<vci_param::S>)m_srcid;
    p_vci_initiator.trdid  = 0;
    p_vci_initiator.pktid  = 0;
    p_vci_initiator.contig = true;
    p_vci_initiator.cons   = false;
    p_vci_initiator.wrap   = false;
    p_vci_initiator.cfixed = false;
    p_vci_initiator.clen   = 0;

    switch (r_initiator_fsm) {
    case M_WRITE_CMD:		// It is actually a single flit VCI read command
        offset = (r_block_count.read()*m_flits_per_block +
                  r_burst_count.read()*m_flits_per_burst) * vci_param::B;
        p_vci_initiator.rspack  = false;
        p_vci_initiator.cmdval  = true;
        p_vci_initiator.address = (sc_dt::sc_uint<vci_param::N>)r_buf_address.read() + offset;
        p_vci_initiator.cmd     = vci_param::CMD_READ;
        p_vci_initiator.wdata   = 0;
        p_vci_initiator.be      = (uint32_t)0xF;
        p_vci_initiator.plen    = (sc_dt::sc_uint<vci_param::K>)m_flits_per_burst*vci_param::B;
        p_vci_initiator.eop     = true;
        break;
    case M_READ_CMD:		// It is actually a multi-flits VCI WRITE command 
        offset = ( r_block_count.read()*m_flits_per_block +
                   r_burst_count.read()*m_flits_per_burst +
                   r_flit_count.read() ) * vci_param::B;
        index  = (r_burst_count.read()*m_flits_per_burst) + r_flit_count.read();
        p_vci_initiator.rspack  = false;
        p_vci_initiator.cmdval  = true;
        p_vci_initiator.address = (sc_dt::sc_uint<vci_param::N>)r_buf_address.read() + offset; 
        p_vci_initiator.cmd     = vci_param::CMD_WRITE;
        p_vci_initiator.wdata   = (uint32_t)m_local_buffer[index];
        p_vci_initiator.be      = 0xF;
        p_vci_initiator.plen    = (sc_dt::sc_uint<vci_param::K>)m_flits_per_burst*vci_param::B;
        p_vci_initiator.eop     = ( r_flit_count.read() == (m_flits_per_burst - 1) );
        break;
    case M_READ_RSP:
    case M_WRITE_RSP:
        p_vci_initiator.rspack  = true;
        p_vci_initiator.cmdval  = false;
	break;
    default:
	p_vci_initiator.rspack  = false;
	p_vci_initiator.cmdval  = false;
	break;
    }

    // IRQ signal
    if(((r_initiator_fsm == M_READ_SUCCESS)    ||
			    (r_initiator_fsm == M_WRITE_SUCCESS)   ||
			    (r_initiator_fsm == M_READ_ERROR)      ||
			    (r_initiator_fsm == M_WRITE_ERROR) ) &&  r_irq_enable) p_irq = true;
    else			                               p_irq = false;
} // end GenMoore()

//////////////////////////////////////////////////////////////////////////////
tmpl(/**/)::VciBlockDeviceTsarV4(   sc_core::sc_module_name	   	name, 
		const soclib::common::MappingTable 	&mt,
		const soclib::common::IntTab	&srcid,
		const soclib::common::IntTab	&tgtid,
		const std::string      	    	&filename,
		const uint32_t	        	block_size,
		const uint32_t	        	burst_size,
		const uint32_t          		latency)

: caba::BaseModule(name),
	m_segment(mt.getSegment(tgtid)),
	m_srcid(mt.indexForId(srcid)),
	m_flits_per_block(block_size/vci_param::B),
	m_flits_per_burst(burst_size/vci_param::B),
	m_bursts_per_block(block_size/burst_size),
	m_latency(latency),
	p_clk("p_clk"),
	p_resetn("p_resetn"),
	p_vci_initiator("p_vci_initiator"),
	p_vci_target("p_vci_target"),
	p_irq("p_irq") 
{
	SC_METHOD(transition);
	sensitive_pos << p_clk;

	SC_METHOD(genMoore);
	sensitive_neg << p_clk;

	if( (block_size != 64 ) && 
			(block_size != 128) && 
			(block_size != 256) && 
			(block_size != 512) && 
			(block_size != 1024) )
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "The block size must be 128, 256, 512 or 1024 bytes" << std::endl;
		exit(1);
	}
	if ( m_segment.size() < 32 ) 
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "The size of the segment cannot be smaller than 32 bytes" << std::endl;
		exit(1);
	}
	if ( (m_segment.baseAddress() & 0x0000001F) != 0 ) 
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "The base address of the segment must be multiple of 32 bytes" << std::endl;
		exit(1);
	}
	if ( block_size%burst_size != 0 )
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "The block_size parameter must be a multiple of the burst_size" << std::endl;
		exit(1);
	}
	if ( vci_param::B != 4 )
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "The VCI data fields must have 32 bits" << std::endl;
		exit(1);
	}
	m_fd = ::open(filename.c_str(), O_RDWR);
	if ( m_fd < 0 ) 
	{
		std::cout << "Error in component VciBlockDeviceTsarV4 : " << name << std::endl;
		std::cout << "Unable to open file " << filename << std::endl;
		exit(1);
	}
	m_device_size = lseek(m_fd, 0, SEEK_END) / block_size;
	if ( m_device_size > ((uint64_t)1<<32) ) 
	{
		std::cout << "Warning: block device " << name << std::endl;
		std::cout << "The file " << filename << std::endl;
		std::cout << "has more blocks than addressable with the 32 bits PIBUS address" << std::endl;
		m_device_size = ((uint64_t)1<<32);
	}

	m_local_buffer = new uint32_t[m_flits_per_block];

	std::cout << std::endl << "Instanciation of VciBlockDeviceTsarV4 : " << name << std::endl;
	std::cout << "    file_name  = " << filename << std::endl;
	std::cout << "    burst_size = " << std::dec << m_flits_per_burst*vci_param::B << std::endl;
	std::cout << "    block_size = " << std::dec << m_flits_per_block*vci_param::B << std::endl;
	std::cout << "    latency    = " << std::dec << m_latency << std::endl;
	std::cout << "    segment " << m_segment.name()
		<< " | base = 0x" << std::hex << m_segment.baseAddress()
		<< " | size = "   << std::dec << m_segment.size() << std::endl;

} // end constructor

//////////////////////////
tmpl(void)::print_trace()
{
	const char* initiator_str[] = {
		"IDLE         ",
		"READ_BLOCK   ",
		"READ_CMD     ",
		"READ_RSP     ",
		"READ_TEST    ",
		"READ_SUCCESS ",
		"READ_ERROR   ",
		"WRITE_BLOCK  ",
		"WRITE_CMD    ",
		"WRITE_RSP    ",
		"WRITE_TEST   ",
		"WRITE_SUCCESS",
		"WRITE_ERROR  ",
	};
	const char* target_str[] = {
		"IDLE        ",
		"WRITE_BUFFER",
		"READ_BUFFER ",
		"WRITE_COUNT ",
		"READ_COUNT  ",
		"WRITE_LBA   ",
		"READ_LBA    ",
		"WRITE_OP    ",
		"READ_STATUS ",
		"WRITE_IRQEN ",
		"READ_IRQEN  ",
		"READ_SIZE   ",
		"READ_BLOCK  ",
		"READ_ERROR  ",
		"WRITE_ERROR ",
	};

	std::cout << "BDEV_TGT : " << target_str[r_target_fsm.read()] 
		  << "  BDEV_INI : " << initiator_str[r_initiator_fsm.read()] 
		  << "  block = " << r_block_count.read() 
		  << "  burst = " << r_burst_count.read() 
		  << "  flit  = " << r_flit_count.read() <<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

