Connect SystemC models using UVM Connect

By Vishal Baskar |  No Comments  |  Posted: December 23, 2022
Topics/Categories: EDA - Verification  |  Tags: , , ,  | Organizations:

Learn how UMVC helps bridge between SystemC and System Verilog using transaction level modeling for test and library efficiency.

We are aware that SystemC (SC) models enable high-performance simulation of system behavior and create accurate and efficient models of hardware-software components. SystemC is being widely adopted for system-level modeling. Although the complexity of systems can be handled using abstraction levels offered by C/C++/SystemC, their verification is always a bottleneck. One of the great strengths of SystemC is that it can be used for modeling high levels of abstraction which can be used with SystemVerilog components. We also should not forget its versatility in mixed language designs where it offers various mechanisms and constructs required for embedded systems modeling.

Here comes UVM Connect (UVMC) to the rescue. It eases these processes so they are consistent and easy to debug where SC enables abstraction refinements at various levels. It makes possible reuse of the stimulus generation agents in SV to verify SC models. One can also leverage the speed and capacity of SC for verifying untimed or loosely timed system-level environments. We will look at two examples that use transaction level modelling (TLM).

TLM are transaction-based methods which can be used for communication between modules. UVM offers TLM libraries such as ports, sockets, imp, and interface ports. The libraries branch into two versions, TLM-1.0 and TLM-2.0. We have examples based on each.

Some key points to consider:

  • TLM-1.0 has no standard transaction class, so each application must create its non-standard classes, resulting in very poor interoperability between models from different sources. TLM-2.0 addresses this shortcoming with the generic payload.
  • TLM-1.0 has no explicit support for timing annotation, so there is no standardized way of communicating timing information between models. TLM-2.0 addresses this with the addition of timing annotation function arguments to the blocking and non-blocking transport interface.
  • The TLM-1.0 interfaces require all transaction objects and data to be passed by value or const reference, and models created using it must generate a delay using wait that may slow down simulation in certain use cases. TLM-2.0 passes transaction objects by non-const reference, which is a fast solution for modeling memory-mapped buses.

The TLM-2.0 library provides model interoperability for memory-mapped bus modeling, and it is recommended that core interfaces, sockets, generic payloads, and base protocols are used in concert. They are collectively known as the interoperability layer. TLM-2.0 rules require that the same transaction object be used until transaction execution is complete. This improves the efficiency of the execution. This article discusses two examples. One is a simple example of data transfer by port connection; the other is data transfer of different transaction types and sizes on both the SV and SC side.

In both examples, the transaction is initiated in the form of packets from the SV side and is sent using UVMC. The SC model here acts as a monitor, enabling unpacking and packing of the bits and thus ensuring that there is no loss of data and that the packet is sent back to the SV side using UVMC. The received bits are then compared using a comparator or a checker to make sure the same bits that were sent were received. The only difference in the second example is the addition of data and address inverted from the SC side and the received packet is verified on the SV side.

Figure 1. Examples reference (Siemens EDA)

Figure 1. Examples reference (Siemens EDA  – click to enlarge)

Example of a simple port connection

In the first example, the packets sent to both the SV and SC ports are of the same type. A producer_loop_back component initiates the transfers while a consumer_loop_back component returns the same transaction that is received. The goal at the end of the test is to ensure that there is no loss of information after packing and unpacking bits, and that all sent packets are compared on the SV side with the packets that are received. The SC part here acts as a monitor, receiving packed bits of data and addresses, from the SV side, and sending them back by unpacking them using TLM ports.

The example comprises several elements: a SystemC model, a SystemVerilog testbench, TLM connections, and UVMC libraries. It handles packets of data being transferred from the SV side to the SC side via a TLM 1.0 blocking transport port using the UVM connections. Unpacking is done on the SC side, and the bits are then sent back to the SV side, ensuring no loss of data. Here, we consider the basic building blocks of a SystemC model as a monitor in a UVM testbench, and connections are made via TLM ports using UVMC, sending a data type like an ethernet packet.

