Global training solutions for engineers creating the world's electronics

The Easier UVM Code Generator Tutorial

Part 1: Getting Started

Using the Code Generator on a Project

You can use the code generator in several different ways:

  • You can use the code generator simply as a learning aid by generating and running working examples of UVM code that are based on your particular DUT. You can define template files that represent the interfaces of your specific DUT, run the code generator, then provide driver and monitor code to implement the protocols and thus convince yourself and your management that getting a UVM environment up-and-running is not so difficult!

  • You can use the code generator to create a framework for your production code, then throw away the code generator templates and extend and maintain the production code without re-running the code generator ever again. Although the code generator is only used in the initial stages of the project and is then discarded, you still have the benefit that all the generated code starts out with a consistent structure.

  • You can use the code generator to create a framework for your production code, then use a combination of include files and script files to extend and modify the functionality of the generated code according to the specific needs of the project. If you implement this approach carefully, you should be able to re-run the code generator at any stage to reflect any changes you make to the template files, but this does depend on you not making any modifications to the generated code that cannot be reproduced at will using include files or scripts, which might not be trivial to do. Using the code generator for as long as possible into a project means you can continue to take advantage of the code generator to create the boilerplate code that connects everything together.

 

Here we have a series of tutorials that will build up your knowledge of the code generator step-by-step. Amongst many other things, you will learn how to include user-defined code within the generated code.

Before you start, make sure you have followed the installation instructions to install the necessary Perl modules. If you don't, you will see the following error message when you run the code generator:

Can't locate File/Copy/Recursive.pm

 

The Tutorial Example

We will start with the simplest example possible. To run the code generator at all you will need an absolute minimum of three template or control files: an interface template file, a common template file, and a pinlist file. We also have two very short files to define the DUT itself, which is just a stub in this minimal example. We will also add two very short script files, one to run the generator and a second to run simulation, simply to avoid having to remember the command lines and their arguments.

clkndata.tpl    Interface template file
common.tpl      Common template file
pinlist         Pinlist file
mydut/mydut.sv  SystemVerilog source file for the DUT
gen             Script to run the code generator
run             Script to run the simulator

You can download the files for this example (the directory named ./minimal), or because they are so short, you can simply copy-and-paste the contents from this webpage.

Interface template file

The first template file we need is the interface template file. This file characterizes one DUT interface, specifying such things as the variables in a UVM transaction and the variables in the SystemVerilog interface. The code generator requires at least one interface template file that must specify at least one variable in the associated transaction class and interface. For this minimal example we have an interface named clkndata that transfers one byte of data to the DUT on the active edge of a clock signal:

Filename clkndata.tpl

agent_name = clkndata
trans_item = data_tx
trans_var  = rand byte data;

if_port  = logic clk;
if_port  = byte data;
if_clock = clk

The agent_name clkndata will be used as the root for many of the filenames and SystemVerilog names generated, such as clkndata_agent, clkndata_if, clkndata_pkg, and clkndata_config. Note that the template filename itself is the agent name with the file extension .tpl. In practice you would have one such template file for each DUT interface, each containing a unique agent name.

The trans_item data_tx will be used as the class name of the class that extends uvm_sequence_item to define the transaction used within the generated agent, that is, the transaction sent from the sequencer to the driver and the transaction sent out through the analysis port of the monitor.

The items after the "=" on the trans_var line, that is rand byte data;, will be included in the generated transaction class. Note the ";" at the end of the line: this is important as it will literally be copied into the generated code. Also do remember to include the rand keyword if you want the variable randomized. There will typically be multiple trans_var lines, but we will keep it to one for the sake of showing a minimal example.

The items after the "=" on the if_port lines, that is logic clk; and byte data;, will be included in the generated SystemVerilog interface. Again, note the ";" at the end of the lines. Even for a minimal example we need a clock as well as a data signal.

