/*****************************************************************************

  The following code is derived, directly or indirectly, from the SystemC
  source code Copyright (c) 1996-2008 by all Contributors.
  All Rights reserved.

  The contents of this file are subject to the restrictions and limitations
  set forth in the SystemC Open Source License Version 3.0 (the "License");
  You may not use this file except in compliance with such restrictions and
  limitations. You may obtain instructions on how to receive a copy of the
  License at http://www.systemc.org/. Software distributed by Contributors
  under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
  ANY KIND, either express or implied. See the License for the specific
  language governing rights and limitations under the License.

 *****************************************************************************/

#include "lt_target_extension_mandatory.h"

using namespace sc_core;

static const char *filename = "lt_target_extension_mandatory.cpp";  ///  filename for reporting

lt_target_extension_mandatory::lt_target_extension_mandatory    ///< constructor
( sc_module_name  name                                          ///< instance name
, sc_time         invalidate_dmi_time = sc_time(25, SC_NS)      ///< DMI invalidation timeout
)
: sc_module       ( name                    )                   ///< module
, m_socket        ( "LT_target_mandatory"   )                   ///< socket
, m_latency_read  ( sc_time ( 100, SC_NS )  )                   ///< read latency
, m_latency_write ( sc_time (  10, SC_NS )  )                   ///< write latency
{
  // register interface methods
  
  m_socket.register_nb_transport_fw    ( this, &lt_target_extension_mandatory::nb_transport_fw );
  m_socket.register_get_direct_mem_ptr ( this, &lt_target_extension_mandatory::get_dmi_ptr     );
  m_socket.register_transport_dbg      ( this, &lt_target_extension_mandatory::transport_dbg   );

  // prime the DMI timeout mechanism

  SC_METHOD ( invalidate_dmi_method );
  
  sensitive << m_invalidate_dmi_event;
  
  dont_initialize();
  
  m_invalidate_dmi_time = invalidate_dmi_time;
}

tlm::tlm_sync_enum                                              ///< response
lt_target_extension_mandatory::nb_transport_fw                  ///< non-blocking forward transport
( transaction_type  &trans                                      ///< transaction
, phase_type        &phase                                      ///< phase
, sc_time           &t                                          ///< time
)
{
  ostringstream  msg;                                        ///< log message

  assert ( phase == tlm::BEGIN_REQ );
  
  // check for extension

  extension_initiator_id  *extension_ptr;
  
  trans.get_extension ( extension_ptr );
  
  msg.str("");

  msg << "Extension ";
  
  if ( extension_ptr == 0 )
  {
    msg << "not present - ERROR";
    
    REPORT_FATAL ( filename,  __FUNCTION__, msg.str () );
  }
  else
  {
    msg << "present, Data: " << extension_ptr->m_initiator_id;
  
    REPORT_INFO ( filename,  __FUNCTION__, msg.str () );
    
    sc_dt::uint64 address = trans.get_address();
    
    assert ( address <= m_max_address );
  
    unsigned int& data = *reinterpret_cast<unsigned int*>(trans.get_data_ptr());
    
    msg.str ( "" );
        
    switch ( trans.get_command () )
    {
      case tlm::TLM_WRITE_COMMAND:
      {
        // write command
        
        msg << "Write request - "
            << "Addr: 0x" << internal << setw ( sizeof(sc_dt::uint64) * 2 ) 
            << setfill( '0' ) << uppercase << hex << trans.get_address ();
    
        *reinterpret_cast<unsigned int*>( &m_memory [ address ] ) = data;
        t += sc_time(10, SC_NS);
        
        break;
      }
    
      case tlm::TLM_READ_COMMAND:
      {
        // read command
        
        msg << "Read request - "
            << "Addr: 0x" << internal << setw ( sizeof(sc_dt::uint64) * 2 ) 
            << setfill( '0' ) << uppercase << hex << trans.get_address ();
    
        data  = *reinterpret_cast<unsigned int*>( &m_memory [ address ] );
        t      += sc_time(100, SC_NS);
      
        break;
      }
      
      default:
      {
        // unknown command
        
        msg << "Unknown request";
    
        REPORT_FATAL ( filename,  __FUNCTION__, msg.str () );
        
        break;
      }
    }
      
    REPORT_INFO ( filename,  __FUNCTION__, msg.str () );
    
    trans.set_response_status ( tlm::TLM_OK_RESPONSE );
    trans.set_dmi_allowed     ( true                 );
  }

  // return completed, no phase update needed
  
  return tlm::TLM_COMPLETED;
}

unsigned int                                                    ///< byte count
lt_target_extension_mandatory::transport_dbg                    ///< debug transport
( transaction_type  &transaction_ref                            ///< transaction
)
{
  unsigned int num_bytes  = 0;
  unsigned int address    = (unsigned int) transaction_ref.get_address();
  
  if ( address <= m_max_address )
  {
    num_bytes = transaction_ref.get_data_length ();
    
    if ( ( address + num_bytes ) > m_max_address)
    {
      num_bytes = m_max_address - address + 1;
    }
    
    if ( transaction_ref.is_read () )
    {
      // read operation
      
      memcpy ( transaction_ref.get_data_ptr(), &m_memory [ address ], num_bytes );
    }
    else
    {
      // write operation
      
      memcpy ( &m_memory [ address ], transaction_ref.get_data_ptr(), num_bytes );
    }
  }
  
  return num_bytes;
}

bool                                                            ///< DMI granted
lt_target_extension_mandatory::get_dmi_ptr                      ///< get DMI pointer
( transaction_type  &trans                                      ///< transaction
, tlm::tlm_dmi      &dmi_properties                             ///< DMI properties
)
{
  bool           dmi_granted ( false );                         ///< DMI granted flag
  ostringstream  msg;                                           ///< log message
  
  // setup a timed event to invalidate the DMI pointer
  
  m_invalidate_dmi_event.notify ( m_invalidate_dmi_time );

  // check for extension
  
  extension_initiator_id *extension_ptr;
  
  trans.get_extension ( extension_ptr );
  
  msg.str ( "" );
  
  msg << "Extension ";
  
  if ( extension_ptr == 0 )
  {
    msg << "not present - ERROR";
    
    REPORT_FATAL ( filename,  __FUNCTION__, msg.str () );
  }
  else
  {
    msg << "present, Data: " << extension_ptr->m_initiator_id;
  
    REPORT_INFO ( filename,  __FUNCTION__, msg.str () );
    
    if (trans.get_address() <= m_max_address)
    {
      dmi_properties.allow_read_write   (                 );
      dmi_properties.set_start_address  ( m_min_address   );
      dmi_properties.set_end_address    ( m_max_address   );
      dmi_properties.set_dmi_ptr        ( m_memory        );
      dmi_properties.set_read_latency   ( m_latency_read  );
      dmi_properties.set_write_latency  ( m_latency_write );
  
      dmi_granted = true;
    }
    else
    {
      // should not happen
  
      dmi_properties.set_start_address  ( trans.get_address () );
      dmi_properties.set_end_address    ( trans.get_address () );
    }
 }
  
  return dmi_granted;
}

void
lt_target_extension_mandatory::invalidate_dmi_method            ///< invalidate DMI method
( void
)
{
    m_socket->invalidate_direct_mem_ptr ( m_min_address, m_max_address );
}