To communicate, verification components must agree on the data they are exchanging, and the interface that will be used to exchange that data. The data type for the connections should look the same regardless of the types of TLM interface being connected. The only requirement is that the port types are compatible. Using UVMC, we get port type matching for free from the C++ or SystemVerilog compilers. This example uses the SystemC side to be the monitor which can be scaled further involving the UVM environment and scoreboard.

 

 

The packets that are sent from the SV side are checked with the ones that are received from the SC side and are checked for inverted data and addresses. This ensures that there is no loss in packets during the process.

Conversion on the SC Side

Ideally, the process would mirror the transaction types on the SV side. But in some cases, you cannot mirror the types of declaration orders and types. You can choose a nominal way to write our custom converter by allowing the pack and unpack method to occur as is on the SV side and implement a sub-type to the SC converter specialization to convert according to how the bits are received on the SV side. We will define a converter for this packet, then connect an instance of the consumer with an SV-side producer using a blocking transport interface conveying that transaction.

From the SV side, the packet transaction will be packed normally with cmd, addr, data, and extra_int, assuming 3 bytes in the data array. It looks like this:

Ports: The port’s hierarchical name will be registered as a lookup string for matching against other port registrations within both SV and SC. A string match between two registered ports results in those ports being connected. Lookup strings are global across both SC and SV. A lookup string can be anything you wish, if it is unique to other UVMC connections. Just before UVM’s end_of_elaboration phase, UVM Connect will establish the actual cross-language connection. Also, on the SystemC side, when this function is called, you pass in a reference to the TLM instance and an optional lookup string. During elaboration, UVMC will connect the ports whose registered lookup strings match. In the example below, “transport_port_sv_out” is the lookup string and the same look-up string will be used from the sc side.

REQ and RSP: These are used to specify the request and response transaction types for bidirectional TLM-1.0 ports. The default RSP type is the REQ type, so the RSP must be specified only if it is different from REQ. These are the required parameters for SC connections. On the SC side, you do not typically need to specify any type of parameters.

Comparator: The comparator function checks the transaction queues out_q and in_q, and then checks for the size of the bits in the transactions on the SV side. The compare function here checks the data and address received a match.

Packer: On the SC Side, you declare a simple transaction class with different sizes of bits and vectors. Once the transaction in the form of packets is received from the SV side to the SystemC side, the packet is unpacked and packed using the <do_pack> and <do_unpack> functions and sent back to the SV side.

A consumer with a transport port on the SC side is created to receive the packet on the “put” port (sv_out) and send it back to the analysis port (sv_in) on the SV side. The idea here is to check that we do not lose any information after packing and unpacking.

CVRT, CVRT_REQ, CVRT_RST: At this point, a converter is declared for packing/unpacking the rsp and req type for the transport port. Use of a converter policy class for this connection is optional. In SV, you do not need to specify a converter for transaction types that extend uvm_object and implement the <do_pack> and <do_unpack> methods.

Once the transaction has been sent back to the SV side from the SC side, data is checked using a comparator function, do_check as mentioned in sv_main.

Complex example using converters

In the first example, we saw how transactions can be passed by simply connecting the ports of both the SC and SV sides using built-in support for the TLM 1.0 transaction type and a blocking transport. That holds good if the transactions type on both sides is the same. When the types are different (i.e., the types are other than the generic payload), you can define a conversion algorithm for all connections of a transaction type or design a custom-made conversion algorithm for each connection using TLM-2.0 libraries.

Suppose the two components are developed so that both components agree on the content of the transaction, but their transaction definitions are of different types. This condition always occurs between components written in two different languages; they may not be able to share a common transaction definition. For these components to communicate with each other, you need an adapter or converter that can handle the transaction types defined in each language.

The members (properties) of the transaction classes do not need to have the same declaration number, type, and order in both languages. The converter can adapt different transaction definitions and serialize the data at the same time. The following are valid and compatible UVM Connect transaction definitions, assuming a properly coded converter.

Conversion on the SV Side

For this example, we have used the conversion algorithm within the transaction class itself. A transaction in UVM is derived from <uvm_sequence_item>, which defines the virtual methods <do_pack> and <do_unpack> that allow users to implement this conversion functionality. UVMC’s default converter for SV works for these types of transactions. These macros expand into two or more lines of code and are more efficient than using the packer’s API directly. This is the recommended option for SV-based transactions.

