VMM Using SystemVerilog

To get most of Hardware Verification languages like system Verilog a Methodology is must.

VMM is Verification Methodology Manual using System Verilog defined by Synopsys and ARM

The VMM for SystemVerilog enables its users to build a scalable, predictable and reusable environment enabling users to take full advantage of assertions, reusability, testbench automation, coverage, formal analysis, and other advanced technologies to help solve their RTL and system-level verification problems. Using VMM one can quickly build a layered test bench and each component of VMM can reused. It also gives a consistent look and feel throghout the project .

The VMM for SystemVerilog enables all SoC and IP projects to establish an effective, efficient and predictable verification process that is based upon the experience of leading industry experts from ARM, Synopsys, and their customers.

The broad scope of the VMM for SystemVerilog allows it to cover many verification techniques and show how they can be most effectively used together. The right combination of advanced techniques can improve verification thoroughness, increase verification productivity, accelerate the development process and, over the course of a project, consume fewer resources than brute-force approaches. The VMM for SystemVerilog can both enhance existing approaches and form the basis for a comprehensive verification environment that takes full advantage of constrained random generation, functional coverage and assertions.

Let us start with VMM base classes

The following 8 base classes are available in VMM

vmm_notify : Base class for VMM notification
vmm_log : Base class for VMM messaging
vmm_data : Base class for VMM data and transactions
vmm_channel : Base class for VMM channels and completion models
vmm_xactor : Base class for VMM Transactors
vmm_env : Base class for VMM verification Environment
vmm_atomic_gen : Base class for VMM Atomic Generator
vmm_scenario_gen : Base class for VMM Scenario Generator

The VMM components used inside the Test bench

Generator: Generates the transactions either individually or in steams
Transactor: Takes High level transactions such as single Ethernet Packet transaction in to multiple Ethernet Packet transaction.
Driver : Controls the signals to the DUT it6 executes a single command such as bus read or write or driving Ethernet packet into the DUT
Monitor: Bundles the signals from the DUT into transactions.
Checker: Compares the output of the DUT, as seen by the Monitor, to the Scoreboard, using a predictor or reference model.
Assertions: Constantly check the DUT for correctness.
Scoreboard : Stores the transactions from transactors for later comparison

Layered VMM Testbench



VMM Messaging – overview

A testbench produces messages of many types and severities

The vmm_log class allows to control


All messages are sent to standard output, i.e. displayed on the screen and sent to the simulation log file, just like $display.

Message Type

In VMM, every message has a type and a severity. One may want to print a message to


The message type tells which of these is happening


Severity – describes message importance

Severity levels and the type in parenthesis

Fatal: Functional correctness definitely compromised (Failure)
Example: Testbench failure
Error: Functional correctness may be compromised (Failure)
Example: Actual model results don’t match expected results
Warning: Functional correctness not compromised (Failure or Timing).
Normal: Regular, expected message.
Trace: High-level simulation execution trace message (Debug)
Example: "Executing transaction"
Debug: Detailed simulation execution trace message (Debug)
Verbose: Very detailed simulation execution trace message (Debug)
Example: "Sending byte #5 (0x5A)"

vmm_log class


Declaration and instantiations

The vmm_log usually instantiated inside a testbench object such as generator, checker or in a data object.

Vmm_log log = new (“Name”, “instance”)

The name string is the name of the class that contains the log, such as “USB Host”, or “MAC Frame”.

The instance string is the name of this instance of the object such as “Generator 1”, or “Left side”. If there is only a single instance, just use the string “class”.

Log Object Macros

The easiest way to use a vmm_log object is with the macros