Finally, if_clock picks out one of the interface variables as the clock signal. The name given as the if_clock must be one of the if_ports in this same template file. The order of the lines in the template file is unimportant, though it will make more sense to the human reader if you order the lines as shown here.

(See the Reference Guide for a full list of the settings that can be given in the interface template files.)

Common template file

The second template file we need is the common template file, which must be named common.tpl. The code generator always requires a common.tpl file, which specifies some common settings. There is only one mandatory setting, namely dut_top, but there are a large number of optional settings that give you a lot of control over the generated code. Many of the settings have sensible default values, so you can ignore these settings as you get started.

Filename common.tpl

dut_top = mydut

dut_top specifies the name of the top-level SystemVerilog module of the DUT, which is mydut in this case. The DUT source files must be placed in a directory named ./dut, or you can choose a different directory name using the common template file setting dut_source_path, or you can list the locations and compilation order of the files manually in a single file named files.f.

Here is the DUT file used in this minimal example:

Filename mydut/mydut.sv

module mydut (input clk, input byte data);
  always @(posedge clk)
    $display("mydut data = %h", data);
endmodule

(See the Reference Guide for a full list of the settings that can be given in the common template file.)

Pinlist file

The third template/control file is the pinlist file, which specifies the connections between the pins (ports) of the DUT (as specified in the SystemVerilog source code of the DUT) and the variables of the DUT interfaces (as specified in the interface template files). You can change the name of this file using the common template file setting dut_plist.

Filename pinlist

!clkndata_if
clk  clk
data data

The pinlist file is divided into sections, one section per DUT interface, each section starting with an exclamation mark "!" followed by the name of the interface, which will always be the agent name plus the suffix _if. In this minimal example there is only one agent clkndata, so the corresponding interface name is clkndata_if. Each of the remaining lines contain the name of a port of the top-level module of the DUT followed by the name of a variable in the corresponding DUT interface, e.g. data data.

You can connect DUT ports that are not part of any specific interface using an unnamed section in the pinlist file, for example:

!
clock_port global_clock_var
reset_port global_reset_var

You can also pass SystemVerilog parameters to the DUT in the pinlist file.

(See the Reference Guide for a full definition of the pinlist file.)

Run the code generator

Having provided the three template/control files (clkndata.tpl, common.tpl, pinlist) and the DUT file (mydut.sv), we can now run the code generator for this minimal example. We will put the command line in a one-line script file:

Filename gen

perl ../easier_uvm_gen.pl clkndata.tpl

The perl script easier_uvm_gen.pl can be anywhere: we are just locating it from the script using a relative path. The names of any interface template files are included on the command line. The name of the common template file defaults to common.tpl (but can be set from the command line using the -m switch.)

(See the Reference Guide for a full list of command line flags.)

We can now run the code generator:

/home/user/minimal > gen

Easier UVM Generator: version 2015-11-16

Parsing Templates ...

Reading[1]: clkndata.tpl
Writing code to files
generating testbench
writing simulator script to generated_tb/sim directory
Code Generation complete

The generated structure looks like this. The best way to figure out exactly what the code generator does is to run it yourself!

top_tb (module)
 ↳ top_th (module instance)
   ↳ clkndata_if (interface instance)
     mydut (module instance)
   top_config (class uvm_object)

 ↳ top_test (object, class uvm_test)
    ↳ top_env (uvm_env)
       ↳ clkndata_config (uvm_object)
         clkndata_agent (uvm_agent)
          ↳ clkndata_sequencer
            clkndata_driver (uvm_driver)
            clkndata_monitor (uvm_monitor)
         clkndata_coverage (uvm_subscriber)

       ↳ top_default_seq (created in run_phase, class uvm_sequence)
          ↳ clkndata_default_seq (uvm_sequence)
             ↳ data_tx (uvm_sequence_item)
Run the simulation

Having run the code generator, we can now run a simulation immediately, out-of-the-box. Again, we will put the command lines into a script file:

Filename runius

cd generated_tb/sim
compile_ius.sh

