Verilog Co-Simulation

Introduction

Icarus Verilog is by far the most popular opensource Verilog simulator. It supports the IEEE 1364-2005 standard of the Verilog hardware description language, which happens to be the last standalone IEEE standard for Verilog. IEEE standards committee has since merged the Verilog standard with the SystemVerilog standard (IEEE 1800-2009).

Classic Verilog as defined by the IEEE 1364 standard lacks advanced verification constructs that are necessary to implement Universal Verification Methodology or UVM. Some of the verification features lacking in Verilog are constrained randomisation, object orientism and dynamic scoping. As a result, Icarus Verilog users are compelled to create traditional testbenches in order to test RTL designs coded in Verilog.

Embedded UVM

Embedded UVM intends to provide an opensource solution Verilog and VHDL users. We use the Verilog Procedural Interface (VPI or PLI-2.0) to integrate UVM testbenches seemlessly with Icarus Verilog or for that matter any other Verilog simulator that supports PLI-2.0 standard.

Binding Verilog signals with E-UVM

Embedded UVM provides two different ways to integrate UVM testbenches with Verilog designs. The first method uses HdlSignal construct implemented by the ESDL library. Tou can create a set of HdlSignal's in E-UVM, and individually bind them with actual Verilog signals. Such a binding forms a two way connection, i.e. any change on an Verilog signal is automatically reflected on the corresponding HdlSignal. And when you change the value of an HdlSignal in E-UVM testbench, the value automatically propagates to the bound Verilog signal.

Obviously, an E-UVM testbench that uses this technique needs to run step-locked with Verilog simulation. While this can be accompalished using PLI hooks provided in a Verilog simulator, such fine grained time locking makes a simulation very slow. So let us look at a more sophisticated technique that employs Transaction Level Modelling (TLM).

The TLM technique

Unlike signal binding, where value of each signal is transferred as and when it changes, in TLM technique, a whole transaction is transferred in one go. On top of that a transaction is transferred asynchronously, i.e. the testbench puts a transaction in an asynchronous TLM Fifo and then goes about doing its own work. The Design Under Test (DUT) silently comes and picks the up transaction from the asynchronous Fifo. The DUT does not have to wait for the testbench, and both the testbench and the DUT simulations can go on in parallel on independent threads.

TLM is a hugely popular concept with UVM powered SystemVerilog testbences. A transaction is generally grouped as a class object. But classic Verilog does not implement a class construct. So how can we transfer a trasaction to Verilog?

An established UVM technique for such a TLM transfer is via packing and unpacking of the transaction using byte_pack and related methods. Typically UVM methods can be used to byte-pack the elements of a transaction into a byte buffer. This buffer can then be transferred to the DUT and unpacked there into individual signals. But any such packing and unpacking can make a simulation run slower and also puts the verification engineer to inconvenience, taking toll on her productivity.

E-UVM avoids all this. Let us investigate how.

VPI drivers in E-UVM

E-UVM implements special UVM components for integrating a testbench with Verilog simulation. On the driver side, this component is called uvm_vpi_driver.

The idea is to split the driver into two parts and implement the BFM in Verilog.
When the UVM driver receives a transaction from sequencer, the user simply needs to push the transaction into an asynchronous Fifo particularly instantiated for this purpose in the base class. On the Verilog side, a transaction can be simply fetched into a set of signals by calling $<foo>_try_next_item. For this to happen, we need to specify somewhere a way to map the transaction elements to the corresponding signals on the Verilog side. This is how it is done.

On the E-UVM transaction side, we need to override a method named do_vpi_put as illustrated below:

  // To be coded as a method in E-UVM transaction class
  override void do_vpi_put(uvm_vpi_iter iter) {
    iter.put_values(addr, strb, data, type);
  }

In the BFM code in Verilog, the variables (these are elements of the transaction class) addr, strb, data, and type can now be accessed as:

  // Verilog Driver BFM
  $avl_try_next_item(addr, strb, wdata, flag)

Note that put_values is a variadic method and can take as many arguments as you desire to push. The same number of variables would be pull-able on the BFM side.

VPI monitors in E-UVM

Just like VPI driver, for the monitor side, E-UVM implements a VPI monitor. Again a VPI monitor is split into two parts with the BFM getting implemented in Verilog. The BFM can now push the desired transaction elements to the VPI monitor implemented in E-UVM via an asynchronous Fifo.

  // Verilog Monitor BFM
  $avl_put(addr, strb, rdata, flag)
  // To be coded as a method in E-UVM transaction class
  override void do_vpi_get(uvm_vpi_iter iter) {
    iter.get_values(addr, strb, data, type);
  }