`vmm_fatal(vmm_log log, string msg)
`vmm_error(vmm_log log, string msg)
`vmm_warning(vmm_log log, string msg)
`vmm_note(vmm_log log, string msg)
`vmm_trace(vmm_log log, string msg)
`vmm_debug(vmm_log log, string msg)
`vmm_verbose(vmm_log log, string msg)

Examples using Macros

Two examples of using the above message macros.

The first displays a simple string.

The second needs to print variable arguments, so it uses $psprintf, which returns a formatted string.

`vmm_verbose(log, "Checking rcvd byte");
if (got != expect) begin
`vmm_error (log, $psprintf("Bad data: 0x%h vs. 0x%h",got, expect));
End

Note that these macros expand to several lines, so surround them with begin-end when used in an if-statement.

Message Handling

The messaging class handles each message according to its severity level.

The default is that


Use the method vmm_log::modify () to change how messages are handled.

Controlling Verbosity

By default, only messages with a severity of NORMAL (vmm_fatal, vmm_error, vmm_warning, vmm_note) or higher are displayed.

This can be controlled in two ways

Using +vmm_log_default

The command line switch +vmm_log_default=DEBUG will enable printing of all messages with

The severity level DEBUG and higher.

Using set_verbosity()

The method vmm_log::set_verbosity() allows to set the level of printing on the fly.

The following code sets the level to DEBUG for any vmm_log object with “Drv” in its name:

log.set_verbosity(log::DEBUG_SEV, "/Drv/", "/./", );

This call overrides the +vmm_log_default switch, and only applies to current vmm_log objects, not any created afterwards.

Create complex, multi-line messages using the vmm_log methods start_msg(), text (), and end_msg().

Change the formatting of vmm_log by extending the vmm_log_format class and register an instance with the vmm_log::set_format method.

VERIFICATION ENVIRONMENT

vmm_env:

Overview:

Testbench goes through many phases of execution, from initialization, simulation, and cleanup.

The class vmm_env helps to manage these steps and ensures that all execute in the proper order.

The new() method should only initialize values, and should never have any side effects such as spawning threads or consuming time.

If a testbench object starts running as soon as new () is called, one will not be able to delay its start, or synchronize it with other testbench operations.

The vmm_env class divides a simulation into the following nine steps, with corresponding methods:

gen_cfg() – Randomize test configuration descriptor
build() – Allocate and connect test environment components.
reset_dut() – Reset the DUT
cfg_dut() – Download test configuration into the DUT
start() – Start components
wait_for_end() – End of test detection
stop() – Stop data generators and wait for DUT to drain
cleanup() – Check recorded statistics and sweep for lost data
report() – Print final report

Execution Sequence in vmm_env class

Base class run () Dut specific extension




Simplest Example

The top method run () keeps track of which steps have executed When run () is called, it runs the remaining ones.

For example, the following program runs all nine steps automatically

program test;
initial begin
verif_env env;
env = new(...);
env.run();
end
endprogram

The class verif_env extends vmm_env. When run() is called and it will call all the steps which have not yet been run.

Basic Example

The next example runs the first step, makes a modification to the configuration, and then completes the test.

program test;
initial begin
verif_env env;
env = new(...);
env.gen_cfg(); // Create rand config
env.rand_cfg.n_frames = 1; // Only run for 1 frame
env.run(); // Run the other steps
end
endprogram

Automatic Sequencing

The run() task is not the only method that executes the steps. As shown in the following example, if build() is called without calling gen_cfg(), the build() method will automatically execute the previous step

program test;
initial begin
verif_env env;
env = new();
env.build(); // Config and build
begin
my_eth_fr my_fr;
my_fr = new(); // Use my own frame
env.src[0].rand_fr = my_fr; // Use to build more
end
env.run();
end
endprogram

Using Vmm_env


`include “vmm.sv”
class verif_env extends vmm_env;
my_cfg cfg;
my_gen gen[4];
my_drv drv[4];
my_mon mon[4];
virtual function void gen_cfg();
super.gen_cfg();
// rest of gen_cfg method
Endfunction
virtual function void build();
super.build();
// rest of build method
Endfunction
Endclass

DATA AND TRANSACTIONS

Traditional Approach Vs OOP Approach

In traditional testbench the transactions are implemented using procedures one per transaction.

This caused the following problems:


Instead, model transactions as objects.