Filename runquesta

cd generated_tb/sim
vsim -c -do "do compile_questa.do; run -all"

Filename runvcs

cd generated_tb/sim
compile_vcs.sh

Filename runriviera

cd generated_tb/sim
vsimsa -do compile_riviera.do

generated_tb is the default name for the directory containing the generated files, but you can change that by adding a project = line to the common template file. The ./sim directory contains scripts to run the Cadence, Mentor, Synopsys, and Aldec simulators (correct when this tutorial was written, but tools keep changing, of course). (See the Reference Guide for a fuller description of compilation and simulation.)

The generated verification environment will run a sequence that generates just a single transaction, and so somewhere in the middle of your simulation log you should see a UVM_INFO message from clkndata_driver that prints out the value of the data field in the transaction, something like: The generated verification environment will run a sequence that generates just a single transaction, and so somewhere in the middle of your simulation log you should see a UVM_INFO message from clkndata_driver that prints out the value of the data field in the transaction, something like:

# UVM_INFO ../tb/clkndata/sv/clkndata_driver.sv(47) @ 0: uvm_test_top.m_env.m_clkndata_agent.m_driver
 [clkndata_driver] req item
# vseq.seq.req
#   data =      f4

However, you will not see this data value being printed out from the DUT (from the line $display("mydut data = %h", data);) because we have not provided an implementation of the driver, so although the driver is receiving a transaction from the sequencer, it is not actually wiggling the pins of the DUT interface.

Implement the driver

In order to wiggle some pins in the DUT interface, we need to complete the implementation of the driver. We can do this by providing the implementation of a task that is already being called by the generated driver code, and then use the template files to instruct the code generator to include our task. Using this technique, we will not need to repeat the edit every time we re-run the code generator: Our task gets picked up automatically.

Filename include/clkndata_do_drive.sv

task clkndata_driver::do_drive();
  vif.data <= req.data;
  @(posedge vif.clk);
endtask

We have to be careful to use the correct naming conventions within this task. clkndata is the agent name from the interface template file, but the suffix _driver, the task name do_drive, the virtual interface name vif, and the transaction variable name req are all fixed by the code generator, so we have to be sure to use exactly these names. The task would do whatever pin wiggling is necessary to drive a transaction onto the DUT interface, synchronizing with any timing signals and consuming time ( @(posedge vif.clk) ) as necessary.

To have the code generator include our task, we need to add the name of the specific file to be included in the corresponding interface template file. We can include files to implement the driver, the monitor, and the coverage collection subscriber for each agent, thus avoiding the need to manually hack the code each time it is regenerated. Any include files must be placed in a directory named ./include, or you can choose a different directory name using the common template file setting inc_path. (See the Reference Guide for a fuller description of extending the generated code.)

Filename clkndata.tpl

...
driver_inc = clkndata_do_drive.sv
...

With the driver implementation in place, we can now simply re-run the code generator and the simulator using the same scripts as before. The simulator log now shows a little more action:

# mydata data = f4

This message demonstrates that our dummy DUT is now receiving a transaction through the clkndata interface as a result of the driver wiggling pins. However, the verification environment is still only sending one single transaction to the DUT.

Send more than one transaction

The code generator generates a default sequence for the agent that sends a single transaction to the driver, and also a top-level virtual sequence that contains a loop that starts the child sequence a number of times as given by the m_seq_count variable. This variable has a default value of 1, so by default only one transaction is generated, but this value can easily be changed using a setting in the common template file.

Filename common.tpl

dut_top = mydut
top_default_seq_count = 10

The only effect of this top_default_seq_count setting is to change a loop counter in the default top-level virtual sequence, which can be useful when getting simple examples up-and-running. As you extend the generated code you would eventually replace this default top-level virtual sequence with your own user-defined sequence, making the top_default_seq_count setting irrelevant.

Now simply run-run the scripts, and you will see the DUT receiving 10 transactions instead of 1.

 

Next