Sion Uvm
Sion Uvm
methodology(UVM)
What is verification methodology
• Methodology is a systematic way of doing things with a set of standard rules and
guidelines
• Verification methodology means while doing verification we have to follow some
rules and regulations
• Different verification methodologies
• AVM-Advanced verification methodology
• RVM-Reference verification methodology
• OVM-Open verification methodology
• VMM-Verification methodology manual
• UVM-Universal verification methodology
What is Universal verification methodology
• It is a methodology for functional verification using system verilog
• It complete with a supporting library of sv code
• UVM was created by Accellera based on OVM version (Open version methodology)
• UVM library contains: which can be used for shorthand notation of complex implementation
• Component classes for building testbench components like generator/driver/monitor etc.
• Reporting classes for logging, Factory for object substitution.
• Synchronization classes for managing concurrent process.
• Policy classes for printing, comparing, recording, packing, and unpacking of uvm_object based.
• TLM Classes for transaction level interface.
• Sequencer and Sequence classes for generating realistic stimulus.
• And Macros
Introduction
• UVM is a methodology based on SystemVerilog language and is not a language on its own.
• It enables efficiency in terms of reuse and is also currently part of IEEE 1800.2 working
group.
• Modularity and Reusability – The methodology is designed as modular components
(Driver, Sequencer, Agents , env etc) which enables reusing components across unit level to
multi-unit or chip level verification as well as across projects.
• Separating Tests from Testbenches – Tests in terms of stimulus/sequencers are kept
separate from the actual testbench hierarchy and hence there can be reuse of stimulus across
different units or across projects
• Simulator independent – The base class library and the methodology is supported by all
simulators and hence there is no dependence on any specific simulator
Continu,,,
• Sequence methodology-- gives good control on stimulus generation. There are several ways in
which sequences can be developed which includes randomization, layered sequences, virtual
sequences etc which provides a good control and rich stimulus generation capability.
• Configuration class - config class is a database where we configure all the parameters needed
throughout the hierarchy using set and get .sv we don't not have configuration facility.
• Phasing - since all the components are derived from uvm_ component , phasing helps in
synchronisation of each and every component before proceeding to next phase.
• TLM - in SV mailbox is used for passing the message between component but here tlm that is
transaction level modelling which supports multiple languages and it is port to port connection
not like mailbox which is component to component connection.
Continu,,,
• Factory - here factory is class that manufactures components and objects which gives the ability
to modify and no of objects that makes TB hierarchy in more predictable manner.overriding of
components or objects becomes much more easy using factory concept using create() method
instead of new() method
• Virtual sequencer and sequences - for keeping the independency between TB writer and test
case writer .here test case writer don't have to worry about the path in order to start a sequence
which can not be done in SV.
• Reusable and more portable . No need to be dependent on test.able to change the objects from
top level .
UVM class hierarchy
uvm_void:
• It is the base class for all UVM classes.
• This class is the top of the class inheritance hierarchy.
• System verilog has no need for a class that serves as a base of all classes, but specman e need to
have a common root base class, so they added it to the UVM library, but it turns out it is not
needed in the UVM
uvm_object:
• The uvm_object class is the base class for all UVM data and hierarchical classes.
• Its primary role is to define a set of methods for such common operations
as create, copy, compare, print, and record
Contin…
uvm_transaction:
• The ~uvm_transaction~ is the root base class for UVM transactions, which, unlike
~uvm_components~, are transient in nature.
• It extends <uvm_object> to include a timing and recording interface.
• Simple transactions can derive directly from ~uvm_transaction~, while sequence-enabled
transactions derive from ~uvm_sequence_item~
uvm_report_object:
• The uvm_report_object provides an interface to the UVM reporting facility.
• Through this interface, components issue the various messages that occur during simulation.
• A report consists of an id string, severity, verbosity level, and the textual message itself.
• They may optionally include the filename and line number from which the message came.
uvm_transaction vs uvm_sequence_item
• uvm_transaction is the base class for modeling any transaction which is derived from
uvm_object .
• uvm_sequence_item is an extension of uvm transaction class.
• A sequence item is nothing but a transaction that groups some information together and also
adds some other information like: sequence id (provide id for sequence_item then easily
identify by the driver), and transaction id, etc.
• It is recommended to use uvm_sequence_item for implementing sequence based stimulus.
• Proper sequencer and Driver communication won't Happen if it is not extending from
sequence_item.
UVM ARCHITECTURE
UVM Architecture
Sequence_item:
• The sequence-item is written by extending the uvm_sequence_item, uvm_sequence_item inherits from the
uvm_object via the uvm_transaction class.
• Therefore uvm_sequence_item is of an object type.
• The sequence-item consist of data fields required for generating the stimulus.
• In order to generate the stimulus, the sequence items are randomized in sequences.
• Therefore data properties in sequence items should generally be declared as rand and can have constraints
defined.
• Data fields represent the following types of information,
1. Control Information – a type of transfer, transfer size, etc ex:rand bit wr;
2. Payload Information – data content of the transfer ex: rand bit [7:0] wdata
3. Configuration Information – mode of operation, error behavior, etc
4. Analysis Information – fields used to capture information from DUT, ex: output bit[7:0]rdata;
as analysis information fields will be used for capturing response, except these fields the other fields
can be declared as rand and can have constraints associated with it.
Sequence_item example
class mem_seq_item extends uvm_sequence_item;
//Control Information
rand bit [3:0] addr;
rand bit wr_en;
rand bit rd_en;
//Payload Information
rand bit [7:0] wdata;
//Analysis Information
bit [7:0] rdata;
//Utility and Field macros,
`uvm_object_utils_begin(mem_seq_item)
`uvm_field_int(addr,UVM_ALL_ON)
`uvm_field_int(wr_en,UVM_ALL_ON)
`uvm_field_int(rd_en,UVM_ALL_ON)
`uvm_field_int(wdata,UVM_ALL_ON)
`uvm_object_utils_end
//Constructor
function new(string name = "mem_seq_item");
super.new(name);
endfunction
//constaint, to generate any one among write and read
constraint wr_rd_c { wr_en != rd_en; };
endclass
Sequence:
• A sequence generates a series of sequence_item’s and sends it to the driver via sequencer,
Sequence is written by extending the uvm_sequence
• A uvm_sequence is derived from an uvm_sequence_item
• a sequence is parameterized with the type of sequence_item, this defines the type of the item
sequence that will send/receive to/from the driver.
`uvm_object_utils(mem_sequence)
//Constructor
function new(string name = "mem_sequence");
super.new(name);
endfunction
`uvm_object_utils(mem_sequencer)
//Constructor
function new(string name = "mem_sequencer");
super.new(name);
endfunction
endclass
UVM Driver
• A driver is written by extending the uvm_driver
• uvm_driver is inherited from uvm_component, Methods and TLM port (seq_item_port) are defined for
communication between sequencer and driver
• The uvm_driver is a parameterized class and it is parameterized with the type of the request
sequence_item and the type of the response sequence_item
• UVM_Driver Methods
• get_next_item
• This method blocks until a REQ sequence_item is available in the sequencer.
• try_next_item
• This is a non-blocking variant of the get_next_item() method. It will return a null pointer if there is no
REQ sequence_item available in the sequencer.
• item_done
• The non-blocking item_done() method completes the driver-sequencer handshake and it should be
called after a get_next_item() or a successful try_next_item() call.
class mem_driver extends uvm_driver #(mem_seq_item);
virtual mem_if vif;
Driver example `uvm_component_utils(mem_driver) // Constructor
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
`uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction: build_phase // run phase
virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); //respond_to_transfer(req);
drive();
seq_item_port.item_done();
end
endtask : run_phase// drive
virtual task drive();
req.print();
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
@(posedge vif.DRIVER.clk);
`DRIV_IF.addr <= req.addr;
if(req.wr_en) begin
`DRIV_IF.wr_en <= req.wr_en;
`DRIV_IF.wdata <= req.wdata;
end
end
endtask : drive
endclass : mem_driver
Output Monitor
• The user-defined monitor is extended from uvm_monitor, uvm_monitor is
inherited by uvm_component
• Output monitor is a passive entity that samples the DUT signals through the virtual
interface and converts the signal level activity to the transaction level
• Monitor samples DUT signals but does not drive them
• The monitor should have an analysis port (TLM port) and a virtual interface
handle that points to DUT signals.
Monitor class example class mem_monitor extends uvm_monitor;
// Virtual Interface
virtual mem_if vif;
uvm_analysis_port #(mem_seq_item) item_collected_port;
// Placeholder to capture transaction information.
mem_seq_item trans_collected;
`uvm_component_utils(mem_monitor)
// new - constructor
function new (string name, uvm_component parent);
super.new(name, parent);
trans_collected = new();
item_collected_port = new("item_collected_port", this);
endfunction : new
// run phase
virtual task run_phase(uvm_phase phase);
item_collected_port.write(trans_collected);
endtask : run_phase
endclass : mem_monitor
Agent
• a user-defined agent is extended from uvm_agent, uvm_agent is inherited by uvm_component
• An agent typically contains a driver, a sequencer, and a monitor
• Agents can be configured either active or passive
Active agent
• Active agents generate stimulus and drive to DUT
• An active agent shall consists of all the three components driver, sequencer, and monitor
Passive agent
• Passive agents sample DUT signals but do not drive them
A passive agent consists of only the monitor
• An agent can be configured as ACTIVE/PASSIVE by using a set config method, the default agent will be
ACTIVE. the set config can be done in the env or test.
// connect_phase
function void connect_phase(uvm_phase phase);
if(get_is_active() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect_phase
Agent with example class mem_agent extends uvm_agent;
//declaring agent components
mem_driver driver;
mem_sequencer sequencer;
mem_monitor monitor;
// UVM automation macros for general components
`uvm_component_utils(mem_agent)
// constructor
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
// build_phase
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_component_utils(mem_scoreboard)
uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export;
// new - constructor
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
// write
virtual function void write(mem_seq_item pkt);
$display("SCB:: Pkt recived");
pkt.print();
endfunction : write
endclass : mem_scoreboard
Environment class example
class mem_model_env extends uvm_env;
mem_agent mem_agnt;
`uvm_component_utils(mem_model_env)
// new - constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new
// build_phase
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mem_agnt = mem_agent::type_id::create("mem_agnt", this);
endfunction : build_phase
endclass : mem_model_env
Test
• The user-defined test is derived from uvm_test, uvm_test is inherited from uvm_component.
• The test defines the test scenario for the testbench
• test class contains the environment, configuration properties, class overrides etc
• A sequence/sequences are created and started in the test
• The UVM testbench is activated when the run_test() method is called, the global run_test() task
should be specified inside an initial block.
initial begin
run_test();
end
• There can be many user-defined test cases.Among multiple test cases, a particular test case can be
selected and execute on two methods,
1. by specifying the test name as an argument to run_test();
example: run_test("mem_model_test");
2. by providing the UVM_TESTNAME command line argument
example: <SIMULATION_COMMANDS> +UVM_TESTNAME=mem_model_test
Test class example
class mem_model_test extends uvm_test;
`uvm_component_utils(mem_model_test)
mem_model_env env;
mem_sequence seq;
endclass : mem_model_test
Top
TestBench top is the module, it connects the DUT and Verification environment
components.
Typical Testbench_top contains,
• DUT instance
• interface instance
• run_test() method
• virtual interface set config_db
• clock and reset generation logic
• wave dump logic
Top_module example
module tbench_top;
//clock and reset signal declaration
bit clk;
bit reset;
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
//creatinng instance of interface, inorder to connect DUT and testcase
mem_if intf(clk,reset);
//DUT instance, interface signals are connected to the DUT ports
memory DUT (
.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.rd_en(intf.rd_en),
.wdata(intf.wdata),
.rdata(intf.rdata) );
//enabling the wave dump
initial begin
uvm_config_db#(virtual mem_if)::set(uvm_root::get(),"*","mem_intf",intf);
end
initial begin
run_test();
end
endmodule
Difference between uvm_component and uvm_object
uvm_component uvm_object
• Quasi Static Entity (after build • Dynamic Entity (create when needed,
phase it is available throughout the transfer from one component to
simulation) other & then dereference)
• Always tied to a given • Not tied to a given hardware or any
hardware(DUT Interface) Or a TLM TLM port
port
• Having phasing mechanism for • No phasing mechanism
control the behavior of simulation
• uvm_component have two arguments, • uvm_object have one arguments i.e
a name and a uvm_component name
parent.
• Components are instantiated at start • Objects are instantiated at run time
of simulation
UVM Class Hierarchy
Message Report
Bottom to Paraalel
up execution execution
Build_phase
• Build_phase are executed at start of simulation
• Purpose is to construct the testbench components
• All build_phase methods are functions, therefore execute in zero-simulation time
• Build_phase is top-down approach
Virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
Connect_phase
• Used to make TLM connections between components
• It has to occur after build_phase
• It works from bottom to up in testbench component hierarchy
Virtual function void connect_phase(uvm_phase phase);
super.build_phase(phase);
driv.seq_item_port.connect(seqr.seq_item_export)
endfunction
end_of_elaboration
• Used to make any final adjustments to tb connectivity, tb configuration before simulation starts
• Used to display environment hierarchy
• This phase executes from bottom_up
virtual function void end_of_elaboration_phase(uvm_phase phase);
uvm_top.print_topology();
uvm_report_info(get_full_name(),”end_of_elaboration”,uvm_LOW);
endfunction
2.Run_Phase
• The UVM Testbench stimulus is generated and executed during the run time
phases which follows the build phases.
• Run phase was present in OVM as well but additional other phases were added to UVM to give
finer run-time for tests, scoreboard and other components.
run_Phase:
• The run phase occurs after the start_of_simulation phase and is used for the stimulus
generation and checking activities of the Testbench.
• The run phase is implemented as a task, and all uvm_component run tasks are executed
in parallel.
• Transactors such as driver and monitor will nearly always use this phase.
task run_phase(uvm_phase phase);
::::::::::
endtask
pre_reset:
• pre_reset phase starts at the same time as the run phase.
• Its purpose is to take care of any activity that should occur before the reset.
• E.g. waiting for a power signal to go active.
reset:
• Specially for DUT or Interface specific reset behavior.
• This phase would be used to generate reset to put the DUT/Interface into a default state.
post_reset:
• This phase is intended for any activity required just after the reset phase.
extract:
• The extract phase is used to retrieve and process information from Scoreboards and Functional
Coverage Monitors.
• This may include the calculation of statistical information used by the report phase.
• This phase is usually used by Analysis side components.
check:
• This phase is also used by the Analysis Components.
• This phase is used to check if the DUT behaved correctly and to find any error that
may have occurred during the stimulus execution.
report:
• The report phase is used to display the results of the simulation to the standard output
or to write the results to file.
• This phase is usually used by Analysis Components.
final:
• The final phase is used to complete any other outstanding actions that the Testbench
has not already completed.
How UVM Phasing is triggered?
• To start a UVM Testbench, the run_test() method has to be called from the static part of the
Testbench.
• It is usually called from within an initial block in the top level module of the Testbench.
// Top level Testbench module
module top_tb; .... .... // UVM start up:
initial begin
uvm_config_db #(virtual bus_if)::set(null, "*", "BUS_vif" , BUS);
run_test("bidirect_bus_test");
end endmodule: top_tb
• Once the run_test() method is called, it constructs the root componentof the UVM environment
& then triggers/initiates the UVM Phasing process.
• A phase starts only when all components in the previous phase have dropped their objections.
• A phase continues to execute until all components have dropped their objections in the current
phase
• The run_test() method can be passed with a string argument, which in the above code
is “bidirect_bus_test”, to define the default type name which is used as the root node of
the Testbench Hierarchy.
• In addition, run_test() method also checks for a command line plusarg
called UVM_TESTNAME and uses that plusarg string to lookup a factory registered
uvm_component to override any default type name.
• Hence to execute the “bidirect_bus_test” using command line plusarg, we’ve to use the
following command line:
• % <simulator executable> +UVM_TESTNAME=bidirect_bus_test
Phase Synchronization
• By default all components allow all other components to complete a phase before all
components move to next phase
time
Note:In Phase domain each component phases are independent of another component phase- do not use phase domain
How simulation ends in UVM methodology?
• UVM has a phased execution which consists of a set of build phases, run phases and check
phases.
• The run() phase is where the actual test simulation happens and during this phase every
component can raise an objection in beginning and hold it until it is done with its activity.
• Once all components drops the objection, the run() phase completes and then check() phase of all
components execute and then the test ends.
• This is how a normal simulation ends, but there are also controls on simulation timeouts to
terminate the run() phase if some component hangs due to a bug in design or testbench.
• When the run() phase starts, a parallel timeout timer is also started.
• If the timeout timer reaches one of the specified timeout limits before the run() phase completes,
the run() phase will timeout, an error message will be issued and then all phases post run() will
get executed and test ends after that.
UVM REPORTING
Why we go for uvm reporting
• UVM Reporting or Messaging has a rich set of message-display commands & methods to
alter the numbers & types of messages that are displayed without re-compilation of the design.
• UVM Reporting also includes the ability to mask or change the severity of the message to
adapt the required environment condition.
• UVM Reporting has the concepts of Severity, Verbosity and Simulation Handing Behavior.
Each of them can be independently specified and controlled.
• Components are inherited from uvm_report_object, hence components already have methods
and functions to display messages
Concepts of reporting
• Severity
• Severity indicates importance
• Examples are Fatal, Error, Warning & Info
• Verbosity
• Verbosity indicates filter level
• Examples are None, Low, Medium, High, Full & Debug
• Simulation Handling Behavior
• Simulation handling behavior controls simulator behavior
• Examples are Exit, Count, Display, Log, Call Hook & No Action
• Simulation Handling Behavior in-fact is the Action taken by the Simulator which is
dependent on Severity being produced by the Verification Environment. We’ll see
more details shortly about it.
Reporting methods
• Four basic reporting functions that can be used with different verbosity levels
virtual function void uvm_report_info (string id,string message,int verbosity=UVM_MEDIUM,string filename="",int line=0)
virtual function void uvm_report_warning (string id,string message,int verbosity=UVM_MEDIUM,string filename="",int line=0)
virtual function void uvm_report_error(string id,string message,int verbosity=UVM_LOW, string filename="",int line=0)
virtual function void uvm_report_fatal (string id,string message,int verbosity=UVM_NONE, string filename="",int line=0)
• UVM has six levels of verbosity with each one represented by an integer.
• UVM verbosity level is required only for uvm_info, cant be used for uvm_warning,
uvm_error, uvm_fatal
uvm_report_info (get_type_name (), $sformatf ("None level message"), UVM_NONE);
uvm_report_info (get_type_name (), $sformatf ("Low level message"), UVM_LOW);
uvm_report_info (get_type_name (), $sformatf ("Medium level message"), UVM_MEDIUM);
uvm_report_info (get_type_name (), $sformatf ("High level message"), UVM_HIGH);
uvm_report_info (get_type_name (), $sformatf ("Full level message"), UVM_FULL);
uvm_report_info (get_type_name (), $sformatf ("Debug level message"), UVM_DEBUG);
uvm_report_warning (get_type_name (), $sformatf ("Warning level message"));
uvm_report_error (get_type_name (), $sformatf ("Error level message"));
uvm_report_fatal (get_type_name (), $sformatf ("Fatal level message"));
uvm_actions
• Fundamentally the Verbosity level describes how verbose a Testbench can be.
• The default Verbosity is UVM_MEDIUM.
• There are different Verbosity level being supported by UVM.
• In case of default Verbosity level i.e. UVM_MEDIUM, any messages with UVM_HIGH or
above are filtered out.
UVM_NONE 0
UVM_LOW 100
UVM_MEDIUM 200
(Default)
UVM_HIGH 300
UVM_FULL 400
UVM_DEBUG 500
How to change verbosity settings
• Verbosity settings can be modify by using command line switches and method calls
• Using verbosity method calls that selectively change verbosity settings for specific components or
for entire component hierarchy
• function void set_report_verbosity_level (int verbosity_level)
function void set_report_severity_action (uvm_severity severity,uvm_action action)
function void set_report_id_action (string id,uvm_action action)
function void set_report_severity_id_action (uvm_severity severity,string id,uvm_action action)
Using get_type_name, get_full_name, get_name
• There are three built in methods which is used to call inside the reporting macros
• They are get_name(), get_type_name(), get_full_name()
• get_name(): returns the name of the object
`uvm_error(get_name(), “driver“) // :drv [D] driver
• Config db allows passing of objects and data to various components in the testbench.
• It is built on top of the UVM resource database, uvm_resource_db
• uvm_resource_db is a data sharing mechanism where hierarchy is not important.
• The database is essentially a lookup table which uses a string as a key and where you can add
and retrieve entries.
class uvm_resource_db#(type T=uvm_object)
• The uvm_config_db class is the recommended way to access the resource database.
• A resource is any piece of information that is shared between more than one component or object.
• We use uvm_config_db::set to put something into the database and uvm_config_db::get to
retrieve information from the database.
• The uvm_config_db class is parameterized, so the database behaves as if it is partitioned into
many type-specific "mini databases."
• There are no limitations on the the type - it could be a class, a uvm_object, a built in type such as
a bit, byte, or a virtual interface.
• There are two typical uses for uvm_config_db.
• The first is to pass virtual interfaces from the DUT to the test, and the second is to pass
configuration classes down through the testbench.
Functions in resource_db
• There are several common functions of the uvm_resource_db class that allow you to add or
retrieve data.
Methods Description
get_by_type This function gets the resource by the type specified by the parameter so the only argument is the
scope.
get_by_name This function gets a resource by using both the scope and name given when it was added to the
database.
set This function creates a new resource in the database with a value, scope, and name that will be
used for retrieval.
read_by_name This function locates a resource by scope and name and returns the value through an output
argument.
read_by_type This function locates the resource using only the scope as a lookup and returns the value through
an output argument.
write_by_name This function locates the resource by scope and name and writes the value to it. If the resource
does not exist then it will be created like the set function.
write_by_type This function locates the resource by the scope and writes the value to it. If the resource does not
exist it is created.
WHEN IS THE CONFIGURATION DATABASE USED?
• The uvm_config_db is used when hierarchy is important.
• With the uvm_config_db, the user not only can add an object to the database, but can also specify, with
great detail, the level of access to retrieval by specifying the hierarchy
• The classic example of uvm_config_db usage is with sharing a virtual interface.
• A SystemVerilog interface is instantiated at the top level of the testbench and connects to the ports of
the device under test (DUT).
• For the UVM testbench to be able to drive to driver or monitor this interface, it needs to have access
to it.
• The various interface instantiations can be added to the database with the access level controlled, since
it can then be retrieved by the appropriate component only if it is in the specified hierarchy.
• Virtual interfaces are not the only use for the configuration database. Any object can be stored and
retrieved.
• Other common uses of the configuration database include sharing configuration objects or setting
whether an agent is active or passive.
HOW IS DATA STORED AND RETRIEVED?
• configdatabase, there are only two functions that are most commonly used with the configuration
database:
• • set – adds an object to the uvm_config_db
• • get – retrieves an object from the uvm_config_db
class uvm_config_db#(type T=int) extends uvm_resource_db#(T)
• Note that all the methods of the uvm_config_db class are static so they must be called with the scope
resolution operator, as is the case with the uvm_resource_db.
• Once again, type parameterization is used so the actual type for the resource, T, must be given.
• Also noteworthy, the default parameter type of the uvm_resource_db is uvm_object, whereas the
default type for the uvm_config_db is int
Contin……
• set() function has four arguments
Static function void set(uvm_component cntxt, string inst_name, string field_name, Tvalue)
cntxt: The context is the hierarchical starting point of where the database entry is accessible.
string inst_name:The instance name is the hierarchical path that limits accessibility of the database entry.
String field_name:The field name is the label used as a lookup for the database entry.
Tvalue: The value to be stored in the database of the parameterized type. By default the type is int.
• The get() function which is used to retrieve items from the database.
• It is important to note that objects are not removed from the database when you call get().
• The actual variable is passed in as an inout formal function argument and so is performed as a copy-in-
copy-out operation.
Static function void get(uvm_component cntxt, string inst_name, string field_name, inoutTvalue)
cntxt:The context (cntxt) is the starting point for the search.
string inst_name: The instance name in this case can be an empty string since it is relative to the context
String field_name:The field name is the label given when the object was added to the database
Tvalue: The value argument is the variable that the retrieved value is assigned to
Contin……
• An interface has been instantiated in the top level and now needs to be added to the uvm_config_db
using the set()function.
• The most basic way to do this is to use the set() function and allow the virtual interface to be widely
accessible from anywhere within the testbench.
uvm_config_db#(virtual tb_intf)::set(uvm_root::get(),"*", "dut_intf", vif
• The first argument is the context (cntxt) which is the starting point of the lookup search.
• The example uses uvm_root::get() to acquire the top-level so the search will start at the top of the
hierarchy in this case.
• Normally "this" would be used as the context if the call to set() is within a class, but to set the virtual
interface correctly in the database, the code has to be placed inside a module, so there would be no
class context.
• The second argument is the instance name. In this example, the interface is being made globally
available amongst all components so the wildcard, "*", is used.
• The third argument is the field name which is a label used for lookup.
• Finally, the value argument is the actual instance of the interface.
Contin……
• In most cases, you do not want to make a database entry globally available.
• Since a global scope is essentially a single namespace, it makes reuse more difficult if everything is
stored in this scope.
• To restrict its access, use the hierarchical path in conjunction with the wildcard character as shown
below
uvm_config_db#(TYPE)::set(this,"*.path","label", value)
• Adding other objects into the uvm_config_db is just as straightforward as adding a virtual interface
• The important thing to remember is that each entry needs a unique field name or label (if the global
scope is being used), or the path needs to be limited in such a way that non-unique labels do not
conflict as the scopes are now limited to specific areas of the naming hierarchy
Contin……
• uvm_config_db: a virtual interface, an integer value, and a configuration object. Also, there is
a generic call to the get() function.
• To retrieve the integer value the label would be "retry_count" and the value stored in this
entry would be assigned to the rty_cnt property in the object that is calling the get() function.
Config database methods
• set()
• get()
• exists()
• wait_modified()
• Convenience tasks
set()
static function void set ( uvm_component cntxt, string inst_name, string field_name, T value);
• Static function of the class uvm_Config_db to set a variable in the configuration database
// Set virtual interface handle under name "apb_vif" available to all components below uvm_test_top, indicated by the *
uvm_config_db #(virtual apb_if) :: set (null, "uvm_test_top.*", "apb_vif", apb_if);
// Set an int variable to turn on coverage collection for all components under m_apb_agent
uvm_config_db #(int) :: set (null, "uvm_test_top.m_env.m_apb_agent.*", "cov_enable", 1);
// Consider you are in agent's build_phase then you may achieve the same effect by
uvm_config_db #(int) :: set (this, "*", "cov_enable", 1);
get()
static function bit get ( uvm_component cntxt, string inst_name, string field_name, T value);
• Static function of the class uvm_Config_db to set a variable in the configuration database
// Get virtual interface handle under name "apb_vif" into local virtual interface handle at m_env level
uvm_config_db #(virtual apb_if) :: get (this, "*", "apb_vif", apb_if);
// Get int variable fails because no int variable found in given scope uvm_config_db #(int) :: get (null,
"uvm_test_top", "cov_enable", cov_var);
exists()
static function bit exists ( uvm_component cntxt, string inst_name, string field_name, bit spell_chk);
Convenience tasks
typedef uvm_config_db #(uvm_bitstream_t) uvm_config_int; typedef
uvm_config_db #(string) uvm_config_string; typedef uvm_config_db
#(uvm_object) uvm_config_object; typedef uvm_config_db
#(uvm_object_wrappet) uvm_config_wrapper;
FACTORY CONCEPT
Introduction
• As per the recommended UVM methodology, we should never construct new components
and/or transactions using new() class constructor.
• Instead, it is recommended that – we should make calls to a look-up table to create the requested
components and transactions.
• This special look-up table is called “Factory” in UVM. Entries to this look-up table are made
by registering the components and/or transactions while defining them.
• To create a component/transaction using Factory, create() method is used.
• The purpose of factory in UVM is to change the behaviour of the testbench without any
change in code or without any compilation.
• Basically it's for overriding purpose where you can override any object/component or class
into the another without any further modification.
Contin…
• UVM Factory facilitates an object of one type to be substituted with an object of derived type
without having to change the structure of the Testbench or modify the Testbench code.
• This behavior is called “overriding” and there are following types of overriding is possible
with UVM Factory
1. Type Overriding(set_type_override or Global override)
2. Instance Overriding(set_inst_override)
• Overriding helps to replace one transaction/sequence with another to generate new scenarios or
conditions without making any change in the Testbench structure/code.
• Similarly, a new version of the components can be brought into the Testbench without any
change in the structure of the Testbench & beauty of all this is that – it happens all the fly at the
run time.
Factory registrations for object and component
• Registering uvm_object with factory • Registering uvm_component with factory
`uvm_object_utils(sequence) `uvm_component_utils(monitor)
• Consider a case in which you have a agent and driver, monitor are instantiated inside the agent.
• Now you want to change the base driver with the extended driver, you can the extend the base
driver and create a new driver class.
• Now to use this extended driver class you need to change the code in the agent class also
• e.g new instantiation of the extended driver class, if not then again you need to extend the agent
class and use extended driver class, which is too much of overhead
• To avoid this we use factory. In factory by registering the driver class we can now override the
base driver with extended driver without changing the code in the agent class.
Components/objects creating using new()
calling the new() is not recommended and limits reuse Suppose user wants to add extra data or trying to override the
Class driver extends uvm_component; driver task using new()
`uvm_component_utils(driver)
function new(string name, uvm_component parent); Class my_project extends driver;
super.new(name,parent); `uvm_component_utils(my_project)
endfunction function new(string name, uvm_component parent);
:::::: super.new(name,parent);
virtual task driver_transfer(); endfunction
:::::: ::::::
endclass virtual task driver_transfer();
Super.drive_transfer();
Class agent extends uvm_component; ::::::
`uvm_component_utils(agent) endclass
driver my_drv;
function new(string name, uvm_component parent); • Agent instantiates previous driver not the my_project driver
super.new(name,parent);
my_drv=new(“my_drv”, this);
• To add this m_project driver in agent need to extend the agent
endfunction class & potentially other clases need to instantiate dis driver
:::::: class ag1 extends agent; • Sine driver, agent clases need to be extended dis makes code
virtual task driver_transfer(); modification all over the testbench
::::::
endclass
• Uvm factory introduces overriding option by using factory concept overriding the exact driver from
outside the agent instead of using new(), user needs to use create()
• The main difference between new() and create() is, by using create we can overriding the code
without any modification ,but by using new() its not possible
Components/objects creating using create()
• We will register the components & objects using type_id and while creating the instances of
components or objects we will create them by the factory method “create” which is called using type_id
calling the create() The code inside the agent, the
Class driver extends uvm_component; Class child_driver extends driver;
driver instance will be created as `uvm_component_utils(child_driver)
`uvm_component_utils(driver)
function new(string name, uvm_component parent); the create method is called with function new(string name, uvm_component
super.new(name,parent); type_id of driver parent);
endfunction later in one test case suppose we super.new(name,parent);
:::::virtual task driver_transfer(): endfunction
endclass
need to override this particular ::::::
driver with child_driver, we can virtual task driver_transfer();
Class agent extends uvm_component; override it from top-level Super.drive_transfer();
`uvm_component_utils(agent) ::::::
component (test) using the factory Endclass
driver my_drv;
function new(string name, uvm_component parent); as shown below.
super.new(name,parent); class test extends uvm_test;
my_drv=new(“my_drv”, this); ....
endfunction function void build_phase(uvm_phase phase);
:::::: ....
virtual task driver_transfer(); // calling factory overriding method to override the type_id of driver with child_driver type_id
:::::: set_type_override_by_type(driver::get_type(),child_driver::get_type());
endclass endfunction
• type_id of driver is overridden with child_driver type_id, inside agent create method is called with child_driver type_id hence the
instance of child_driver is created.
• With the help of the factory, we can override the type of underlying components or objects from the top-level component without having
to edit the code.
Example for factory reuse
• Consider a case when you are moving from one project to another.
• For example, USB 2.0 to USB 3.0. You understand that you leverage everything from USB 2.0
testbench and use it as USB 3.0 testbench with exception of driver.
• When you override the old driver class with the new driver class using factory method
set_type_override, all instances of old driver class will be replaced by the new instance of USB3.0
driver class.
Factory methods
factory.set_type_override_by_type(original_type::get_type(), substitute_type::get_type())
set_type_override_by_type
set_type_override_by_name
set_inst_override_by_type
<original_type>::type_id::set_inst_override(original_type::get_type(),
Instance override <substitute_type>::get_type(), <path_string>);
set_inst_override_by_name
factory.set_inst_override_by_name("a_packet","bad_packet", "*");
Type Overriding:
• In type-override, overriding the current class type(original class) with other class type(derived class
or substitute_type)
<original_type>::type_id::set_type_override(<substitute_type>::get_type());
Agent_top
agent2
agent1
drv2 mon2 seqr2
drv1 mon1 seqr1
<original_type>::type_id::set_inst_override(<substitute_type>::get_type(), <path_string>);
Instance Override
class my_test extends uvm_test;
`uvm_component_utils(my_test) env e; • Objects or sequence related
function new (string name, uvm_component parent); objects are generally only
super.new(name, parent);
endfunction: new used with type override
function void build_phase(uvm_phase phase);
super.build_phase(phase);
my_driver::type_id::set_inst_override(my_updated_driver::get_type(), "top.e.agent.drvr"); • since the instance override
set_inst_overidde_by_type(my_driver::get_type(),my_updated_driver(),”*”) approach relates to a position
or
set_inst_overidde_by_type(my_driver::get_type(),my_updated_driver(),”agent.*”); in the UVM Testbench
or component hierarchy which
set_inst_overidde_by_type(my_driver::get_type(),my_updated_driver(),”agent*”);
e = env::type_id::create("e", this); objects do not take part in.
endfunction: build_phase
task run_phase (uvm_phase phase); ... ... ...
endtask: run_phase
endclass: my_test
Debugging the UVM Testbench Structure & Factory Content
• For complex UVM Testbench Environment, it is often useful to print out the structure of the
testbench in tabular form, that were registered with the Factory.
• A great technique to view the structural composition of the Testbench classes and the Factory
setup is to call the this.print() and factory.print() methods in the end_of_elaboration_phase()
(as shown in Code below) from the top-level testbench.
• By the time the end_of_elaboration_phase() executes, the entire environment has already been
built and connected
• So these print() methods show what had been built in the Testbench and the types that were
registered with the factory.
• Utility macros are used to declare either object or component going to store in factory
For simple objects with no field macros, use For simple objects with no field macros, use
`uvm_object_utils(TYPE) `uvm_component_utils(TYPE)
For simple objects with field macros, use For simple objects with field macros, use
`uvm_object_utils_begin(TYPE) `uvm_component_utils_begin(TYPE)
`uvm_field_* macro invocations here `uvm_field_* macro invocations here
`uvm_object_utils_end `uvm_component_utils_end
For parameterized objects with no field macros, use For parameterized objects with no field macros, use
`uvm_object_param_utils(TYPE) `uvm_component_param_utils(TYPE)
For parameterized objects, with field macros, use For parameterized objects, with field macros, use
`uvm_object_param_utils_begin(TYPE) `uvm_component_param_utils_begin(TYPE)
`uvm_field_* macro invocations here `uvm_field_* macro invocations here
`uvm_object_utils_end `uvm_component_utils_end
UVM Field Macros
• The `uvm_field_* macros are invoked inside of the `uvm_*_utils_begin and `uvm_*_utils_end
macro blocks to form “automatic” implementations of the core data methods:
copy, compare, pack, unpack, record, print, and sprint.
• By using the macros, you do not have to implement any of the do_* methods inherited
from uvm_object.
• The field macros expand into general inline code that is not as run-time efficient nor as flexible as
direct implementions of the do_* methods.
• Each `uvm_field_* macro is named according to the particular data type it handles:
integrals, strings, objects, queues, etc., and each has at least two arguments: FIELD and FLAG.
FIELD-int
`uvm_object_utils_begin(ABC)
FLAG-UVM_DEFAULT
`uvm_field_int(m_addr, UVM_DEFAULT)
`uvm_field_int(m_data, UVM_DEFAULT)
`uvm_object_utils_end
Field_macros FIELD description
• Macros that implement data operations for scalar properties.
UVM_PHYSICAL Treat as a physical field. Use physical setting in policy class for this field
Treat as an abstract field. Use the abstract setting in the policy class for
UVM_ABSTRACT
this field
UVM_READONLY Do not allow the setting of this field from the set_*_local methods
A radix for printing and recording can be specified by OR’ing one of the following constants in
the FLAG argument
FLAG Description
UVM_BIN Print/record the field in binary (base-2)
UVM_DEC Print/record the field in decimal (base-10)
UVM_UNSIGNE Print/record the field in unsigned decimal (base-
D 10)
UVM_OCT Print/record the field in octal (base-8).
UVM_HEX Print/record the field in hexadecimal (base-16)
UVM_STRING Print/record the field in string format
UVM_TIME Print/record the field in time format
do_methods in sequence_item
• Using field_automation_macros are not recommended these days, because it introduce a lot of
additional code and reduces simulation performance
• It is recommended to use do_macros, user can implement the functions called do_print,
do_record, do_compare, do_pack, do_unpack
• They are 6 do_macros in sequence_item
1. do_print
2. do_record
3. do_copy
4. do_compare
5. do_pack
6. do_unpack
7. clone
8. create
do_methods in sequence_item
print
sprint
convert2string
do_print record
do_record copy
do_copy compare
do_compare pack
do_pack pack_bytes
do_unpack pack_ints
clone unpack
create unpack_bytes
unpack_ints
clone
create
do_methods in sequence_item
• We know that sequence calls the start_item and finish_item inside the task body
• Instead of writing all these statements in code by simply calling uvm_sequence macros
• At compile time these macros will be substituted , with calls to start_item() and
finish_item()
• uvm_do_macros are used to reduce the no. of lines in code by creating item,
randomizing it and automatically calling the required tasks to start given sequence or
sequence_item
uvm sequence macros
1.create_item() / create req 2.wait_for_grant() 3.randomize the req 4.send the req
5.wait for item done 6.get response.
Macro Description
This macro takes seq_item or sequence as argument.
`uvm_do(Item/Seq) On calling `uvm_do() the above-defined 6 steps will be
executed.
`uvm_create(Item/Seq) This macro creates the item or sequence.
create() and randomize() are skipped, rest all other
`uvm_send(Item/Seq)
steps are executed.
Only create() is skipped, rest all other steps are
`uvm_rand_send(Item/Seq)
executed.
This macro performs above 6 steps along with
`uvm_do_with(Item/Seq,Constraints)
constraints defined in second argument.
create() is skipped, rest all other steps are executed
`uvm_rand_send_with(Item/Seq,Constraints)
along with constraints defined in second argument.
`uvm_do_pri(Item/Seq,Priority ) Performs `uvm_do() with priority mentioned.
uvm sequence macros
1.create_item() / create req 2.wait_for_grant() 3.randomize the req 4.send the req
5.wait for item done 6.get response.
Macro Description
Performs `uvm_do() along with constraints defined and priority
`uvm_do_pri_with(Item/Seq,Constraints,Priority)
mentioned.
create() and randomize() are skipped, rest all other steps are
`uvm_send_pri(Item/Seq,Priority)
executed with priority mentioned.
Only create() is skipped, rest all other steps are executed with
`uvm_rand_send_pri(Item/Seq,Priority)
priority mentioned.
`uvm_rand_send_pri_with(Item/Seq,Priority, create() is skipped, rest all other steps are executed along with
Constraints) constraints defined with priority mentioned.
This macro is used to declare a variable p_sequencer whose type
`uvm_declare_p_sequencer(SEQUENCER) is specified by SEQUENCER. by using p_sequencer
handle, properties of sequencer can be accessed.
Writing sequence using do_macros
`uvm_object_utils(mem_sequence)
`uvm_object_utils(mem_sequence)
//Constructor
//Constructor function new(string name = "mem_sequence");
function new(string name = "mem_sequence"); super.new(name);
super.new(name); endfunction
endfunction
virtual task body();
`uvm_create(req)
virtual task body(); assert(req.randomize());
`uvm_do(req) `uvm_send(req);
endtask endtask
endclass endclass
Writing sequence using do_macros
//`uvm_rand_send //`uvm_do_with
class mem_sequence extends uvm_sequence#( class mem_sequence extends uvm_sequence#(mem_seq_i
mem_seq_item); tem);
`uvm_object_utils(mem_sequence) `uvm_object_utils(mem_sequence)
//Constructor //Constructor
function new(string name = "mem_sequence"); function new(string name = "mem_sequence");
super.new(name); super.new(name);
endfunction endfunction
endclass
Writing sequence using do_macros
endtask endclass
endclass
m_sequencer
&
p_sequencer
Need for m_sequencer, p_sequencer
• In SystemVerilog based OVM/UVM methodologies, the sequence is an object with limited life time
unlike a component which has a lifetime through out simulation.
• The sequence is created , started and once done, de-referenced from memory and this can be any time
in the duration of a test.
• So if we want to access anything from the testbench hierarchy (which are components) - the sequence
would need a handle to the sequencer on which the sequence is running.
• Note that sequencer is a component
• m_sequencer is a handle of type uvm_sequencer_base which is available by default in a sequence
• To access the real sequencer on which sequence is running , you would need to typecast the
m_sequencer to the physical sequencer which is generally called p_sequencer (though you could
name it any)
• We do not use them only in virtual sequences. These can be used in any sequence, if we need any
functionality of sequencer in sequence.
Example for m_sequencer, p_sequencer
• Example where a sequence(object) wants to access a clock monitor which is available with its sequencer
(component)
• //Typecast the m_sequencer base type to p_sequencer because get access to clock monitor
//A test_sequencer class derived from base UVM sequencer
//Lets say, it has a clock monitor component to access clock.
class test_sequencer_c extends uvm_sequencer;
clock_monitor_c clk_monitor;
endclass
task pre_body()
//Typecast the m_sequencer base type to p_sequencer
if(!$cast(p_sequencer, m_sequencer)) begin
`uvm_fatal("Sequencer Type Mismatch:", " Worng Sequencer");
end
//get access to clock monitor
my_clock_monitor = p_sequencer.clk_monitor;
endtask
endclass
m_sequencer, p_sequencer
• m_sequencer is a generic sequencer pointer of type uvm_sequencer_base. It will always exist for
a uvm_sequence and is initialized when the sequence is started.
• The p_sequencer is a type specific sequencer pointer, created by registering the sequence to a sequencer
using the `uvm_declare_p_sequencer macros.
• Being type specific, you will be able to access anything added to the sequencer (i.e. pointers to other
sequencers, etc.).
• p_sequencer will not exist if the `uvm_declare_p_sequencer macros isn’t used.
• Internally, in start method, this child class object is assigned into parent handle called m_sequencer.
• So, a static casting occurs such that a parent class handle points to child class object
(m_sequencer = user_defined_sequencer_object).
• Now, when referring to sequence, if a p_sequencer is defined, the macro `uvm_declare_p_sequencer expands
to a function that declares a user_defined_sequencer handle known as p_sequencer.
• This function then casts the m_sequencer (parent class handle) back to p_sequencer (child class handle)
using dynamic casting ($cast).
m_sequencer vs p_sequencer
m_sequencer p_sequencer
• It's set automatically when you call • Implement a function to set a value
start()
Virtual sequence
&
Virtual sequencer
Why Sequence is not directly connected to Driver in UVM
• In SV, the Transgenerator is randomizing all the transactions sending all these transactions
at a time to the driver through mailbox.
• Here we don’t know that driver is getting all transactions and sending to DUT, the driver
doesn’t give any response
• In UVM , the sequence is generating and randomizing each transaction, before sending
transaction to driver it sends request to driver through sequencer
• Driver gives response like get_next_item and item_done responses to sequence through this
sequencer
What is Virtual Sequence
RD_sequence
APB_SEQ
VIRTUAL WR_sequence
SEQUENCE
AHB_SEQ RD_sequence
WR_sequence
AXI_SEQ
RD_sequence
• 2nd Approach: Virtual sequence will run on Virtual Sequencer, Virtual sequencer will
contain sequencers handle(with virtual sequencer)
Virtual Sequence implementation approach-1
(without virtual sequencer & starting virtual
sequence on null )
Virtual Sequence implementation
• Virtual Sequence declaration which includes target Sequencers handles
• The way a Virtual Sequence starts the Sub-Seqs on target Sequencers
• The way a Virtual Sequence is started from a Test class
// Virtual Sequencer
Class class virtual_seqr extend uvm_sequencer;
`uvm_component_utils(virtual_seqr) • Virtual Sequencer
// Target Sequencer Handles i.e. “virtual_seqr” class is declared
ahb_seqr SQR_AHB; by extended the UVM base
axi_seqr SQR_AXI; class uvm_sequencer.
// Constructor
• Target Sequencer handles are also
function new (string name = "virtual_seqr",
uvm_component parent); declared inside it.
super.new(name, parent);
endfunction: new
endclass: virtual_seqr
Virtual Sequence class
// Base Virtual Sequence // Virtual Sequence
class base_vseq extends uvm_sequence #(uvm_sequence_item); class my_vseq extends base_vseq; `uvm_object_utils(my_vseq)
`uvm_object_utils(base_vseq) // Constructor
// Virtual Sequencer Handle function new (string name = "my_vseq");
virtual_seqr v_sqr; super.new(name);
// Target Sequencers Handle endfunction: new
ahb_seqr SQR_AHB; // Body Task(starting the sub-sequences)
axi_seqr SQR_AXI; task body();
// Constructor // Assigning the Sub-Sequencer Handles
function new (string name = "base_vseq"); super.new(name); super.body;
endfunction: new // Sub-Sequence Creation & Execution
// Body Task (Assign target sequencers handle) ahb_sequence ahb_seq; axi_sequence axi_seq;
task body(); ahb_seq = ahb_sequence::type_id::create("ahb_seq");
if (!$cast(v_sqr, m_sequencer)) axi_seq = axi_sequence::type_id::create("axi_seq");
begin repeat(30)
`uvm_error(get_full_name(), "Virtual Seqr pointer cast failed") begin
end ahb_seq.start(SQR_AHB); axi_seq.start(SQR_AXI);
SQR_AHB = v_sqr.SQR_AHB; end
SQR_AXI = v_sqr.SQR_AXI; endtask: body
endtask: body endclass: my_vseq
endclass: base_vseq
First, a Base Virtual Sequence will be declared & later Virtual Sequence will be derived from the base virtual
sequence.
Environment class
// Environment
Class class Environment extends uvm_env;
`uvm_component_utils(Environment)
// Virtual Sequencer Handle
• In the Environment class
virtual_seqr v_sqr;
// Agents Handles i.e. “Environment”, Virtual Sequencer
ahb_agent AHB_AGNT; is instantiated & built along with two
axi_agent AXI_AGNT;
// Constructor
Agents
function new (string name = "Environment", uvm_component parent); i.e. “AHB_AGNT” & “AXI_AGNT”.
super.new(name, parent); • Target Sequencer handles are also
endfunction: new
// Build Phase assigned in the connect_phase().
function void build_phase (uvm_phase phase); • Usage of a flexible & handy feature of
v_sqr = virtual_seqr::type_id::create("v_sqr");
UVM i.e. “m_sequencer” is being
AHB_AGNT=ahb_agent::type_id::create("AHB_AGNT");
AXI_AGNT = axi_agent::type_id::create("AXI_AGNT"); shown which by default points to the
endfunction: build_phase UVM Sequencer derived from
// Connect Phase
function void connect_phase (uvm_phase phase);
the uvm_sequencer.
v_sqr.SQR_AHB = AHB_AGNT.m_sequencer;
v_sqr.SQR_AXI = AXI_AGNT.m_sequencer;
endfunction: connect_phase
endclass: Environment
Environment class
// Main Test
class Test extends uvm_test;
`uvm_component_utils(Test)
// Instantiations
my_vseq vseq; • Virtual Sequence is started on the
Environment Env; Virtual Sequencer from the Test
// Constructor
function new (string name = "Test", uvm_component parent = null);
super.new(name, parent); • In the Test class i.e. “Test”, both
endfunction: new
// Build Phase Environment & Virtual Sequence
function void build_phase (uvm_phase phase); i.e. “Environment” & “my_vseq”
Env = Environ::type_id::create("Env");
endfunction: build_phase are instantiated and created.
// Run Phase
task run_phase (uvm_phase phase);
// Create the Virtual Sequence & Environment • Finally, Virtual Sequence is started
vseq = my_vseq::type_id::create("vseq"); on the Virtual Sequencer which
phase.raise_objection(this);
// Start the Virtual Sequence exists inside the Environment.
vseq.start(Env.v_sqr);
phase.drop_objection(this);
endtask: run_phase
endclass: Test
Sequence arbitration
UVM Sequence Arbitration Mechanism
• Multiple sequences can interact concurrently with a driver connected to a single interface.
• The sequencer supports an arbitration mechanism to ensure that at any point of time only one
sequence has access to the driver.
• The choice of which sequence can send a sequence_item is dependent on a user-selectable
sequencer arbitration algorithm.
• There are six built-in sequencer arbitration mechanisms that are implemented in UVM.
• There is also an additional hook to implement a user-defined algorithm.
• The sequencer has a method called set_arbitration() that can be called to select which
algorithm the sequencer should use for arbitration.
Contin,,,
• The six algorithms that can be selected are as follows:
1.SEQ_ARB_FIFO (Default if none specified).
• If this arbitration mode is specified, then the sequencer picks sequence items in a FIFO order
from all sequences running on the sequencer.
• Example: if seq1, seq2, and seq3 are running on a sequencer, it will pick an item from seq1 first,
followed by seq2, and then seq3 if available, and continue.
2.SEQ_ARB_WEIGHTED
• If this arbitration mode is selected, sequence items from the highest priority sequence are always
picked first until none available, then the sequence items from the next priority sequence, and so
on.
• If two sequences have equal priority, then the items from them are picked in random order.
Continu,,,
3.SEQ_ARB_RANDOM
• If this arbitration mode is selected, sequence items from different sequences are picked in random
order by ignoring all priorities.
4.SEQ_ARB_STRICT_FIFO
• This is similar to SEQ_ARB_WEIGHTED except that if two sequences have the same priority,
then the items from those sequences are picked in a FIFO order rather than in random order.
5.SEQ_ARB_STRICT_RANDOM
• This is similar to SEQ_ARB_RANDOM except that the priorities are NOT ignored.
• The items are picked randomly from sequences with the highest priority first followed by next and
in that order.
6.SEQ_ARB_USER
• This algorithm allows a user to define a custom algorithm for arbitration between sequences.
• This is done by extending the uvm_sequencer class and overriding
the user_priority_arbitration() method.
How to prioritize a sequence?
• The priority is specified by passing an argument to the start() method of the sequence.
• The priority is decided based on relative values specified for different sequences.
For Example:
• If two sequences are started as follows, the third argument specifies the priority of the
sequence.
• seq_1.start(m_sequencer, this, 700); //Highest priority
• seq_2.start(m_sequencer, this, 500); //Next Highest priority
• seq_3.start(m_sequencer, this, 300); //Lowest priority among three sequences
Sequencer and driver
handshaking
Sequencer and driver communiction
• In UVM, there is a mechanism to be followed when we want to send the transactions from the
sequencer to the Driver in order to provide stimulus to the DUT.
• The transfer of request and response sequence items between sequences and their target driver is
facilitated by a TLM communication mechanism implemented in the sequencer.
• A particular Sequence is directed to run on a Sequencer which in turns further breaks down into a
series of transaction items
• These transaction items are needs to be transferred to the Driver where these transaction items
are converted into cycle based signal/pin level transitions.
Sequencer side operation
• To send a sequence_item to a driver there are four steps that need to occur:
Step 1 – Creation: Creating the “transaction item” with the declared handle using factory
mechanism.
Step 2 - Ready - start_item():The start_item() call is made, passing the sequence_item handle as an
argument. This call blocks until the sequencer grants the sequence and the sequence_item access to
the driver.
Step 3 – Set:The sequence_item is prepared for use, usually through randomization, but it may also
be initialised by setting properties directly.
Step 4 - Go - finish_item(): The finish_item() call is made, which blocks until the driver has
completed its side of the transfer protocol for the item. No simulation time should be consumed
between start_item() and finish_item().
Step 5 - Response - get_response():This step is optional, and is only used if the driver sends a
response to indicate to indicate that it has completed transaction associated with the
sequence_item. The get_response() call blocks until a response item is available from the
sequencers response FIFO.
Sequencer and driver communiction
• These are the operational steps from a Sequence which we want to execute using a Sequencer
that is connected to a Driver inside an “Agent”.
• Whole of this process is shown in the Figure 1 & Figure 2 below:
Fig: Driver & Sequencer Interaction for Fig: Transaction Execution Flow Between a Sequencer,
Transaction Exchange Driver & Virtual Interface
Driver side operation
• Steps are made by the Driver in order to complete the communication with Sequencer(Sequence)
Declaring the “transaction item” with a handle.
get_next_item(): Calling the “get_next_item(<transaction_item_handle>)“.
Default transaction_handle is “req”. “get_next_item()” blocks the processing until the “req” transaction
object is available in the sequencer request FIFO & later “get_next_item” returns with the pointer of the
“req” object.
try_next_item(): This is a non-blocking variant of the get_next_item() method.
It will return a null pointer if there is no REQ sequence_item available in the sequencers request FIFO.
However, if there is a REQ sequence_item available it will complete the first half of the driver-
sequencer handshake and must be followed by an item_done() call to complete the handshake.
Next, Driver completes its side protocol transfer while working with the virtual interface.
item_done(): Calling the “item_done()” OR “item_done(rsp)“.
It indicates to the sequencer the completion of the process. “item_done” is a non-blocking call & can be
processed with an argument or without an argument.
If a response is expected by the Sequencer/Sequence then item_done(rsp) is called.
It results in Sequencer response FIFO is updated with the “rsp” object handle.
TLM PORTS
Introduction
new()
used()
put() try_put() transport() Unidirectional export
uvm_analysis_export#(T) is_empty()
uvm_*_export#(T)
uvm_analysis_port#(T)
get() can_put() nb_transport() Bidirectional export uvm_analysis_import#(T) is_full()
uvm_*_export#(REQ, RSP)
try_get()
peek() flush()
can_get() Unidirectional port Unidirectional import
uvm_*_port#(T) uvm_*_import#(T)
try_peek() Bidirectional port Bidirectional import Unidirectional import
uvm_*_port#(REQ, RSP) uvm_*_import#(REQ, RSP) uvm_tlm_fifo#(T)
can_peek() Bidirectional import
uvm_tlm_analysis_fifo#(T)
TLM interface put methods
• put() is one of the TLM method which can be used to communicate between a Producer&
a Consumer.
• port is the interface which calls put() method and export is the interface which provides the
implementation of put() method.
• That way, TLM communication is independent of the Producer and Consumer abstraction level.
/////////// Producer ////////////////////////// /////////// Consumer //////////////////////////
class producer extends uvm_component; class consumer extends uvm_component;
`uvm_component_utils(producer) `uvm_component_utils(consumer)
uvm_blocking_put_port #(txn) my_port; uvm_blocking_put_imp #(txn, consumer) my_export;
function new (string name, uvm_component parent);
super.new(name, parent); function new (string name, uvm_component parent);
my_port = new (“my_port”, this); super.new(name, parent);
endfunction: new my_export = new(“my_export”, this);
task run_phase(uvm_phase phase); endfunction: new
for (int packet = 1; packet<11; packet++)
begin task put (txn t);
txn t; case(t.kind)
t = txn::type_id::create(“t”, this); t.READ: $display(“Read transaction”);
`uvm_info(“PID”, $sformatf(“Packet no. %d is sent”, packet), t.WRITE: $display(“Write transaction”);
UVM_LOW) endcase
my_port.put(t); endtask: put
#10; endclass: consumer
end
endtask: run_phase
endclass: producer p1.my_port.connect(c1.my_export);
TLM interface get methods
• In terms of purpose, get() method performs the same the put() does – only difference is that get()
pulls the transaction from the Producer yet put()push the transaction to theConsumer.
/////////// Consumer ///////////////////
///////////// Producer /////////////////// class consumer extends uvm_component;
class producer extends uvm_component; `uvm_component_utils(consumer)
`uvm_component_utils(producer) //// Port Declaration
///// Export Declaration uvm_blocking_get_port #(my_txn) my_port;
uvm_blocking_get_imp #(my_txn, producer) my_export; function new (string name, uvm_component
//// Constructor parent);
super.new(name, parent);
function new (string name, uvm_component parent); my_port = new(“my_port”, this);
super.new(name, parent); endfunction: new
my_export = new(“my_export”, this); task run_phase(uvm_phase phase);
endfunction: new for (int i=1; i<11; i++)
//// get() task implementation begin
task get(output my_txn t); my_txn txn;
`uvm_info("CID", $sformatf("Transaction no. %0d is asked
my_txn tmp; for", i), UVM_LOW)
my_port.get(txn);
tmp = my_txn::type_id::create(“tmp”, this); #10;
transport
• Calling <port>.transport(req, resp) method executes the given request and returns the
response in the given output argument
• The transport method call is blocking, it may block until the operation is complete
nb_transport
• Calling <port>.nb_transport(req,resp) the method executes the given request and returns the
response in the given output argument, if possible
• If for any reason the operation could not be executed immediately, then a 0 must be
returned, otherwise 1
TLM Export
• The TLM Export is a port that forwards a transaction from a child component to its parent
• The TLM Export has unidirectional and bidirectional ports
• An export can be connected to any compatible child export or imp port.
• It must ultimately be connected to at least one implementation of its associated interface
uvm_tlm_fifo #(T)
uvm_tlm_analysis_fifo #(T)
TLM FIFO Methods
• size:The size indicates the maximum size of the FIFO,a value of zero indicates no upper bound
Calling size() returns the size of the FIFO
A return value of 0 indicates the FIFO capacity has no limit
• used: Returns the number of entries put into the FIFO
• is_empty: Returns 1 when there are no entries in the FIFO, 0 otherwise
• is_full: Returns 1 when the number of entries in the FIFO is equal to its size, 0 otherwise
• flush: Calling flush method will Remove all entries from the FIFO
after the flush method call used method returns 0 and the is_empty method returns 1
TLM Analysis_fifo
• An analysis_fifo is a uvm_tlm_fifo#(T) with an unbounded size and a write Method
• It can be used any place a uvm_analysis_imp is used
• Typical usage is as a buffer between a uvm_analysis_port in an initiator component and TLM1
target component
The analysis_export provides the write method to all connected analysis ports and parent exports
analysis_export #(T)
Method:function void write (T t)
• Write() method:
• Calling <port>.write(trans) method will broadcasts a transaction to any number of listeners.
• Write method call is a nonblocking call
• As along with the interface methods, TLM ports are required for the communication between
the components.