Friday 16 November 2018

Developing & Delivering KnowHow


The Easier UVM Code Generator Tutorial Part 2:
Adding User-Defined Code



In the Getting Started tutorial we used the generated code (almost) out-of-the-box to send a sequence of random transactions to the DUT. The only modification we needed to make to the generated code was to implement the driver to wiggle the pins of the DUT. In this tutorial we will also implement the monitor and coverage collector components so that we can collect functional coverage information during simulation.

You can download the files for this example. The files are in the directory named ./minimal_plus.

Implement the Monitor

In order to do any kind of checking or coverage collection in the verification environment we first need to complete the implementation of the monitor. Similar to the driver in the Getting Started Tutorial, we can implement the monitor by providing the implementation of a task that is already being called by the generated monitor code and then use the template files to instruct the code generator to include our task.

Filename include/clkndata_do_mon.sv
task clkndata_monitor::do_mon();
  forever @(posedge vif.clk)
  begin
    m_trans = data_tx::type_id::create("m_trans");
    m_trans.data = vif.data;
    analysis_port.write(m_trans);
  end
endtask

As for the driver, 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 _monitor, the task name do_mon, the virtual interface name vif, the tranasaction variable name m_trans, and the analysis port name analysis_port are all fixed by the code generator, so we have to be sure to use exactly these names. The task should monitor the pin wiggling on the DUT interface, synchronizing with any timing signals and consuming time ( @(posedge vif.clk) ) as necessary, and should then assemble a transaction m_trans which it should send out through the analysis port analysis_port, as shown above.

And as for the driver, you will need to add the name of the specific file to be included in the corresponding interface template file. (See the Reference Guide for a fuller description of extending the generated code.)

Filename clkndata.tpl
...
driver_inc = clkndata_do_drive.sv
monitor_inc = clkndata_do_mon.sv
...

Implement a Covergroup

The code generator will generate a subscriber component, which it connects to the analysis port of the monitor. By default, this subscriber will sample the values of the fields of any incoming analysis transactions using a covergroup. The automatically generated code looks like this:

Filename clkndata_coverage.sv
class clkndata_coverage extends uvm_subscriber #(data_tx);

  `uvm_component_utils(clkndata_coverage)

  bit m_is_covered;

  data_tx m_item;

  covergroup m_cov;
    option.per_instance = 1;
    cp_data: coverpoint m_item.data;
  endgroup

  extern function new(string name, uvm_component_parent);
  extern function void write(input data_tx t);

endclass


function clkndata_coverage::new(string name, uvm_component_parent);
  super.new(name, parent);
  m_is_covered = 0;
  m_cov = new;
endfunction


function void clkndata_coverage::write(input data_tx t);
  m_item = t;
  m_cov.sample();
  if (m_cov.get_inst_coverage() >= 100) m_is_covered = 1;
endfunction

...

In order to complete the coverage model, you can provide a set of user-defined coverpoints and bins in an include file, for example:

Filename include/clkndata_cover_inc.sv
covergroup m_cov;
  option.per_instance = 1;
  cp_data: coverpoint m_item.data {
    bins zero = {0};
    bins one  = {1};
    bins negative = { [-128:-1] };
    bins positive = { [1:127] };
    option.at_least = 16;
  }
endgroup

Once again, you will have to be careful to use the correct naming conventions. The covergroup must be named m_cov, and the name of the variable that refers to the incoming analysis transaction must be m_item.

Once again, you will need to add the name of the specific file to be included in the corresponding interface template file.

Filename clkndata.tpl
...
driver_inc = clkndata_do_drive.sv
monitor_inc = clkndata_do_mon.sv
agent_cover_inc = clkndata_cover_inc.sv
...

The generated subscriber component will now include the covergroup you have just provided:

Filename clkndata_coverage.sv
class clkndata_coverage extends uvm_subscriber #(data_tx);
  ...
  data_tx m_item;

  `include "clkndata_cover_inc.sv"
  ...
  extern function void write(input data_tx t);
  ...
