How to Align SystemVerilog-to-SystemC TLM Transactions Definitions

This post presents a method to align definitions of the objects defined in SystemVerilog and SystemC. An object from SystemVerilog is aligned with an object in SystemC if they both have the same fields with same data types.

Verification projects that use both SystemVerilog and SystemC make use of TLM transactions to exchange data between the two realms. The communication between SV and SC requires a serialization operation on the source side and a deserialization on the destination side, the last being sensitive to coding errors (i.e. definition misalignment). Serialization is required since both sides use TLM Generic Payload (GP) as a common ground, which in turn uses a byte array to transport data.

One simple way to check if the definitions are aligned is by sending a transaction with a TLM read command, unpack it and send it back to SV and comparing with the original transaction:

Transaction Definition Alignment Verification Dataflow

Before jumping to an example, the following assumptions will be made:

  • UVM-ML is used to connect SV to SC
  • A blocking port is connected, SV being the initiator

Let’s consider a simple transaction with three fields:

class simple_transaction;
        byte m_type;
        int unsigned m_len; // number of data frames
        int  m_data[$];

First, one must create an “uvm_tlm_generic_payload” that holds one of these transactions. In order to serialize the transaction into TLM GP, one can use SystemVerilog’s streaming operators:

function uvm_tlm_generic_payload trans2gp(simple_transaction tr);

    // Create a new GP object
    uvm_tlm_generic_payload gp = new();

    // Pack transaction
    byte unsigned data[] = {>>{tr.m_type,tr.m_len,tr.m_data}};

    // Put the serialized transaction in the GP
    return gp;

Once the GP is available it can be sent to SystemC, where it will be unpacked into a similar class:

class simple_transaction {
  unsigned char m_type;
  uint32_t m_len;
  std::vector<int> m_data;

For packing/unpacking in C++, there are multiple options: string streams, overwriting the stream operators for the transaction or even “memcpy”. This won’t be shown here since it’s not relevant, but you can find the whole example on AMIQ’s GitHub

void b_transport(tlm_generic_payload& gp, sc_time& dt) {
    // Unpack gp into transaction class
    simple_transaction t = array2transaction(gp.get_data_ptr(), gp.get_data_length());

After extracting all the fields, the same transaction will be repacked as a GP (by doing the same operations in reverse order) and sent back to SV::

void b_transport(tlm_generic_payload& gp, sc_time& dt) {
    // Overwrite previous data
    unsigned char* data = gp.get_data_ptr();
    unsigned int len = gp.get_data_len();
    transaction2array(t, data, len);

Back in SV, the new GP containing the read command response (the same transaction packed from SC) is unpacked into a different transaction object and compared with what was initially sent:

{>>{recv.m_type, recv.m_len, recv.m_data}} = gp.m_data;

if ( begin
`uvm_info("PASS", "Got same data", UVM_LOW);
end else begin
`uvm_error("FAIL", "Got different data on same transaction");

For the full example check out AMIQ’s GitHub.

The UVM do_pack / do_unpack functions can also be used to pack data in SV automatically.

If using UVM-ML with Cadence, there is also the “mltypemap” utility which can automatically generate corresponding object declarations along with packers and unpackers in both SC and SV.


How to Pack Data Using the SystemVerilog Streaming Operators (>>, <<)

How to Unpack Data Using the SystemVerilog Streaming Operators (>>, <<)

C++ string streams

Converting endianess in C/C++

Type mapping from SV to C++


Leave a Comment:

Your comment will be visible after approval.

(will not be published)