Their data values exist in a transaction class that can be randomized, copied, packed, and unpacked.

The code that actually executes the transactions resides in the Driver.

Transaction Coding Guidelines


Transactions Vs Transactors

The transaction class contains both physical values that are sent to the DUT (address, data, etc.) and meta-data that has extra information about the transaction, such as a “kind” field.

Even though this might be encoded in the physical values, put it into a separate field that can be easily accessed and can be randomized.

class alu_xactn extends vmm_data;
rand reg [7:0] data_in1 ;
rand reg [7:0] data_in2 ;
rand reg [2:0] select ;
rand bit reset_N ;
endclass

The Transactor contains the code to send the transaction to the next testbench level.

ID fields

Every transaction has three integer ID fields uniquely identifying it.


class vmm_data;
integer stream_id;
integer scenario_id;
integer object_id;
...
endclass

In the following example, a constraint block uses the stream_id.

Constraints - Guidelines

Every transaction should have one or more constraint blocks for the “must-obey” constraints that are never turned off or overridden.

For example, they would make sure an integer field is never negative or that a length field is never 0.


Methods used in VMM transaction class

The vmm_data class defines a set of virtual methods for manipulating its properties.

Make own methods when extending vmm_data.

display() & psdisplay()

These methods display the contents of the class, either to the screen or to string.

Virtual task display (string prefix);

Virtual function string psdisplay (string prefix) ;

allocate()

This method allocates a vmm_data object and initializes required fields.

This is a virtual method, unlike new() so the correct method is called regardless of the handle Type.

Virtual function vmm_data allocate()

copy()

This method makes a copy of an existing transaction. It has an optional “to” field so one can copy to a previously allocated object.

Note that this method returns a vmm_data type, so use $cast () with it.

virtual function vmm_data my_data::copy(vmm_data to = null);
my_data cpy;
// Copying to a new instance?
if (to == null)
cpy = new();
else
if (! $cast(cpy, to, CHECK)) begin
`vmm_fatal(this.log, "Attempting to copy to a non my_data instance");
return;
end
// Copy ID’s and any other properties
cpy.stream_id = this.stream_id;
cpy.scenario_id = this.scenario_id;
cpy.object_id = this.object_id;
// Assign the copy to the return handle
copy = cpy;
endfunction

compare()

This method compares two objects and reports the difference.

Virtual function bit compare (to, diff, kind);

The current object is compared with “to” using type “kind”.

The method returns 1 if the two objects are the same, 0 if not.

The diff string gives a description of the difference.

The following three methods are used for converting between the physical fields of an object and an array of bytes

byte_size()

virtual function integer byte_size (kind);

The method byte_size tells how many bytes are needed to pack an object of this kind.

byte_pack();

virtual function integer byte_pack (bytes,offset,kind);

The method byte_pack packs the object of type kind into a dynamic array of bytes.

byte_unpack();

Virtual function integer byte_unpack (bytes,offset,kind);

The method byte_unpack unpacks the data from the dynamic array of bytes.

The offset tells the methods where to start in the byte array.

Notification - Overview

VMM provides an event notification class that allows notifying when an event notification is indicated, and includes data.

These notifiers are based on integer identifiers that hold a symbolic value.

The following code creates three notification identifiers associated with the alu_driver class:

class alu_driver extends vmm_xactor;
static integer TR_STARTED;
static integer TR_ABORTED;
static integer TR_SUCCESS;
endclass

Configuring a Notifier

Configure a notifier before using it.

A notifier can be ON_OFF, ONE_SHOT, or BLAST.

The following example calls the configure () method in the notify object (preinstantiated in vmm_xactor), which returns an integer value.

class alu_driver extends vmm_xactor;
function new(...);
this.TR_STARTED = this.notify.configure(*, vmm_notify::ON_OFF);
endtask
endclass

Channels and completion models:

Channels


Channels Vs Mailboxes


Definition and Creation of Channel

class alu_xactn extends vmm_data;
...
Endclass
// macro automatically creates new data type by
// appending "_channel" to data_type_name
`vmm_channel(alu_xactn); // alu_xactn_channel declaration
program test;
initial begin
alu_xactn_channel ch;
ch = new(“ALU channel”, “class”);
end
endprogram