endclass

Further Extending the Generated Code

The above include file clkndata_cover_inc.sv only contains a covergroup. The generated code instantiates that covergroup and contains a write method to sample the covergroup. But what if you need your subscriber to take some other action besides or instead of sampling a covergroup? The answer is that, instead of just including a covergroup, you can pull in all the contents of the class from a set of include files. You also need to suppress the automatic generation of the new and write methods if you are going to supply them yourself. To do so, you would change the interface template file as follows:

Filename clkndata.tpl
...
#agent_cover_inc = clkndata_cover_inc.sv

agent_cover_inc_inside_class = clkndata_cover_inc_inside.sv
agent_cover_inc_after_class  = clkndata_cover_inc_after.sv
agent_cover_generate_methods_inside_class = no
agent_cover_generate_methods_after_class  = no
...

The generated subscriber component would now look like this, leaving you to define the actual content of the class in the include files:

class clkndata_coverage extends uvm_subscriber #(data_tx);

  `uvm_component(clkndata_coverage)

  `include "clkndata_cover_inc_inside.sv"

endclass

`include "clkndata_cover_inc_after.sv"

You might then provide the following two include files:

Filename include/clkndata_cover_inc_inside.sv
covergroup m_cov;
  option.per_instance = 1;
  cp_data: coverpoint m_item.data {
    bins zero = {0};
    bins one  = {1};
    bins negative = { [-128:-1] };
    bins positive = { [1:127] };
    option.at_least = 16;
  }
endgroup

extern function new(string name, uvm_component_parent);
extern function void write(input data_tx t);
Filename include/clkndata_cover_inc_after.sv
function clkndata_coverage::new(string name, uvm_component_parent);
  super.new(name, parent);
  m_cov = new;
endfunction

function void clkndata_coverage::write(input data_tx t);
  m_item = t;
  m_cov.sample();
endfunction: write

You now have the flexibility to provide whatever code you like within these two include files. In this example we have used extern function definitions for new and write, but this is not necessary. We could have defined these function inline in the class definition.

Alternatively, instead of the code generator writing out `include directives, you can have the user-defined code inserted inline in the generated code as follows:

Filename clkndata.tpl
...
agent_cover_inc_inside_class = clkndata_cover_inc_inside.sv  inline
agent_cover_inc_after_class  = clkndata_cover_inc_after.sv   inline
...

Just as we have complete flexibility in defining the contents of the subscriber class using include files, so we can use include files to define the contents of the driver and the monitor component classes and the test harness module. See the Reference Guide for a fuller description of extending the generated code.

It would now be possible to re-generate the code and run simulation using the minimal stimulus from the Getting Started tutorial, but instead, we are going to add some user-defined stimulus in an attempt to improve functional coverage. This will show how to have the generated code execute a user-defined sequence.

Override a Sequence

As we saw in the Getting Started Tutorial, the generated code contains a top-level virtual sequence that starts a default sequence running on each agent, where that default sequence generates a single random transaction:

Filename top_seq_lib.sv
...
task top_default_seq::body();
  super.body();
  ...
  clkndata_default_seq seq;
  seq = clkndata_default_seq::type_id::create("seq");
  seq.randomize();
  seq.start(m_clkndata_agent.m_sequencer, this);
endtask
...

You can add a user-defined sequence by extending any default sequence that is created and started by the generator, e.g.

Filename my_clkndata_seq.sv
class my_clkndata_seq extends clkndata_default_seq;
  ...
  task body();
    ...
    for (int i = 0; i < 16; i++)
    begin
      req = data_tx::type_id::create("req");
      start_item(req);
      if ( !req.randomize() with { data == i; })
        ...
      finish_item(req);
    end
  endtask
endclass

You then need to identify the file containing the sequence in the interface template file, and also add a line that will generate a factory override to start your user-defined sequence in place of the default sequence:

