Home > Community > Blogs > System Design and Verification > tracing tlm 2 0 activity in an esl design part i
 
Login with a Cadence account.
Not a member yet?
Create a permanent login account to make interactions with Cadence more conveniennt.

Register | Membership benefits
Get email delivery of the Cadence blog (individual posts).
 

Email

* Required Fields

Recipients email * (separate multiple addresses with commas)

Your name *

Your email *

Message *

Contact Us

* Required Fields
First Name *

Last Name *

Email *

Company / Institution *

Comments: *

Tracing TLM 2.0 Activity In An ESL Design – Part I

Comments(0)Filed under: System Design and Verification, ESL, SystemC analysis, George Frazier, TLM, TLM 2.0, sctlmrecord

Many design teams that use SystemC  are in various stages of evaluating TLM 2.0 – the Open SystemC Initiative’s transaction level library designed for modeling memory-mapped buses and on-chip communication networks. The new standard  takes us one step closer to creating an ecosystem where ESL models can interoperate across the boundaries of design teams, IP providers, and tool vendors. TLM 2.0 designs are much faster than equivalent RTL designs. TLM 2.0 processor models often run faster than the actual hardware they’re emulating.

But this doesn’t mean it’s easy to fish out a particular sequence of reads or writes from the ocean of transport calls a simulation session might produce. If you’ve worked with real TLM 2.0 designs, you know that a typical loosely timed model can generate millions of calls between sockets. Consider a virtual platform where a processor can read from a number of memories connected via multiple layers of interconnects. The volume of information – expressed as the set of all data member values of generic payload objects traveling through b_transport(), nb_transport_fw(), and nb_transport_bw() calls – can be formidable. Sure, looking through sequences of TLM 2.0 reads and writes might be easier than making sense of the equivalent RTL-level information, but it’s still like searching for a lost grain of sand – it doesn’t much matter whether you’re at Destin Beach or in the Sahara Desert. Without proper tools you might as well grab a Corona Light and look for an umbrella.

The quickest way to get a sense for what’s happening in the guts of a TLM 2.0 target is to instrument your transport methods with print statements. Almost every TLM 2.0 example I see on the Internet does this. Here’s an example of a simple memory block that reports some data for each successful operation:

#include <systemc>

#include "tlm.h"

void simple_memory::b_transport(tlm_generic_payload& tx, sc_time& dt)

{

  tlm_command opcode = tx.get_command();

  uint64 addr = tx.get_address();

  unsigned length = tx.get_data_length();

  unsigned char * data_ptr = tx.get_data_ptr();

  wait(10*(length/4), SC_NS);

  if (tx.get_byte_enable_length() != 0) {

    tx.set_response_status(TLM_BYTE_ENABLE_ERROR_RESPONSE);

    return;

  }

  int strw = tx.get_streaming_width();

  int nb_cpy;

  if (tx.get_streaming_width() < length) {

    nb_cpy = length / strw;

  }

  else {

    nb_cpy = 1;

    strw = length;

  }

  if (nb_cpy*strw != length) {

    tx.set_response_status(TLM_BURST_ERROR_RESPONSE);

    return;

  }

  if (addr + strw <= size) {

    for(int cpy = 0; cpy < nb_cpy; cpy++) {

      if (opcode == TLM_READ_COMMAND) {

        memcpy(data_ptr+cpy*strw, &mem_array[addr], strw);

      }

      if (opcode == TLM_WRITE_COMMAND) {

        memcpy(&mem_array[addr], data_ptr+cpy*strw, strw);

        for (int i=0; i<strw; i++) {

          notify_write_access(addr+i);

        }

      }

    }

    tx.set_response_status(TLM_OK_RESPONSE);

  }

  else {

    tx.set_response_status(TLM_ADDRESS_ERROR_RESPONSE);

  }

  if (tx.get_response_status() == TLM_OK_RESPONSE){

    if (opcode == TLM_READ_COMMAND) {

      std::cout << "Reading " << length <<

           " bytes from " addr << std::endl;

    }

    else {

      std::cout << "Writing " << length <<

           " bytes from " addr << std::endl;

    }

  }

}

This might be OK for small examples, but you can’t do much with text output. A better approach, though still manual, is to use the SCV transaction recording API to create an SST2 database of memory accesses. SCV transaction recording fits naturally into the TLM 2.0 paradigm. Presuming our simple_memory class has the following members:

scv_tr_stream *m_streamHandle;

scv_tr_generator< > *m_genHandle;

scv_tr_db* m_db;

and some initialization code in the constructor to create the database:

m_db = new scv_tr_db("MemoryDatabase");

we can change our transport routine to write generic payload information to a database. Now our simple_memory class creates a transaction for each successful read or write. It uses scv_tr_handle::record_attribute() to write attribute data to the transaction.

#include <systemc>

#include "scv.h"

#include "tlm.h"

void simple_memory::b_transport

  (tlm_generic_payload& tx, sc_time& dt)

{

  tlm_command opcode = tx.get_command();

  uint64 addr = tx.get_address();

  unsigned length = tx.get_data_length();

  unsigned char * data_ptr = tx.get_data_ptr();

  // Create a transaction stream

  // and a transaction generator if they don’t exist

  if (!m_genHandle)

  {

    m_streamHandle =

      new scv_tr_stream("MemoryStream",

                        "TRANSACTOR", m_db);

    m_genHandle =

      new scv_tr_generator< >("MemoryTx",

              *m_streamHandle), "addr", "data");

  }

  // Get a handle for the new transaction

  scv_tr_handle h = m_genHandle->begin_transaction();

  wait(10*(length/4), SC_NS);

  // Write the command type

  if (tx.get_response_status() == TLM_OK_RESPONSE){

    if (opcode == TLM_READ_COMMAND) {

      h.record_attribute("command", "READ");

    }

  } else {

      h.record_attribute("command", "READ");

  }

  // Write the address and data length

  h.record_attribute("addr", addr);

  h.record_attribute("length", length);

  // End the transaction

  h.end_transaction();

}

The resulting SST2 database is better than text, but for complex designs you might end up writing a significant amount of transaction recording code. Unless you add the ability to turn tracing on and off, large designs can slow significantly depending on how much data they produce. Also, this strategy doesn’t capture information about the connectivity of the design. There is no history of where reads and writes initiated or which interconnects they traveled across to reach the memory.

In my next post I’ll cover a new feature in IUS 8.2 that addresses these problems and provides a framework for debugging TLM 2.0 designs. The feature, called sctlmrecord, automatically creates a complete transaction history of generic payload values as they flow through a TLM 2.0 design. You can simulate up to a point of interest in a design and then turn on recording, capturing only the information you need to debug your problem. The database can be viewed in the Simvision Waveform Viewer or Transaction Explorer. You can query it with TxE scripts.

Best of all, you don’t have to write a single line of code to get it to work.

 

 

Comments(0)

Leave a Comment


Name
E-mail (will not be published)
Comment
 I have read and agree to the Terms of use and Community Guidelines.
Community Guidelines
The Cadence Design Communities support Cadence users and technologists interacting to exchange ideas, news, technical information, and best practices to solve problems and get the most from Cadence technology. The community is open to everyone, and to provide the most value, we require participants to follow our Community Guidelines that facilitate a quality exchange of ideas and information. By accessing, contributing, using or downloading any materials from the site, you agree to be bound by the full Community Guidelines.