We have a transaction class, a packet extended from a packet_base. This class indirectly extends uvm_object. This also defines a generic producer model via include. All transactions and components in the user library must be written out of context; i.e do not assume a UVMC or any other external connection.

In the connect phase, we register the producer’s output port for the UVMC connection using the search string stimulus. The SC side registers its consumers’ port with the same search string. UVMC will match these two strings and complete the cross-language connection (i.e. SV producer’s <out> port will be bound to the SC consumer’s <in> export). Since our package class implements the required <do_pack> and <do_unpack> methods, we can take advantage of UVMC’s standard converter, which delegates these methods.

 

 

The packets that are sent from the SV side are checked with the ones that are received from the SC side and are checked for inverted data and addresses. This ensures that there is no loss in packets during the process.

Conversion on the SC Side

Ideally, the process would mirror the transaction types on the SV side. But in some cases, you cannot mirror the types of declaration orders and types. You can choose a nominal way to write our custom converter by allowing the pack and unpack method to occur as is on the SV side and implement a sub-type to the SC converter specialization to convert according to how the bits are received on the SV side. We will define a converter for this packet, then connect an instance of the consumer with an SV-side producer using a blocking transport interface conveying that transaction.

From the SV side, the packet transaction will be packed normally with cmd, addr, data, and extra_int, assuming 3 bytes in the data array. It looks like this:

It is a simple class “packet” transaction on the SC side. Hence, we can adapt on the SC side as follows:

  • map 32-bit cmd from SV to a single bool in SC
  • map 32-bit addr from SV into two: addr_lo and addr_hi 16-bit values in SC
  • map data byte array data from SV to an integer array in SC

Custom Converter

With the usage of a separate converter class, you are not limited to member-to-member, bit-compatible packing and unpacking. In some cases, like our example here, we cannot use the default converter because it delegates to the transaction pack and unpack methods that the package class user_pkg doesn’t have. When you implement the converter’s <do_pack> and <do_unpack> functions, you will stream members of your transaction to and from the <packer> variable. This is an instance of < uvmc_packer > that inherits from an inner base class.

You can instantiate a generic consumer module in the top-level testbench environment with a TLM-2.0 blocking port. You also register the consumer’s <in> export to have a UVMC connection with a lookup string stimulus. The SV-side will also register its producer’s out port with the same string ‘stimulus’.

On the consumer2.cpp, we can observe that it takes the input transaction from the SV side and then inverts the data and its address before returning it to the SV side. The producer will check that the data and the address has been inverted, and conclude that the transaction packets have been successful and not lost from the SV side and back.

You can then see in the transcript window that the address and the data sent from the SV side are received while the address and the data inverted on the SC side are sent back to the SV side.

Key lessons

UVMC is filled with libraries that are powerful in linking SystemC and SystemVerilog. It bridges both SV and SC language boundaries by providing TLM-1.0 and TLM-2.0 connectivity between components in these two languages.

Many C-program users write tests and can record their tests with SystemC directly into the UVM test bench. The UVMC libraries along with the interface layer and SV UVM components can be used to independently design and communicate without directly referencing each other and can further be integrated into both native and mixed-language environments without modifications, making them reusable.

UVMC also provides a means of directly accessing and controlling UVM simulation via the UVM Command API. The UVMC library is open and is distributed under Apache license. The latest library is available at verificationacademy.com.

This modeling can naturally be scaled up by using this SC model as a scoreboard and can be reused by plugging it into UVM testbenches.

With the increasingly complex designs that are emerging in today’s world, it is safe to say that UVMC has the flexibility to connect different UVM components involving SV and SC models with ease.

Further details

To see the source code used, constructs, and result, please refer to the full paper here:

https://resources.sw.siemens.com/en-US/white-paper-why-not-connect-using-uvm-connect-mixed-language-communication-got-easier

About the author

Vishal Baskar is a Product Engineer in design and verification at Siemens Digital Industries Software.

Comments are closed.

PLATINUM SPONSORS

Synopsys Cadence Design Systems Siemens EDA
View All Sponsors