Filename clkndata.tpl
...
agent_cover_inc = clkndata_cover_inc.sv
agent_seq_inc = myclkndata_seq.sv

agent_factory_set = clkndata_default_seq my_clkndata_seq
...

The code generator will then add the following factory override to the top-level test:

Filename top_test.sv
function void top_test::build_phase(uvm_phase phase);
  ...
  clkndata_default_seq::type_id::set_type_override(my_clkndata_seq::get_type());
  ...
endfunction

You are now ready to run the simulation and inspect the functional coverage reports. In order to make it easier to interpret the simulation log, you might want to adjust the verbosity threshold for UVM_INFO messages to suppress all superfluous messages. This can be done by adjusting the simulation script. You might also want to change the simulator command line flags to allow functional coverage to be viewed interactively. Here we show a run script for Questasim, but the same would apply for other simulators:

Filename gui
cd generated_tb/sim;
sed 's/UVM_VERBOSITY=UVM_FULL/UVM_VERBOSITY=UVM_NONE/' <compile_questa.do >result
mv -f result compile_questa.do
vsim -novopt -gui -do "do compile_questa.do; run -all"



Links

Easier UVM Coding Guidelines
Introduction to the Easier UVM Coding Guidelines
Summary of the Easier UVM Coding Guidelines
Detailed Explanation of the Easier UVM Coding Guidelines
Easier UVM Glossary
Easier UVM Coding Guidelines - Download

Easier UVM - Deeper Explanations
Coverage-Driven Verification Methodology
Requests, Responses, Layered Protocols and Layered Agents
How to Access a Parameterized SystemVerilog Interface from UVM

Easier UVM Code Generator
Easier UVM Code Generator - Download
Easier UVM Code Generator - Tutorial Part 1: Getting Started
Easier UVM Code Generator - Tutorial Part 2: Adding User-Defined Code
Easier UVM Code Generator - Tutorial Part 3: Adding the Register Layer
Easier UVM Code Generator - Tutorial Part 4: Hierarchical Verification Environments
Easier UVM Code Generator - Tutorial Part 5: Split Transactors
Easier UVM Code Generator - Frequently Asked Questions (FAQ)
Easier UVM Code Generator - Reference Guide

Easier UVM Video Tutorial
Introducing Easier UVM
Easier UVM - The Big Picture
Key Concepts of the Easier UVM Code Generator
Running Easier UVM in EDA Playground
Easier UVM - Components and Phases
Easier UVM - Configuration
TLM Connections in UVM
Easier UVM - Transaction Classes
Easier UVM - Sequences
Easier UVM - Tests
Easier UVM - Reporting
Easier UVM - Register Layer
Easier UVM - Parameterized Interfaces
Easier UVM - Scoreboards
The Finer Points of UVM Sequences (Recorded Webinar)
UVM Run-Time Phasing (Recorded Webinar)

A YouTube playlist with all the above videos and more

Easier UVM Paper and Poster
Easier UVM - Coding Guidelines and Code Generation - as presented at DVCon 2014

Easier UVM Q&A Forum
Easier UVM Google Group

Easier UVM Examples Ready-to-Run on EDA Playground
Minimal example with driver
Minimal example with coverage in a subscriber as well as driver and monitor.
Minimal example with register sequence and register block
Example with four interfaces/agents, two of which use a register model.
Minimal example with dual-top modules and split transactors
Minimal example showing a UVM sequence getting information from the config database
Minimal example showing features of objections and the command line processor
Minimal example showing the reporting features of UVM.
Example that drops an objection when coverage exceeds some threshold
Example that sends a response transaction from the driver back to the uvm_reg_adapter
Example that uses a frontdoor sequence to pass a response object back to the register sequence that called read/write
Example of a parameterized interface generated from an Easier UVM interface template file
Example that pulls in a user-defined parameterized interface
Example of a reference model with the Syosil scoreboard

Back to the full list of UVM Resources

Privacy Policy Site Map Contact Us