
// Unit test for tlm_generic_payload update_original_from() method

#include <iomanip>

// #define SC_INCLUDE_DYNAMIC_PROCESSES

#include "systemc"
using namespace sc_core;
using namespace sc_dt;
using namespace std;

#include "tlm.h"
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"

#include "mm.h"

struct my_extension: tlm::tlm_extension<my_extension>
{
  my_extension() {}

  virtual tlm_extension_base* clone() const
  {
    my_extension* ext = new my_extension;
    ext->len = len;
    ext->bel = bel;
    ext->ptr = ptr;
    ext->byt = byt;
    return ext;
  }

  virtual void copy_from(tlm_extension_base const &ext)
  {
    len = static_cast<my_extension const &>(ext).len;
    bel = static_cast<my_extension const &>(ext).bel;
    ptr = static_cast<my_extension const &>(ext).ptr;
    byt = static_cast<my_extension const &>(ext).byt;
  }

  unsigned int len;
  unsigned int bel;
  unsigned char* ptr;
  unsigned char* byt;
};


struct Initiator: sc_module
{
  tlm_utils::simple_initiator_socket<Initiator> socket;

  typedef unsigned char uchar;

  SC_CTOR(Initiator)
  : socket("socket")
  {
    SC_THREAD(thread_process);
    data = new uchar[32];
    byte_enable = new uchar[32];
  }

  void thread_process()
  {
    tlm::tlm_generic_payload* trans;
    sc_time delay = SC_ZERO_TIME;

    for (int i = 0; i < 1000000; i++)
    {
      int addr = 0;
      tlm::tlm_command cmd = static_cast<tlm::tlm_command>(rand() % 2);

      unsigned int len = rand() % 33;
      unsigned int bel = rand() % (len + 1);

      // Fix sizes when performing a speed test
      //cmd = tlm::TLM_READ_COMMAND;
      //len = 32;
      //bel = 8;


      for (unsigned int i = 0; i < len; i++)
        if (cmd == tlm::TLM_WRITE_COMMAND)
        {
          data[i] = rand() % 256;
        }
        else
          data[i] = 0x99;

      trans = m_mm.allocate();
      trans->acquire();

      trans->set_command( cmd );
      trans->set_address( addr );
      trans->set_data_ptr( data );
      trans->set_data_length( len );
      trans->set_streaming_width( len );

      trans->set_byte_enable_length( bel );

      for (unsigned int i = 0; i < bel; i++)
      {
        byte_enable[i] = (rand() % 2) ? 0xff : 0;
      }

      if (bel)
        trans->set_byte_enable_ptr( byte_enable );
      else
        trans->set_byte_enable_ptr( 0 );

      trans->set_dmi_allowed( false );
      trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE );

      // Add sticky extension diagnostics
      my_extension* ext;
      trans->get_extension(ext);
      if (!ext)
      {
        ext = new my_extension;
        trans->set_extension(ext);
      }

      ext->len = len;
      ext->bel = bel;
      ext->ptr = data;
      ext->byt = byte_enable;

      socket->b_transport( *trans, delay );  // Blocking transport call

      if ( trans->is_response_error() )
        SC_REPORT_ERROR("TLM-2", "Response error from b_transport");

      //cout << "cmd = " << cmd << ", len = " << len << ", bel = " << bel << endl;
      //cout << hex << setw(2) << setfill('0') << (unsigned int)data[i];

      if (cmd == tlm::TLM_READ_COMMAND)
      {
        for (unsigned int i = 0; i < len; i++)
          if (bel)
            if (byte_enable[i % bel])
              assert( data[i] == ext->ptr[i] );
            else
              assert( data[i] == 0x99 );
          else
            assert( data[i] == ext->ptr[i] );

      }
      trans->release();
    }
  }

  mm  m_mm;  // Memory manager
  unsigned char* data; // 32 bytes
  unsigned char* byte_enable;
};


struct Interconnect: sc_module
{
  tlm_utils::simple_target_socket   <Interconnect> targ_socket;
  tlm_utils::simple_initiator_socket<Interconnect> init_socket;

  SC_CTOR(Interconnect)
  : targ_socket("targ_socket")
  , init_socket("init_socket")
  {
    targ_socket.register_b_transport(this, &Interconnect::b_transport);
  }

  virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay )
  {
    unsigned int     bel = trans.get_byte_enable_length();

    trans2.set_data_ptr( data );
    if (bel)
      trans2.set_byte_enable_ptr( byte_enable );
    trans2.deep_copy_from(trans);

    init_socket->b_transport( trans2, delay );

    trans.update_original_from( trans2 );
  }

  tlm::tlm_generic_payload trans2;
  unsigned char data[32];
  unsigned char byte_enable[32];
};


struct Target: sc_module
{
  tlm_utils::simple_target_socket<Target> socket;

  SC_CTOR(Target)
  : socket("socket")
  {
    socket.register_b_transport(this, &Target ::b_transport);

    typedef unsigned char uchar;
    data = new uchar[32];
  }


  virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay )
  {
    tlm::tlm_command cmd = trans.get_command();
    unsigned char*   ptr = trans.get_data_ptr();
    unsigned int     len = trans.get_data_length();
    unsigned char*   byt = trans.get_byte_enable_ptr();
    unsigned int     bel = trans.get_byte_enable_length();

    my_extension* ext;
    trans.get_extension(ext);
    assert( ext );

    assert( len == ext->len );
    assert( bel == ext->bel );
    for (unsigned int i = 0; i < bel; i++)
      assert( byt[i] == ext->byt[i] );
    for (unsigned int i = 0; i < len; i++)
      assert( ptr[i] == ext->ptr[i] );

    if (cmd == tlm::TLM_READ_COMMAND)
    {
      for (unsigned int i = 0; i < len; i++)
      {
        data[i] = rand() % 256;
        ptr[i]  = data[i];
      }
      ext->ptr = data;
    }

    trans.set_dmi_allowed( true );
    trans.set_response_status( tlm::TLM_OK_RESPONSE );
  }

  unsigned char* data; // 32 bytes
};


SC_MODULE(Top)
{
  Initiator    *initiator;
  Interconnect *interconnect;
  Target       *target;

  SC_CTOR(Top)
  {
    // Instantiate components
    initiator    = new Initiator   ("initiator");
    interconnect = new Interconnect("interconnect");
    target       = new Target      ("target");

    // One initiator is bound directly to one target with no intervening bus

    // Bind initiator socket to target socket
    initiator    ->socket     .bind( interconnect->targ_socket );
    interconnect ->init_socket.bind( target      ->socket );
  }
};


int sc_main(int argc, char* argv[])
{
  cout << "Unit test for update_original_from(). Should remain silent\n";

  Top top("top");
  sc_start();
  return 0;
}

