/* -*- 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
 *         Ghassan Almaless <ghassan.almaless@lip6.fr>, 2011
 *
 * Maintainers: Ghassan
 */

#include "vci_profiler.h"
#include <cstring>
#include <iostream>
#include "vci_buffers.h"
#include <iostream>
#include <iomanip>
#include <cassert>
#include <vector>

namespace soclib {
namespace caba {

#define tmpl(...) template<typename vci_param, typename iss_t> __VA_ARGS__ VciProfiler<vci_param, iss_t>

#define ABS(x) (((x) < 0) ? -(x) : (x))

#define VCI_iRD_CMD             4
#define VCI_iTLB_RD_CMD         5
#define VCI_iTLB_LL_CMD         6
#define VCI_iTLB_SC_CMD         7
#define VCI_DTLB_RD_CMD         8
#define VCI_DTLB_LL_CMD         9
#define VCI_DTLB_SC_CMD         10

#define _CMD_ITLB               0x1
#define _CMD_DTLB               0x2   
#define _CMD_USR                0x4

static const char* vci_cmd_names[VCI_CMD_NR] = {"SC_CMD ", 
                                                "RD_CMD ", 
                                                "WR_CMD ", 
                                                "LL_CMD ", 
                                                "iRD_CMD",
                                                "iTLB_RD_CMD",
                                                "iTLB_LL_CMD",
                                                "iTLB_SC_CMD",
                                                "DTLB_RD_CMD",
                                                "DTLB_LL_CMD",
                                                "DTLB_SC_CMD"};

tmpl(void)::print_stats()
{
    unsigned long local_cntr;
    unsigned long total_cost;
    unsigned long total_flits;
    unsigned long total_rflits;
    unsigned long total_estmtd_enrg;
    std::ostream &output = m_l1->m_log;

    if(m_period == 0)
        return;
    
    total_cost        = 0;
    total_flits       = 0;
    total_rflits      = 0;
    total_estmtd_enrg = 0;
    
    local_cntr = m_access_cntr - m_remote_cntr;
    
    output << name() << " Cycles: " << std::dec << m_tm_now << std::endl 
           << "Access_Rate  = " << (double)  m_access_cntr / m_cycles_cntr << std::endl
           << "Remote_%     = " << (double)  m_remote_cntr / m_access_cntr << std::endl
           << "LL_%         = " << (double)  m_cmd_tbl[vci_param::CMD_LOCKED_READ].trans / m_access_cntr << std::endl
           << "SC_%         = " << (double)  m_cmd_tbl[vci_param::CMD_STORE_COND].trans  / m_access_cntr << std::endl;
    
    for(long i = 0; i < VCI_CMD_NR; i++)
    {

        local_cntr = m_cmd_tbl[i].distance * m_cmd_tbl[i].rflits;

        output << vci_cmd_names[i] << " [" 
               << m_cmd_tbl[i].trans << ", "
               << m_cmd_tbl[i].remote << ", "
               << m_cmd_tbl[i].trans - m_cmd_tbl[i].remote  << ", "
               << m_cmd_tbl[i].cost << ", "
               << m_cmd_tbl[i].rcost << ", "
               << m_cmd_tbl[i].cost - m_cmd_tbl[i].rcost << ", "
               << m_cmd_tbl[i].limit1 << ", "
               << m_cmd_tbl[i].limit2 << ", "
               << m_cmd_tbl[i].rmax_cost << ", "
               << m_cmd_tbl[i].rmax_time << ", "
               << m_cmd_tbl[i].lmax_cost << ", "
               << m_cmd_tbl[i].lmax_time << ", "
               << m_cmd_tbl[i].flits <<  ", " 
               << m_cmd_tbl[i].rflits << ", " << std::hex
               << m_cmd_tbl[i].raddr << ", " << std::dec 
               << m_cmd_tbl[i].distance << ","
               << m_cmd_tbl[i].d1_limit << ","
               << m_cmd_tbl[i].d2_limit << ","
               << m_cmd_tbl[i].max_dist << ","
               << local_cntr << "]" << std::endl;

        total_cost        += m_cmd_tbl[i].cost;
        total_flits       += m_cmd_tbl[i].flits;
        total_rflits      += m_cmd_tbl[i].rflits;
        total_estmtd_enrg += local_cntr;
    }
    
    output << "Total_CMDs         = " << m_access_cntr << std::endl
           << "Total_Cost         = " << total_cost << std::endl
           << "Total_Flits        = " << total_flits << std::endl
           << "Total_Remote_Flits = " << total_rflits << std::endl 
           << "Total_Estmtd_Enrg  = " << total_estmtd_enrg << std::endl;
}

tmpl(void)::clear_stats()
{
    memset(m_cmd_tbl, 0, sizeof(m_cmd_tbl));
    m_access_cntr = 0;
    m_remote_cntr = 0;
    m_cycles_cntr = 0;
}


tmpl(void)::set_limits(unsigned long limit1, unsigned long limit2)
{
    m_limit1 = limit1;
    m_limit2 = limit2;
}

tmpl(void)::set_L1_cache(VciCcVCacheWrapper2V1<vci_param, iss_t> *l1)
{
    m_l1 = l1;
}


tmpl()::VciProfiler(
                  sc_core::sc_module_name insname,
                  size_t index, 
                  size_t x_id,
                  size_t y_id,
                  size_t x_width,
                  size_t y_width,
                  size_t addr_width)
	: BaseModule(insname),
      p_resetn("resetn"),
      p_clk("clk"),
      p_vci("vci")
{
    clear_stats();
    m_index        = index;
    m_x            = x_id;
    m_y            = y_id;
    m_xWidth       = x_width;
    m_yWidth       = y_width;
    m_width        = x_width + y_width;
    m_bits         = addr_width;
    m_tm_now       = 0;
    m_tm_start     = 0;
    m_isPending    = false;
    m_isCMDPending = false;
    m_isPeriod     = false;
    m_isDebug      = false;
    m_last_cmd     = 0;
    m_limit1       = 0;
    m_limit2       = 0;
    m_d1_limit     = 0;
    m_d2_limit     = 0;
    m_period       = 0;
    m_debug_cntr   = 0;
    m_threshold    = 1000000;

    char *ptr = getenv("TSAR_PROFILER_PERIOD");

    if(ptr != NULL)
        m_period = atol(ptr);

    ptr = getenv("TSAR_PROFILER_THRESHOLD");

    if(ptr != NULL)
        m_threshold = atol(ptr);

    ptr = getenv("TSAR_PROFILER_DEBUG");

    if(ptr != NULL)
        m_isDebug = (atol(ptr) != 0) ? true : false;

    ptr = getenv("TSAR_PROFILER_LIMIT1");

    if(ptr != NULL)
        m_limit1 = atol(ptr);

    ptr = getenv("TSAR_PROFILER_LIMIT2");

    if(ptr != NULL)
        m_limit2 = atol(ptr);

    ptr = getenv("TSAR_PROFILER_D1_LIMIT");

    if(ptr != NULL)
        m_d1_limit = atol(ptr);

    ptr = getenv("TSAR_PROFILER_D2_LIMIT");

    if(ptr != NULL)
        m_d2_limit = atol(ptr);

#if 0    
    if(m_period != 0)
        m_log.open(insname, std::ios::out);
#endif

	SC_METHOD(transition);
	dont_initialize();
	sensitive << p_clk.pos();
}

tmpl()::~VciProfiler()
{
}

tmpl(void)::transition()
{
    unsigned long cmd;
    unsigned long shift;
    unsigned long msb;
    long x_coord;
    long y_coord;
    unsigned long distance;
    unsigned long cost;
    bool isUser;
    
    std::ostream &output = m_l1->m_log;

    if(m_period == 0)
        return;

    if( p_vci.cmdval.read() && p_vci.cmdack.read() )
    {
        if(m_isPending == false)
        {
            m_isCMDPending = true;
            m_isPending    = true;
            m_tm_start     = m_tm_now;
            cmd            = p_vci.cmd.read();
            m_access_cntr ++;

            if((cmd == vci_param::CMD_READ) && (p_vci.trdid.read() > 1))
                cmd = VCI_iRD_CMD;
            
            else if((cmd == vci_param::CMD_READ) && (p_vci.pktid.read() & _CMD_ITLB))
                cmd = VCI_iTLB_RD_CMD;

            else if((cmd == vci_param::CMD_READ) && (p_vci.pktid.read() & _CMD_DTLB))
                cmd = VCI_DTLB_RD_CMD;

            else if((cmd == vci_param::CMD_LOCKED_READ) && (p_vci.pktid.read() & _CMD_ITLB))
                cmd = VCI_iTLB_LL_CMD;

            else if((cmd == vci_param::CMD_LOCKED_READ) && (p_vci.pktid.read() & _CMD_DTLB))
                cmd = VCI_DTLB_LL_CMD;

            else if((cmd == vci_param::CMD_STORE_COND) && (p_vci.pktid.read() & _CMD_ITLB))
                cmd = VCI_iTLB_SC_CMD;

            else if((cmd == vci_param::CMD_STORE_COND) && (p_vci.pktid.read() & _CMD_DTLB))
                cmd = VCI_DTLB_SC_CMD;

            m_last_cmd  = cmd;
            m_last_addr = p_vci.address.read();
            m_cmd_tbl[cmd].trans ++;
            m_cmd_tbl[cmd].flits += ((p_vci.plen.read() / 4) + 1);

            shift    = m_bits - m_width;
            msb      = m_last_addr >> shift;
            distance = 0;

            m_isRemote = (msb == m_index) ? false : true;
            
            if(m_isRemote == true)
            {
                isUser   = (p_vci.pktid.read() & _CMD_USR) ? true : false;
                m_cmd_tbl[cmd].remote ++;
                m_remote_cntr ++;
                x_coord  = msb >> m_yWidth;
                y_coord  = msb & ((1 << m_yWidth) - 1);
                distance = ABS(m_x - x_coord) + ABS(m_y - y_coord);
                
                output << ">> " << vci_cmd_names[cmd] 
                       << "\t(" << m_x << "," << m_y << ") --> (" 
                       << x_coord << "," << y_coord << ")\t[ D: " 
                       << distance << " @: " << std::hex << m_last_addr << std::dec 
                       << " T: " << m_tm_now << " "<< ((isUser) ? "U" : "K") << " ]" << std::endl;
                
                m_cmd_tbl[cmd].rflits += ((p_vci.plen.read() / 4) + 1);
                m_cmd_tbl[cmd].distance += distance;
                
                if(distance > m_cmd_tbl[cmd].max_dist)
                    m_cmd_tbl[cmd].max_dist = distance;
                
                if(distance >= m_d2_limit)
                    m_cmd_tbl[cmd].d2_limit ++;
                else if(distance >= m_d1_limit)
                    m_cmd_tbl[cmd].d1_limit ++;
            }

#if 1
            if(m_isDebug)
            {
                output << ">>> " << std::dec
                       << "Cycle: "<< m_tm_now
                       << ", Addr " << std::hex << m_last_addr 
                       << ", isRemote " << m_isRemote
                       << ", Distance " << distance
                       << ", " << vci_cmd_names[m_last_cmd];
            }
#endif
        }

        if(p_vci.eop.read())
            m_isPending = false;
    }

    if ( p_vci.rspval.read() && p_vci.rspack.read() )
    {
        if( p_vci.reop.read() )
        {
            m_isCMDPending = false;
            cost = m_tm_now - m_tm_start;

            m_cmd_tbl[m_last_cmd].cost += cost;
            
            if(m_isRemote == true)
                m_cmd_tbl[m_last_cmd].rcost += cost;

            if(cost > m_limit2)
                m_cmd_tbl[m_last_cmd].limit2 ++;
            else if(cost > m_limit1) 
                m_cmd_tbl[m_last_cmd].limit1 ++;

            if(m_isRemote == true)
            {   
                if(cost > m_cmd_tbl[m_last_cmd].rmax_cost)
                {
                    m_cmd_tbl[m_last_cmd].rmax_time = m_tm_now;
                    m_cmd_tbl[m_last_cmd].rmax_cost = cost;
                    m_cmd_tbl[m_last_cmd].raddr = m_last_addr;
                }
            }
            else
            {
                if(cost > m_cmd_tbl[m_last_cmd].lmax_cost)
                {
                    m_cmd_tbl[m_last_cmd].lmax_time = m_tm_now;
                    m_cmd_tbl[m_last_cmd].lmax_cost = cost;
                }
            }
#if 1
            if(m_isDebug)
            {
                output << "  <<< " << std::dec
                       << "Cycle: "<< m_tm_now
                       << ", Cost: " << cost 
                       << ", Addr " << std::hex << m_last_addr 
                       << ", " << vci_cmd_names[m_last_cmd]
                       << std::endl;
            }
#endif
        }
    }

    m_tm_now ++;
    m_cycles_cntr ++;

#if 1
    if((m_isCMDPending == true) && ((m_tm_now - m_tm_start) > m_threshold) && (m_isDebug == false))
    {
        output << "Threshold is reached: " << std::dec
               << "Tm_now: " << m_tm_now 
               << " Tm_start: " << m_tm_start
               << " Val: " << m_tm_now - m_tm_start
               << " Threshold: " << m_threshold
               << std::endl;

        m_isDebug = true;
    }

    if(m_isDebug == true)
    {
        m_l1->print_trace(0);
        m_debug_cntr ++;
    }

    if(m_debug_cntr > 30)
    {
        m_isDebug = false;
        print_stats();
    }
#endif

    if((m_period != 0) && ((m_tm_now % m_period) == 0))
    {
        output << "Period reached, " << std::dec
               << m_period << ", "
               << m_tm_now << ", "
               << m_isPeriod << ", "
               << m_isCMDPending << std::endl;
            
        m_isPeriod = true;
    }

    if((m_isPeriod == true) && (m_isCMDPending == false))
    {
        print_stats();
        clear_stats();
        m_isPeriod = false;
    }
}

}}

// 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

