Uvm by Praveen
Uvm by Praveen
Uvm by Praveen
Topics covered:
1. UVM methodology
2. UVM factory
3. Factory overriding
4. Stimulus modelling
5. UVM phases
6. Reporting mechanism
7. TLM overview
8. UVM configuration
9. Virtual interface configuration
10. Test bench components
11. UVM sequencer
UVM Methodology:
UVM – universal verification methodology, introduced in 2011.
UVM is all about automation of the test bench.
It provides a frame work for creating modular, reusable testbench components that can be easily integrated into the
design verification process.
It includes a set of guidelines and best practices for developing test benches, as well as methodology for running
simulation and analysing results.
OVM – Open verification methodology, introduced in 2008.
Singleton class – using this class only one object can be created. Eg: test class
SV is good for IP verification and UVM is suitable for IP verification and SOC verification also.
PDC’s – protocol dependent components. Eg: Transactors (driver, monitor, sequencer)
UVC – universal verification component. Eg: agent class
Sequence means sequence of packets. Handle or instance means both are same.
In UVM library except uvm_driver, uvm_sequencer all are virtual classes.
What is methodology:
1. Best practices by verification experts.
2. Base class library.
3. Standard communication mechanism to achieve interoperability.
4. Configuration of the testbench from the top level.
5. Generate sequences independent of TB environment.
6. Achieve reusability in plug and play manner.
Typical UVM Testbench hierarchy:
Agent has monitor, driver (if active type agent) and sequencer (if active type agent).
Agents will be kept inside the agent_top, it is called UVC component repository.
UVM environment:
Under the environment component there will be agents, virtual sequencer, scoreboard, uvm subscriber.
Driver – it converts sequence_item into the pin level waggles for DUT.
Monitor – it converts pin level data from the DUT into transaction, for the use in scoreboard, coverage models, etc.
Configuration object – A container object, used to pass information to the agent which affects what it does and how
it is built and connected.
UVM Hierarchy:
Classes used for creating test bench hierarchy are called components.
It is a class that manufacture (create) UVM components and objects during run time.
Only the derived class can be overridden directly, base/parent class can’t be overridden, as they are virtual type.
Factory registration:
1. Using factory registration macro:
`uvm_component_utils – for component class.
`uvm_object_utils – for stimulus and sequences.
T num = N;
function new(string name = "seq"); // default constructor for the object class
super.new(name);
endfunction
If bit replace = 0, then existing override will not be replaced by the new one. in other words, if there is already an
override for a given type or instance, the new override request will be ignored, preserving the existing override.
Stimulus modelling:
The methods like print, copy, compare,clone etc can be enabled by using field macros or by overriding the
corresponding virtual methods like do_print, do_copy, do_compare.
Transaction class has to be extended from the uvm_sequence_item class.
It represents the main transaction input to the DUT based on the protocol of the Design.
Steps to define stimulus:
1. Derive a data item class from uvm_sequence_item base class
2. Register with the factory using `uvm_object_utils macro
3. Define a constructor for the data item.
4. Add control fields for the items such as constraints
5. UVM field macros to enable printing, copying, comparing etc.
Field macros are used to register the class properties(fields) so, that these properties will be enabled for various pre-
defined methods like copy, compare and print etc.
`uvm_*_utils_end
Virtual methods:
virtual function void do_copy(uvm_object rhs);
packet2 rhs_;
if(! $cast(rhs_,rhs)) `uvm_fatal("do_copy","cast of the rhs object failed")
super.do_copy(rhs);
this.num = rhs_.num;
this.names = rhs_.names;
this.packet1_result = rhs_.packet1_result;
endfunction
UVM phases:
There are 3 major groups of phases,
1. build phases -> tb is configured and constructed.
2. Run phases/ run time phases -> where time is consumed in running the test cases.
3. Clean up phases -> where the results of the test case are collected and reported.
Build phase and final phase are top-down approach, remaining phases are
bottom-up approach except run phase.
Run phase parallel execution, it is time consuming phase. It has sub-run phases.
Shotcut-> BC ES R EC RF
Object class does not have phases, only the component classes has the phases.
Calling run_test(), construct the uvm environment root component and then
initiates the uvm phasing.
Note: The delay in the ‘run_phase’ of components has to be more than the delay in the test class ‘run_phase’
class collector1 extends uvm_component; // in this class we used the 'delay phase end'
`uvm_component_utils(collector1) // refer the code-> objections_delay.sv
Reporting mechanism:
These are two types -> (i) reporting methods (not recommended), (ii) using uvm_macros
It helps in debugging the environment with many agents, control the display messages using their names and can
filter the displaying messages.
Verbosity levels -> UVM_NONE (least verbosity), UVM_LOW, UVM_MEDIUM, UVM_HIGH, UVM_FULL, UVM_DEBUG
(highest verbosity).
UVM_macros:
`uvm_info(“id”,$sformatf(“bjjgwjgw %d”,val),UVM_NONE)
./simv+uvm_set_verbosity=component_name,id,UVM_HIGH,phase_name,option_all_time
Comph.set_report_severity_action(UVM_WARNING,UVM_DISPLAY+UVM_EXIT);
Comph.set_report_verbosity_level(UVM_LOW);
Initial begin
Xtn = new(“Xtn”); // this setting verbosity can be done in component classes only.
Xtn.set_report_verbosity_level(UVM_LOW); //it is present inside the uvm_report_object
TLM (Transaction Level Modeling):
It is all about interoperability (mixed language verification environment) and reusability (maximizes reuse and
minimizes the time and effort).
Export/implementation port: it takes from higher level of abstraction and forwards to another abstraction. It
forwards transaction from one component to another component. Fifo’s has the implementation ports.
Analysis port: it broad castes the data, and later it is received by the single or multiple components where the
analysis implementation is used.
Analysis export: it
In TLM the ‘initiator’ is like a host and the ‘target’ is like the audience.
‘Ports’ have the set of methods like the get(), put(), peek() etc,. and ‘exports’, ‘imp’ has the implementation of the
above ports.
By using TLM the following cases are possible, but the 4th case is not possible:
cases Generator Driver Here in this the case 4 is not possible with the TLM.
1 Initiator Target
By using the mail box we can do the case1, and case 2
2 Target Initiator
3 Initiator Initiator only but the case 3 is not possible with the mail box.
4 Target Target
Blocking get port:
class consumer extends uvm_component;
`uvm_component_utils(consumer)
uvm_blocking_get_port #(trans) get_port;
trans transh;
function new(string name = "consumer", uvm_component parent=null);
super.new(name,parent);
get_port = new("get_port",this); // in ports we will call the method.
endfunction
endclass
TLM fifo:
// ====================================== tlm fifo ===============================
// |-----------| |-----------|
// | generator | |----| | driver |
// |(initiator)|[]==============O|fifo|O=================>[]|(initiator)|
// | | |----| | |
// |-----------| put_export get_export |-----------|
// put port get port
//=================================================================================
class generator extends uvm_component;
`uvm_component_utils(generator)
uvm_blocking_put_port #(trans) gen_put_port;
trans transh;
endclass
endclass
generator genh;
driver drvh;
Analysis interface:
It has single non-blocking function – write() (it is possible made as non-blocking, as here we are broad casting the
data)
function void write(input trans transh); // if we remove 'input' also it will work.
transh.print();
$display("from the scoreboard");
endfunction
endclass
function void write(input trans transh); // if we remove 'input' also it will work.
transh.print();
$display("from the coverage_block");
endfunction
endclass
endtask
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
monh.analysis_port.connect(sbh.fifoh.analysis_export);
monh.analysis_port.connect(subh.analysis_export);
endfunction
endclass
UVM Configuration:
Configuration data base -> maintains associative memory; associative array look up table.
‘uvm_config_db’ it is a parameterized class, here the parameter may be any type like class object or builtin data
types like int, bit, byte --- etc,.
If we use the ‘uvm_config_db’ with same name then the simulator will consider the last one only.
We have to use the ‘set’ and ‘get’ static methods in the ‘build_phase’ only. Here the ‘set’ method is to set the data
base and get method is to access the data base.
bit uvm_config_db #(type T=int)::get(uvm_component cntxt, string inst_name, string field_name, ref T value);
Here consider first 3 arguments we can store in associative array, the 3 arguments are uvm_component context,
string instance_name, string field_name, lastly variable.
Eg: uvm_config_db #(ram_cfg)::set(this,”*”,”ram_config”,tb_cfg); // if we set in the module then use ‘null’ as scope.
uvm_config_db #(ram_cfg)::get(this,””,”ram_config”,tb_cfg_drv);
Hierarchical path:
*.agent* // visibility from agent[1] onwards to bottom of the test bench
* // visibility will bottom to the test bench components from where the config_db is set.
uvm_config_db #(drv_cfg)::set(this,"*drvh","cfgh",cfgh);
endfunction
endclass
//------------------------------------------------------------------------------------------------------
module top;
vif vifh();
// dut instantiation
initial begin
uvm_config_db #(virtual vif)::set(null,"*","vif",vifh);
run_test("test");
end
endmodule
TB components:
UVM Sequencers:
It is a non-virtual class, so we can make our own sequencer (which extends from uvm_sequencer #(trans) ) or we can
use predefined uvm_sequencer in the agent. Here bidirection communication is possible.
It routes the sequence_item’s to the driver. It also does the sequence arbitration (it means when multiple sequences
are starting, that time sequencer randomly pick one and first send it to the driver.) it manages the sequences.
Sequencer and driver connection through the built-in TLM ports, driver has default ‘sequence_item_port’, sequencer
has the ‘sequence_item_export’. the connection is -> drvh.seq_item_port.connect(seqrh.seq_item_export);
In sequencer code, factory registration and parameterization of uvm_sequencer with ‘transaction’ class is enough.
To create user sequencer extends ‘uvm_sequencer’ base class, which is parameterized by request and response item
types. So, that the arbitration and routing logics will be defined in the base class.
UVM Driver:
Fetches data repeatedly from sequencer. Driver dut based on the protocol using the virtual interface.
To create driver:
1. Derive a ‘driver’ from the ‘uvm_driver’ base class and parameterized with ‘transaction’ class.
2. Declare ‘virtual interface’ to connect the driver to the ‘dut’.
3. Obtain the next data item from the sequencer and execute it according to the protocol this is written in
driver ‘run_phase’.
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive_to_dut(req);
seq_item_port.item_done();
end
endtask
The uvm_seq_item_pull_port class of driver has two methods
This can be achieved by: (here rsp is handle of transaction class type)
1. .item_done(); -> Acknowledgement, saying driving completed. Won’t send response to sequencer.
2. .item_done(rsp); -> non-blocking type, sends response to sequencer plus tells driving completed.
3. .put_response(rsp); -> blocking type, sends response to sequencer plus tells driving completed after
sequencer again has to give intermediate response using method ‘.get_response(rsp);’
4. rsp_port.write(rsp) -> built-in analysis port in driver, so driver can broad cast the response back to the
sequencer.
Note: before providing the response, the response’s sequence and transaction id must be set to correspond to
the request transaction using rsp.set_id_info(req)
UVM Sequence:
Sequence just randomize the data only.
Minimize the protocol/environment knowledge requirements. In sequence we are not following any protocol for
operation. So, it is simply we separating it from components and we call it as object.
Generating stimulus based on constraints, stimulus generation on the fly or at time zero.
Can nest sequences. Here in sequences has body, prebody, postbody tasks. m_sequencer, it is the handle of
sequence base class. Inside the uvm_sequence, uvm_driver, uvm_sequencer has rsp & req which are handles of
transaction class.
We have to create the p_sequencer if we want to use it. p_sequencer handle not even present in out TB until we
declare it manually.
The main difference between m_seqeuncer and p_sequencer in one line is the parent and child class handle types.
m_sequencer is of parent type and p_sequencer is of child type.
m_seqeuncer is the handle of a base class type “uvm_sequencer_base” , which is parent class of "uvm_sequencer".
For p_sequencer handle to declare we call macro inside sequence like this, `uvm_declare_p_sequencer(sequencer).
This macro declares p_sequncer handle of type sequencer which is passed and dynamically casts p_sequencer to
m_sequencer. If we don’t call this macro p_sequencer does not exist. We use p_sequencers inside
our sequences to access the methods or properties of user defined sequencer if needed.
Sequence item flow:
Sequence sequencer driver:
task body();
repeat(2) begin
`uvm_info(get_type_name,"starting the seq class to the m_sequencer",UVM_NONE) ///////////// step 2.1
seqh.start(m_sequencer); // it will call the seqh, or we can call multiple sequences.
end
endtask
endclass
/////////////////////////////////////////////////////////////
class driver extends uvm_driver #(trans);
`uvm_component_utils(driver)
Using macros: starting sequence item without using the ‘start method’
If the user doesn’t want to use ‘start_item & finish_item’ in the sequence class that time we use these
“`uvm_do(req); & `uvm_do_with(req,<condition>);”. Here the disadvantage is it doesn’t call the ‘pre_body’ and
‘post_body’ methods of the sequence. So, it is not recommended.
1. `uvm_do();
2. `uvm_do_with(); // here we can pass the in-line command.
3. class do_macro_seq extends uvm_sequence #(trans);
4. `uvm_object_utils(do_macro_seq)
5. seq seqh;
6. function new(string name="do_macro_seq");
7. super.new(name);
8. endfunction
9.
10. task body();
11. repeat(2) begin
12. `uvm_info(get_type_name,"starting the seq class using do_macro",UVM_NONE)
13. `uvm_do(seqh); //////////////////// step 3.1
14. end endtask
15. endclass
16.
17. class do_macro_seq1 extends uvm_sequence #(trans);
18. `uvm_object_utils(do_macro_seq1)
19.
20. function new(string name="do_macro_seq1");
21. super.new(name);
22. endfunction
23.
24. task body();
25. repeat(2) begin
26. // `uvm_do(req); //////////////////// step 4.1, without using the inline constraint.
27. `uvm_do_with(req,{a == 0;}); //////////////////// step 4.1, using inline constraint.
28. `uvm_info(get_type_name,"sequence item using do_macros",UVM_NONE)
29. req.print();
30. end endtask
31. endclass
Sequence configuration:
If we want to use the configuration data base in the sequence that time we go for this method.
Here we set the config_db in the ‘test class’ and we get the config_db in the sequence class, where we want to
implement. In sequence class in ‘pre_body’ method we get the config_db.
class seq_config extends uvm_object;
`uvm_object_utils(seq_config)
int repeat_count;
task pre_body();
if(! uvm_config_db #(seq_config)::get(null,get_full_name(),"seq_config",m_seq_configh)) //here we use 'null'
as there is no hierarchy for the sequence class. Here 'get_full_name' fetches the instance path for the sequence based
on the sequencer on which this sequence is started.(like envh.agth.seqrh)
`uvm_info(get_type_name(),"failed to get",UVM_NONE)
endtask
task body();
repeat(m_seq_configh.repeat_count) begin
`uvm_do(req);
`uvm_info(get_type_name,"getting the configuration in the sequence class",UVM_NONE)
req.print();
end
endtask
endclass
///////////////////////////////////////////////////////////
class test5 extends test; // getting configuration to the transient class(objects), where there is no hierarchy
`uvm_component_utils(test5)
seq_config seq_configh;
seq_config_sequenceh = seq_config_sequence::type_id::create("seq_config_sequenceh");
endfunction
Note: we should ‘set’ it before ‘run_phase’ that’s why we are using start_of_simulation_phase.
Disadvantage is we don’t have time control, because this default sequence start, then after ‘run_phase’ we start
execution parallely.
So, if we want to implement this ‘default sequence’ then in the driver use ‘sub run_phases’ instead of run_phases.
And in the default sequence, give path to ‘reset phase’ and for remaning code write in the ‘main_phase’
class default_seq extends uvm_sequence #(trans);
`uvm_object_utils(default_seq)
task body();
if(starting_phase != null) starting_phase.raise_objection(this,"starting my_seq"); // sequence objection
`uvm_info(get_type_name,"inside of the default sequence",UVM_NONE) // here we have to write the body of the
sequence.
`uvm_info(get_type_name,"outside of the default sequence",UVM_NONE)
if(starting_phase != null) starting_phase.drop_objection(this,"ending my_seq");
endtask
endclass
///////////////////////////////////////////////////////
class test extends uvm_test; // here default sequence setting and topology
`uvm_component_utils(test)
env envh;
default_seq default_seqh;
function void start_of_simulation_phase(uvm_phase phase); // here we will start the default sequence
super.start_of_simulation_phase(phase);
// uvm_config_db
#(uvm_object_wrapper)::set(this,"envh.seqrh.run_phase","default_sequence",default_seq::type_id::get()); // using
wrapper
The uvm_sequnece_library class is inherited from uvm_sequence which means that an instance of a sequence library
is also a sequence.
Similar, to uvm_sequence, uvm_sequence_library is also a parameterized class which is parametrized with req and
rsp which is of uvm_sequence_item.
It can be configured to create and execute the registered sequences any number of times using one of several
modes of operation, including a user-defined mode.
Note: The sequences that do not require driver access or execute sequence_items using “start_item” and
“finish_item”. It do not have to undergo arbitration. So, if multiple plain sequences are started on a sequencer, they
will be executed in parallel similar to default sequence.
Sequence library mode:
type_def enum{UVM_SEQ_LIB_RAND, UVM_SEQ_LIB_RANDC,
*_USER is for apply a user defined random selection algorithm, user can override this method using built-in method
‘select_sequence’ function inside the ‘sequence_library’ class.
uvm_config_db#(uvm_object_wrapper)::set(this,”env.agent.sequencer.main_phase”,”default_
seqence”,my_seq_lib::get_type());
uvm_config_db(uvm_sequence_lib_mode)::set(this,”env.agent.sequencer.main_phase”,”defaul
t_sequence.selection_mode”,UVM_SEQ_LIB_RANDC);
This above code is written in build_phase of test class or run_phase.
It is used when we want to set it as default sequence library, like without using “seq_lib.start(---)”, without
creating object I want to set default sequence.
2. method-2: when there is instance of a library, with creating object I want to set a default sequence.
class test extends uvm_test; // for sequence_library
`uvm_component_utils(test)
env e;
seq_library seqlib;
seqlib.selection_mode = UVM_SEQ_LIB_RANDC;
seqlib.min_random_count = 5;
seqlib.max_random_count = 9;
`uvm_object_utils(seq1)
//uvm_add_to_seq_lib(seq1, seq_library)
class seq6 extends uvm_sequence #(transaction); // it is the extra sequence that we are going to add in the 'run time'
`uvm_object_utils(seq6)
init_sequence_library(); //needed to populate the sequenc library with any sequences that were statically
registered with it or any of its base classes
// assert(seqlib.randomize());
endfunction
endclass
/////////////////////////////////////////////////
driver d;
uvm_sequencer#(transaction) seqr;
endclass
/////////////////////////////////////////////////////////////////////////////
class env extends uvm_env;
`uvm_component_utils(env)
agent a;
endclass
///////////////////////////////////////////////////////////////////
class test extends uvm_test; // for sequence_library
`uvm_component_utils(test)
env e;
seq_library seqlib;
seqlib.selection_mode = UVM_SEQ_LIB_RANDC;
seqlib.min_random_count = 5;
seqlib.max_random_count = 9;
// seqlib.init_sequence_library();
seqlib.add_sequence(seq6::get_type()); // it is the extra sequence that we are going to add in the 'run time' or
in run_phase of the 'sequence library'
seqlib.print();
endfunction
sequencer arbitration:
when sequences are executed in parallel, sequencer will arbitrate and pick the sequences accordingly.
fork
seq1h.start(envh.agth.seqrh,,100); // here the 3rd argument is ‘priority’
seq2h.start(envh.agth.seqrh,,400); // second argument is ‘parent sequence’ defaultly pointed
to null
seq3h.start(envh.agth.seqrh,,700); // if priority is -1, it indicates no priority.
join
phase.drop_objection(this);
endtask
If two sequences have same priority then based on arbitration one is chosen.
Parallel sequence: the sequence that activates the two sequence in parallel.
class concurrent_seq extends uvm_sequence #(write_xtn);//parallel or nested sequences.
`uvm_object_utils(concurrent_seq)
seq1 seq1h;
seq2 seq2h;
task body();
seq1h = seq1::type_id::create("seq1h");
seq2h = seq2::type_id::create("seq2h");
fork
seq1h.start(m_sequencer);
seq2h.start(m_sequencer);
join
endtask
endclass
uvm_callbacks:
Consider a case, in which the driver is implemented to drive the stimulus generated in the sequence.
Most of the test cases requires the normal behavior of the driver. So, we can make use of this driver.
But, some special test cases may require to delay the transaction driving to the DUT or injecting the errors in the
transactions or sending back the responses to the sequences after completion of driving.
So, these special test cases require different type of driver. i.e., we need to change the functionality of the driver's
run_phase().
Callback mechanism will help us for altering the behavior of the transactor without modifying the transactor.
By, using callbacks we can change the behavior of existing component or object without having a necessary to
modify its source code, we can modify its behavior using call back mechanism. Eg: post_randomize, pre_randomize
etc.
uvm events:
In uvm, events are linked with call back methods, it is the difference in system Verilog events and uvm events.
1. I have 2 agents A & B. agent A drives the control signal and agent B drives data packets according to that
control signal.
2. Now, I want to use event – based synchronization between agent A and B, so that data packet is driven only
after driving the control signals.
3. Consider a case, where there should be a synchronism between driver and monitor.
4. We can manage to establish such synchronism between multiple components in ‘env’ by using
‘uvm_event’(scalar) and ‘uvm_event_pool’(vector).