Communication using Channel

// Producer
forever begin
alu_xactn tr = new();
ch.put(tr);
end
and:
// Consumer
forever begin
alu_xactn tr = ch.get();
...
end

Allocate object every time

Just like a SystemVerilog mailbox, the channel contains handles to objects, not the object themselves.

One can modify an object after it has been put in the channel, leading to a common mistake.

// Producer
Alu_xactn_channel ch = new(“ ALU channel”, “class”);
Alu_xactn tr;
tr = new();
while (...) begin
void = tr.randomize();
ch.put(tr);
end

This code only allocates a single cell. It then repeatedly randomizes this cell and puts it in the channel.

The result is that the channel will contain many references to the same object.

The solution is to allocate a new cell every time through the loop.

while (...) begin
tr = new();
void = tr.randomize();
ch.put(tr);
end

Using channels to connect blocks

Just like a SystemVerilog mailbox, the channel contains handles to objects, not the object themselves.

One can modify an object after it has been put in the channel, leading to a common mistake.

// Producer alu_xactn_channel ch = new(“ALU channel”, “class”);
alu_xactn tr;
tr = new();
while (...) begin
void = tr.randomize();
ch.put(tr);
end

Transaction Completion


// Consumer forever begin
tr = ch.peek(); // Read the cell
case (tr.kind) {
... // Process the cell
}
void = ch.get(); // Done, wake up producer
end

Atomic Generator

VMM approach to testbench


These results in

less code (each test is smaller),

more randomness (all unspecified behavior is random) and

More checking (extra randomness broadens the stimulus).

Typical Generator

class alu_gen extends vmm_xactor;
...task main();
forever begin
alu_xactn tr = new();
void = tr.randomize();
this.ch.put(tr);
end
endtask
endclass

With this generator, there is no easy way to randomize cells with different constraints.

Solutions


Factory Patterns

Factories – Correct Solution


class factory;
alu_xactn blueprint = new();
task run();
while (run) begim
alu_xactn tr;
void = blueprint.randomize();
$cast(tr, blueprint.copy());
process(tr);
end
endtask
endclass

Changing the blueprint

class my_transaction extends alu_xactn;
constraint select_valid {select == 3'b100;}
endclass
program test;
verif_env env;
initial begin
env = new();
env.build();
begin
my_transaction my_tr = new();
env.select.blueprint = my_tr;
end
env.run();
end
endprogram

Benefits

Tests can modify constraints by


Transactors

VMM Transactors


A Basic Transactor

Add code to method main() to process transactions.

The other methods all start with a call to the base method to start and stop this method. For example, vmm_xactor::start_xactor() starts the virtual method main() – Don’t need to do this.

class alu_driver extends vmm_xactor;
//start_xactor starts the execution threads
virtual task start_xactor;
super.start_xactor();
...
endtask
//stops execution threads after currently executing
//transaction had completed. Takes effect at next call
//to vmm_xactor::wait_if_stopped()
virtual task stop_xactor();
super.stop_xactor();
...
endtask
// resets the xactor’s state and execution threads
virtual task reset_xactor(...);
super.reset_xactor(...);
...
endtask
virtual task main();
...
endtask
endclass

Stopping an Xactor

The main() method periodically checks to see if the transactor has been stopped by calling wait_if_stopped() as shown below

virtual task main();
forever begin
this.wait_if_stopped();
alu_xactn tr = to_driver.get();
this.wait_if_stopped();
...
end
endtask

The wait_if_stopped() method will block if stop_xactor() has been called.

Different blocks in testbench will define when to stop and what it means.

Should check if the transactor needs to stop after every time-consuming action, such as the call to get () above.

Physical and Virtual Interfaces


Click here to download the VMM example