Uvm Concepts

Download as pdf or txt
Download as pdf or txt
You are on page 1of 68

UVM CONCEPTS

Topics

➢Introduction ➢m_sequencer & p_sequencer


➢UVM class hierarchy ➢UVM Callbacks
➢UVM Architecture ➢Driver sequencer handshaking
➢UVM Phases ➢Virtual sequencer & Virtual sequence
➢UVM Reporting ➢UVM Sequence Arbitration
➢Config_db ➢TLM
➢Stimulus generation ➢RAL
Introduction
➢ UVM is a methodology based on SystemVerilog language and is not a language on its
own. It is the widely used verification methodology with standardized set of rules.

➢ It supports SV based libraries and also contains set of base classes with methods
defined in it, useful for the development of well constructed, reusable SystemVerilog
based Verification environment.

➢UVM was created by Accellera based on OVM version (Open verification methodology)

➢It supports synchronizing the phases, factory concepts, configuration classes, reporting
mechanisms, TLM classes, utility macros, field macros and other useful mechansms.

➢The sequencer-driver handshake mechanism is taken care of under the hood so that
only stimulus needs to be written.
This methodology should support:

▪ Transaction based verification(TBV)


▪ Coverage driven verification(CDV)
▪ Constrained Random Testing(CRT)
▪ Assertion Based Verification(ABV)

Advantages:

• Common testbench structure and run flow


• Reusability through testbench
• Time required to build testbench is very less
• Avoids poor coding practices
• Debugging simplicity
UVM class hierarchy
uvm_void: Base class for all uvm classes. It is top of the class inheritance hierarchy.

uvm_object: Base of all UVM data and hierarchical classes. Primary role is to define a
set of methods for such common operations such as create, compare, print, copy and
record. They are dynamic entities.

uvm_transaction: Used in stimulus generation and analysis. It is the root class for
transactions and extends uvm_object to include timing and recording interfaces. Simple
transactions derived from uvm_transaction and sequence enabled are from
uvm_sequence_item.

uvm_report_object: It provides an interface to the UVM reporting facility. Reporting


consists of
(string id,string message,int verbosity=UVM_LOW, string filename="",int line=0).

uvm_component: All major testbench components are derived from this corresponding
base class. They exists through out the simulation time.
UVM Architecture
Sequence_item: uvm_sequence_item is of object type. It contains data fields for generating
stimulus. These stimulus are randomized in sequence, which are declared as rand. It defines
the pin level activity.
Data fields represent the following types of information,

1.Control Information – a type of transfer, transfer size, control signals.


2.Payload Information – data content of the transfer.
3.Configuration Information – mode of operation, error behavior, etc
4.Analysis Information – fields used to capture information from DUT,

EX:
class s_seq_item extends uvm_sequence_item; bit r_w, rst;

`uvm_object_utils(s_seq_item) function new(string name="s_seq_item");


super.new(name);
randc bit [3:0]addr; endfunction
randc bit [7:0]data_in;
endclass
bit [7:0] data_out;
Sequence: uvm_sequence generates a series of sequence items sends to driver via sequencer.
It is derived from uvm_sequence_item and written by extending uvm_sequence. It is
parameterized with the type of sequence_item.

Ex:
class s_seq extends uvm_sequence #(s_seq_item);

`uvm_object_utils(s_seq) assert(pkt.randomize());
pkt.r_w=1;
s_seq_item pkt; pkt.print();
finish_item(pkt);
function new(string name="s_seq");
super.new(name); #10
endfunction start_item(pkt);
pkt.r_w=0;
task body(); pkt.print();
finish_item(pkt);
pkt=s_seq_item::type_id::create("pkt"); `uvm_info("seq","seq trans",UVM_NONE);
repeat(10) end
begin
#10; endtask
start_item(pkt); endclass
Sequencer: It is written by extending uvm_sequencer parameterized with sequence_item.
It is responsible for routing the sequence_item between sequence and driver. Sequencer
and driver uses TLM interface to communicate using connect method in agent.

drv.seq_item_port.connect(seqr.seq_item_export);

Ex:

class s_seqr extends uvm_sequencer #(s_seq_item);

`uvm_component_utils(s_seqr);

function new(string name="s_seqr",uvm_component


parent);
super.new(name,parent);
endfunction

endclass
UVM Driver: Written by extending uvm_drviver and inherited from uvm_component. It
has TLM ports and methods for communication between driver and sequencer. It is
parameterized with sequence_item for type of request & type of response.

UVM driver methods:

get_next_item():This method blocks until a requested 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(): It is non-blocking method completes the driver-sequencer handshake and
it should be called after a get_next_item() or a successful try_next_item() call.
Ex:

class s_drv extends uvm_driver #(s_seq_item);


task run_phase(uvm_phase phase);
`uvm_component_utils(s_drv) pkt=s_seq_item::type_id::create("pkt");
forever
s_seq_item pkt; begin
virtual sram_if intf; seq_item_port.get_next_item(pkt);
function new(string name="s_drv",uvm_component parent); intf.data_in=pkt.data_in;
super.new(name,parent); intf.addr=pkt.addr;
endfunction intf.r_w=pkt.r_w;
pkt.rst=intf.rst;
virtual function void build_phase(uvm_phase phase); seq_item_port.item_done();
super.build_phase(phase); `uvm_info("DRV","DRV Trans",UVM_NONE)
uvm_config_db #(virtual sram_if)::get(this,"","intf",intf);
endfunction end
endtask
endclass
UVM Monitor: The user-defined monitor is extended from uvm_monitor, it is inherited
by uvm_component. It samples DUT signals and coverts into transaction level but does
not drive them. It should have an analysis port (TLM port) and a virtual interface handle
that points to DUT signals. Collected data can be used for protocol checking and coverage.
OUTPUT MONITOR:
virtual function void build_phase(uvm_phase phase);
Ex:
super.build_phase(phase);
uvm_config_db #(virtual sram_if)::get(this,"","intf",intf);
class s_mon2 extends uvm_monitor;
endfunction
`uvm_component_utils(s_mon2)
task run_phase(uvm_phase phase);
pkt=s_seq_item::type_id::create("pkt");
s_seq_item pkt;
forever
virtual sram_if intf;
begin
#20
uvm_analysis_port #(s_seq_item) MON22SB;
pkt.data_out=intf.data_out;
`uvm_info("MON2","MON2 Trans",UVM_NONE);
function new(string name="s_mon2",uvm_component parent);
MON22SB.write(pkt);
super.new(name,parent);
end
MON22SB=new("MON22SB",this);
endtask
endfunction
endclass
INPUT MONITOR:
task run_phase(uvm_phase phase);
Ex: pkt=s_seq_item::type_id::create("pkt");
forever
class s_mon1 extends uvm_monitor; begin
#10;
`uvm_component_utils(s_mon1) pkt.data_in=intf.data_in;
pkt.r_w=intf.r_w;
s_seq_item pkt; pkt.rst=intf.rst;
virtual sram_if intf; //pkt.data_out=intf.data_out;
pkt.addr=intf.addr;
bit [7:0]mem[0:15]; begin
if(intf.rst==0)
uvm_analysis_port #(s_seq_item) MON12SB; pkt.data_out=0;
else if(pkt.r_w)
function new(string name="s_mon1",uvm_component parent);mem[pkt.addr]=pkt.data_in;
super.new(name,parent); else
MON12SB=new("MON12SB",this); pkt.data_out=mem[pkt.addr];
endfunction end
`uvm_info("MON1","MON1 Trans",UVM_NONE);
virtual function void build_phase(uvm_phase phase); MON12SB.write(pkt);
super.build_phase(phase); end
uvm_config_db #(virtual sram_if)::get(this,"","intf",intf); endtask
endfunction endclass
UVM Agent: A user-defined agent is extended from uvm_agent, uvm_agent is inherited
by uvm_component. An agent encapsulates the sequencer, driver and monitor into a single
entity by instantiating and connecting the components by TLM ports.
Active •Instantiates all three components [Sequencer, Driver, Monitor]
•Enables data to be driven to DUT via driver

Passive •Only instantiate the monitor


•Used for checking and coverage only
•Useful when there's no data item to be driven to DUT

PASSIVE AGENT:
Ex:
class s_ag2 extends uvm_agent;
`uvm_component_utils(s_ag2) virtual function void build_phase(uvm_phase phase);
s_mon2 mon2; super.build_phase(phase);
mon2=s_mon2::type_id::create("mon2",this);
function new(string name="s_ag2",uvm_component parent); endfunction
super.new(name,parent); endclass
endfunction
ACTIVE AGENT:
Ex:

class s_ag1 extends uvm_agent;

`uvm_component_utils(s_ag1)
drv=s_drv::type_id::create("drv",this);
s_seqr seqr;
mon1=s_mon1::type_id::create("mon1",this);
s_mon1 mon1;
endfunction
s_drv drv;
virtual function void connect_phase(uvm_phase phase);
function new(string name="s_ag1",uvm_component parent);
super.connect_phase(phase);
super.new(name,parent);
drv.seq_item_port.connect(seqr.seq_item_export);
endfunction
endfunction
virtual function void build_phase(uvm_phase phase);
endclass
super.build_phase(phase);
seqr=s_seqr::type_id::create("seqr",this);
UVM Scoreboard: The user-defined scoreboard is extended from uvm_scoreboard,
uvm_scoreboard is inherited by uvm_component. It will check the correctness of the DUT
by comparing the DUT output with the expected values. It will receive the transactions
from the Monitor via TLM ports & exports.
UVM Environment: The user-defined environment is derived from uvm_env, uvm_env is
inherited from uvm_component. The environment is the container class, It contains one or
more agents, as well as other components such as the scoreboard, top-level monitor, and
checker and connects them.
Ex:
class s_env extends uvm_env; ag1=s_ag1::type_id::create("ag1",this);
`uvm_component_utils(s_env) ag2=s_ag2::type_id::create("ag2",this);
sb=s_sb::type_id::create("sb",this);
s_ag1 ag1; endfunction
s_ag2 ag2;
s_sb sb; virtual function void connect_phase(uvm_phase phase);
function new(string name="s_env",uvm_component parent); super.connect_phase(phase);
super.new(name,parent); ag1.mon1.MON12SB.connect(sb.ip_fifo.analysis_export);
endfunction ag2.mon2.MON22SB.connect(sb.op_fifo.analysis_export);
virtual function void build_phase(uvm_phase phase); endfunction
super.build_phase(phase); endclass
Scoreboard Ex:

class s_sb extends uvm_scoreboard; task run_phase(uvm_phase phase);


forever
`uvm_component_utils(s_sb); begin
fork
s_seq_item pkt1,pkt2; ip_fifo.get(pkt1);
`uvm_info("SB","MON1",UVM_NONE);
uvm_tlm_analysis_fifo #(s_seq_item) ip_fifo; op_fifo.get(pkt2);
uvm_tlm_analysis_fifo #(s_seq_item) op_fifo; `uvm_info("SB","MON2",UVM_NONE);
join
function new(string name="s_sb",uvm_component parent);
super.new(name,parent); if(pkt2.data_out==pkt1.data_out)
ip_fifo=new("ip_fifo",this); `uvm_info("SB
op_fifo=new("op_fifo",this); Matched",$sformatf("pkt2.data_out=%b,pkt1.data_out=%b",pk
endfunction t2.data_out,pkt1.data_out),UVM_NONE);
else
virtual function void build_phase(uvm_phase phase); `uvm_info("SB Not
super.build_phase(phase); Matched",$sformatf("pkt2.data_out=%b,pkt1.data_out=%b",pk
pkt1=s_seq_item::type_id::create("pkt1"); t2.data_out,pkt1.data_out),UVM_NONE);
pkt2=s_seq_item::type_id::create("pkt2"); end
endfunction endtask
endclass
UVM Test: The user-defined test is derived from uvm_test, uvm_test is inherited from
uvm_component. It defines the test scenario for the testbench. It 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. Or by using command line as
+UVM_TESTNAME=mem_model_test.
virtual function void build_phase(uvm_phase phase);
Ex: super.build_phase(phase);
env=s_env::type_id::create("env",this);
class test extends uvm_test; seq=s_seq::type_id::create("seq");
endfunction
`uvm_component_utils(test)
task run_phase(uvm_phase phase);
s_env env; phase.raise_objection(this);
s_seq seq; seq.start(env.ag1.seqr);

function new(string name="test",uvm_component parent); phase.drop_objection(this);


super.new(name,parent); endtask
endfunction
endclass
UVM Top: TestBench top is the module, it connects the DUT and Verification environment
components. It is a static container to hold everything required to be simulated and
becomes the root node in the hierarchy.
Ex:

module s_top(); sram_if intf(clk,rst);


bit clk; sram_dut dut(intf);
bit rst;
initial begin initial begin
clk=0; uvm_config_db #(virtual
forever #5clk=~clk; sram_if)::set(uvm_root::get(),"*","intf",intf);
end run_test("test1"); //data random, addr random wr_rd
initial
begin end
rst=0; endmodule
#10 rst=1;
end
Difference between uvm_component and uvm_object

UVM_COMPONENT UVM_OBJECT

It is static entity(available throughout the Dynamic entity(create and destroy as


simulation). required.)

Phasing mechanism to control the No phasing mechanism.


behavior of simulation.
Connected using TLM ports. No connection of TLM

It have 2 arguments (name, It have only one argument i.e name.


uvm_component parent).

Instantiated at start of simulation. Instantiated at run time.


UVM SUBSCRIBER: Uvm subscribers are basically listeners of an analysis port.Uvm_component
class
doesn’t have an in-built analysis port.It has analysis_export and also it has methods as new and
write.
For example we can write coverages in uvm_subscriber inherited class.we can connect agent and
coverage in environment.

Phases:
Each components goes through predefined set of phases and it cannot proceed to the next phase until all
components finish their execution in current phase.

Phases acts as a synchronizing mechanism in the life cycle of simulation.

Phases are represented by callback methods, A set of predefined phases and corresponding callbacks are
provided in uvm_component. The Method can be either a function or task.

Phases are not present in objects as they don’t present throughout the simulation.

All phases can be grouped into 3 categories


1.Build_phase 2. Run-time phase 3.Clean-up phase
Build phase: It is responsible for building all lower level components and it executes in top-down manner at start
of simulation.

virtual function void build_phase(uvm_phase phase);


super.build_phase(phase);
endfunction
Connect phase: Responsible for establishing connections between all created components by TLM port-export
and it executes in bottom-up manner.

virtual function void connect_phase(uvm_phase phase);


super.connect_phase(phase);
driv.seq_item_port.connect(seqr.seq_item_export)
endfunction
End of elaboration: To do any final time adjustment in TB architecture and to print topology. To display
environment hierarchy and executes from bottom-up manner.

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
Start of simulation: To display banner information and TB hierarchy and it is called in bottom up manner
virtual function void start_of_simulation phase(uvm_phase phase);
uvm_report_info(get_full_name(),”start_of_simulation”,uvm_LOW);
endfunction

Run_phase(): All the phases are functions but it is a task as it contains a delay elements. At this phase it drives
stimulus to DUT and monitor capture information from DUT based on protocol and it executes in
parallel.
task run_phase(uvm_phase phase);
uvm_objection objection;
super.run_phase(phase);
phase.raise_objection(this);
seq.start(env.agent.sequencer);
phase.drop_objection(this);
`uvm_info("RUN", $sformatf("run_phase of %s", get_name()), UVM_LOW)
endtask: run_phase

Extract phase: Extract and process information from scoreboard and functional coverage monitors and it executes
in bottom up manner `uvm_info("EXTRACT", $sformatf("extract_phase of
function void extract_phase(uvm_phase phase); %s", get_name()), UVM_LOW)
super.extract_phase(phase); endfunction: extract_phase
Check phase: Checks if the DUT behaved correctly and identifies errors that may have occurred during the
execution and it executes in bottom-up manner
function void check_phase(uvm_phase phase);
super.check_phase(phase);
`uvm_info("CHECK", $sformatf("check_phase of %s", get_name()), UVM_LOW)
endfunction: check_phase

Report phase: Displays the result of the simulation or to write the result to file
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("REPORT", $sformatf("report_phase of %s", get_name()), UVM_LOW)
endfunction: report_phase

Final phase: used to complete any outstanding actions that tb didn’t completed executes in top-down manner.
function void final_phase(uvm_phase phase);
super.final_phase(phase);
`uvm_info("FINAL", $sformatf("final_phase of %s", get_name()), UVM_LOW)
endfunction: final_phase
How these phases gets activated:
Once these run_test() method is called it constructs the root components of the uvm
environment and then it initiates the uvm phasing process. It must be called from static part of
testbench means within initial- begin block in top module. A phase starts only when all
components in the previous phase have dropped their objections.

How simulation ends in UVM:


1)In run phase() the simulation occurs and components can raise objections in this phase and after
dropping all these objections run phase completes and check phase() of all components executes
and ends the test
2)By specifying simulation time the timer starts in parallel with run_phase(), Once timeout limits
before run_phse() then an error message is issued and post run() phases will execute.
UVM REPORTING: In Verilog and SV $display doesn’t provide filtering and control messages. In
UVM, the reporting mechanisms are built in every components and are derived from uvm_report_object,
which contains rich set of message display methods and commands to alter the numbers and types of messages
that are displayed without recompilation of design.
It has Severity, Verbosity and Simulation Handing Behavior, which can be controlled independently.

Uvm reporting facility contains three kinds of functionality:


-Displaying messages in a uniform way to various destinations.
-Filtering messages.
-Altering control flow of messages.

Severity: Severity indicates importance. Ex: Fatal, Error, Warning & Info
Verbosity: Verbosity indicates filter level. Ex: None, Low, Medium, High, Full & Debug
Simulation Handling Behavior: Simulation handling behavior controls simulator behavior.Ex: 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
Verbosity levels:

Default is UVM_MEDIUM.
id -- a unique id to form a group of messages.
message -- The message text
verbosity -- the verbosity of the message, indicating its relative importance. If this number is less than or equal to the
effective verbosity level, then the report is issued, subject to the configured action and file descriptor settings.
filename/line -- If required to print filename and line number from where the message is issued, use macros, `__FILE__
and `__LINE__.

Uvm_actions: It is used to control the simulation behavior. It is dependent on severity.

UVM verbosity level is required only for uvm_info, cant be used for uvm_warning, uvm_error, uvm_fatal
The built in methods which are used to call inside the reporting macros:
1) get_name(): returns the name of the object
`uvm_error(get_name(),”driver”)//:drv[D]driver
2) get_full_name(): returns the full hierarchial (path) name of the object
`uvm_error(get_full_name(),”driver”)//:top.test.env.ag1.drv[DRV]driver
3) get-type_name(): returns the type name of the object.
FACTORY CONCEPT: The purpose of factory in UVM is to change the behavior of the testbench without
any change in code or without any compilation. The factory registered components & objects are factory
registered for overriding purpose without any further modification. Factory registration helps in reusing of
your own component without changing the actual code.

For this purpose,the factory needs to know all types of classes created within
the testbench by a process called registration.
Basic operations of uvm factory.
Register: Registration is done using utility macros.

Create: Creation of object is done by using type_id::create method.

Overriding :
SET_INST_OVERRIDE BY TYPE: (set_type_override or Global override)
function void set_inst_override_by_type
(uvm_object_wrapper original_type,uvm_object_wrapper override_type, string full_inst_path);

SET_INST_OVERRIDE BY NAME: (set_inst_override)


function void set_inst_override_by_name
(string original_type_name,string override_type_name,string full_inst_path);
SET_TYPE_OVERRIDE BY TYPE:
function void set_type_override_by_type
(uvm_object_wrapper original_type,uvm_object_wrapper override_type,bit replace = 1);

SET_TYPE_OVERRIDE BY NAME:
function void set_type_override_by_name
(string original_type_name,string override_type_name,bit replace = 1);

When overriding by type, the original_type and override_type are handles to the types’ proxy
objects. Preregistration is not required.
When overriding by name, the original_type_name typically refers to a preregistered type in the factory.
When replace is 1, a previous override on original_type_name is replaced, otherwise a previous override, if
any, remains intact.
factory.print();//It will print the factory configuration.
Uvm_object_wrapper:
The uvm_object_wrapper provides an abstract interface for creating object and component proxies.
virtual class uvm_object_wrapper
Methods:
create_object:
Creates a new object with the optional name.
create_component:
Creates a new component, passing to its constructor the given name and parent.
get_type_name:
Derived classes implement this method to return the type name of the object created by create_component
or create_object.

Utility Macros: These are used to declare either object or component to get register in to the factory. All
classes derived from uvm_transactions and uvm_objects should register with “`uvm_object_utils” and
uvm_component should register with “`uvm_component_utils”.
class ABC extends uvm_object;
class ABC extends uvm_object;
`uvm_component_utils(ABC)
`uvm_object_utils(ABC) function new(string name=“ABC”,uvm_component parent);
function new(string name=“ABC”); super.new(name);
super.new(name); endfunction
endfunction endclass endclass
Field Macros: Macros that were used between *_begin and *_end utility macros are
basically called field macros. They operate on class properties and provides “automatic”
implementations of the core data methods: copy, compare, pack, unpack, record, print, and
sprint. They are used inside utility macros..

class ABC extends uvm_object;


`uvm_object_utils(ABC)
rand bit [15:0] m_addr;
rand bit [15:0] m_data;
`uvm_field_utils_begin(ABC) // error dis line
`uvm_field_int(m_addr, UVM_DEFAULT)
`uvm_field_int(m_data, UVM_DEFAULT)
`uvm_field_utils_end
function new(string name = "ABC");
super.new(name);
endfunction
Endclass
The field macros atleast expects 2 arguments (ARGUMENT,FLAG)
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: Performs deep print of object properties in a format given by its argument.
2.do_record: Records the object properties.
3.do_copy : Returns a deep copy of the object.
4.do_compare: Deep compare this data object provided in the rhs argument, returns 1 if true else 0.
5.do_pack: This method used to perform bitwise concatenation of object properties into array of bits, bytes
& ints.
6.do_unpack: Ths method used to extract property values from array of bits and bytes.
7.clone : This method first create a memory and then copies the data from one object to other.
8.create: This allocate a memory to the created object and to register the corresponding object to the
factory.
Configuration database:
Config db allows passing of objects and data to various components in the testbench with hierarchy. It is built
on top of uvm_resource_db, here resource db doesn’t have hierarchy concept. Note that all the functions are
static and must be called using the :: scope operator in config_db.

The database has both a name table and a type table and each resource is entered into both. Resources are stored
in a database so that each resource can be retrieved by name or by type, and the database is globally accessible.
uvm config db set method:
uvm_config_db#(type T = int)::set(uvm_component_cntxt, string inst_name, string field_name, T value);
Example:
mem_if intf(clk,reset); //interface instance
uvm_config_db#(virtual mem_if)::set(null,"*","mem_intf",intf); //set method
Here mem_if is declared globally so that other components in the hierarchy can access it.
uvm config db get method:
uvm_config_db#(type T=int)::get(uvm_component_cntxt, string inst_name, string field_name, ref T value);
Example:
virtual interface mem_if mem_vif; //virtual interface declaration
if( !uvm_config_db#(virtual mem_if)::get(this,"*", "mem_intf", mem_vif))
`uvm_fatal("virtual interface not connected" ); //get method
Uvm_resource_db: In this if same thing is written at multiple places in the hierarchy then last write wins. It is
recommended to use uvm_config_db. In this there is no hierarchy concept.
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.
Stimulus generation
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.

▪Derive from uvm_sequence base class with a specified data object type
▪Register the sequence with the factory using `uvm_object_utils
▪Set the default sequencer that should execute this sequence
▪Code the main stimulus inside the body() task.

Sequences can do operations on sequence item or on sub_sequences.


▪Execute using the start() method of a sequence or `uvm_do macros
▪Execute sequence items via start_item/finish_item or `uvm_do macros
`uvm_do macros will identify if the argument is a sequence or sequence_item and will
call start() or start_item() accordingly.
The sequence will get executed upon calling the start of the sequence from the test.
sequence_name.start(sequencer_name);
The order in which the methods will get called on calling the start of a sequence is
Communication between the Sequence and driver involves below steps,
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

5&6 are optional. Mid_do & post_do are functions remaining all tasks.
UVM Sequence macros:
These macros are used to start sequences and sequence items on default sequencer, m_sequencer
m_sequencer, p_sequencer: if we want to access anything from the testbench hierarchy (which are
components) - the sequence(object) would need a handle to the sequencer(component) on which the
sequence is running.

m_sequencer: It is a handle of type uvm_sequencer_base which is available by default in a sequence.


It's set automatically when you call start(). It is of type uvm_sequencer_base.

p_sequencer: The p_sequencer is a type specific sequencer pointer, created by registering the sequence to a
sequencer using the `uvm_declare_p_sequencer macros. p_sequencer will not exist if the
`uvm_declare_p_sequencer macros isn’t used. P_sequencer doesn’t exist by default in sequences.Implement
a function to set a value.

m_sequencer is a handle of type uvm_sequencer_base while p_sequencer is a handle of type


user_defined_sequencer.
UVM CALLBACKS:
Callbacks are empty methods with a call to them.
Different functionalities of the component can be obtained by changing the empty callback methods.

Virtual methods are referred to as callback methods,


virtual pre_drive();
virtual post_drive();
`uvm_register_cb(T,CB) This macro is used to register the callback(CB) with the object(T).
In order to call the callbacks..,
`uvm_do_callbacks(T,CB,METHOD): used to call the callbacks

CB => user defined callback class.


T => Object in which CB is used.
METHOD => callback method to be called like pre_drive, post_drive.

We can add methods ..,


uvm_callbacks#(T,CB)::add(t,cb);
CB => user defined callback class type.
T => Object type in which CB is used.
cb => callback class object
t => object in which callback is used
Sequencer and driver communication: The driver class contains a tlm port called
uvm_seq_pull_port which is connected to a uvm_seq_pull_export in sequencer in the connect
phase of agent.

Driver side operation:


class uvm_driver #(type REQ=uvm_sequence_item,type RSP=REQ) extends uvm_component;
uvm_seq_item_pull_port #(REQ,RSP) seq_item_port;
uvm_analysis_port #(RSP) rsp_port;
REQ req;
RSP rsp;
endclass
Steps are made by the Driver in order to complete the communication with Sequencer(Sequence) .
get_next_item(): Blocks the process until the “req” transaction object is available in the sequencer request FIFO
& later returns the pointer of “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 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.

Sequencer side Operation:


A uvm_sequencer has an inbuilt TLM pull implementation port called seq_item_export.

class uvm_sequencer #(type REQ=uvm_sequence_item,RSP=REQ) extends


uvm_sequencer_param_base #(REQ,RSP);
uvm_seq_item_pull_imp#(REQ,RSP) seq_item_export;
endclass

The port in uvm_driver is connected to the export in uvm_sequencer in the connect phase of uvm_agent.
Creation: Creating the transaction item.
Ready start_item(): By passing seq_item handle as assignment. This class blocks until the sequencer grants the seq &
seq_item access to driver.
Set: seq_item is ready to use, usually through randomization.
Go finish_item(): Blocks the driver until the completion of it’s side of the transfer protocol for the item.
Response get_response(): It is optional & blocks until the response item is available from the sequencer response FIFO.
Virtual sequence: When multiple sequences are there then virtual sequence is used. It is a container to
start multiple sequences on different sequencers in the environment. It is usually executed by a virtual
sequencer which has handles to real sequencers. It is required when different sequences to be run on different
environment. Inside it different sequences, sequencers & virtual sequencers are present. It becomes virtual
because it doesn’t associated with any
particular data type.
Virtual sequencer: It just contains handles of different sequencers. When multiple sequencers are there in
Environment then Virtual sequencer should be required. It is a Sequencer that is not connected to the UVM
Driver itself, but contains the handles of the target Sequencer in the Testbench hierarchy.

➢ my_virtual_seq is derived from uvm_sequence just like any other sequence

➢ A handle called p_sequencer is created within the sequence via macro `uvm_declare_p_sequencer and
assigned to be run with my_virtual_sequencer.

➢ Each sequence is started on its corresponding sequencer using the start() method
➢ Each sequencer is referenced by p_sequencer handle which points to the virtual sequencer.
➢ Virtual sequence extended from uvm_sequence.
➢ Virtual sequencer extended from uvm_sequencer.
➢ Handles of sequencers in virtual sequencer are made to point physical sequencers in the build phase of the
environment.
➢ A virtual sequencer class will have handles of physical sequencers.
➢ The virtual sequence has handles for physical sequencers and virtual sequencer.
UVM Sequence Arbitration: The sequencer supports an arbitration mechanism to ensure that at any point of
time only one sequence has access to the driver. Whenever multiple sequences try to access a single driver,
the sequence will make them in a certain order through arbitration.

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.

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.

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.
TLM: Transaction-Level Modelling (TLM) is used for communication among modules. TLM is the concept
in which transaction based methods are implemented, these methods can be used for communication between
the modules. They are used like pipes to connect between components so there won’t be any packet loss.

The UVM provides TLM library with transaction-level interfaces, ports, exports, imp ports, and analysis
ports. all these TLM elements are required to send a transaction, receive transaction, and transport from one
component to another.

UVM TLM PORT:


The TLM Port is used to send the transactions
TLM Ports has unidirectional and bidirectional ports
A port can be connected to any compatible port, export, or imp port
They are used to initiate & forward packets to the top layer of hierarchy.

uvm_*_port #(T) //unidirectional port class


uvm_*_port #(REQ,RSP) //bidirectional port class
The new constructor method used for the creation of TLM Port.
UVM 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.

uvm_*_export#(T) //unidirectional export class


uvm_*_export #(REQ,RSP) //bidirectional export class

T – The type of transaction to be communicated by the export


REQ – The type of request transaction to be communicated by the export
RSP – The type of response transaction to be communicated by the export

UVM TLM IMP:


The TLM Imp Port is used to receive the transactions at destination
TLM Imp Ports has unidirectional and bidirectional ports

TRANSPORT IMP CONSTRUCTOR: (new)This is a constructor method used for the creation of TLM Imp Port.

function new(string name, IMP imp)

uvm_*_imp #(T,IMP) //unidirectional port class


uvm_*_imp #(REQ, RSP, IMP, REQ_IMP, RSP_IMP) //bidirectional port class
T – The type of transaction to be communicated by the imp
IMP – The type of the component implementing the interface. That is the class to which this imp will delegate.
REQ_IMP – The component type that implements the request side of the interface. Defaults to IMP. For master and slave
imps only.
RSP_IMP – The component type that implements the response side of the interface. Defaults to IMP. For master and slave
imps only

TLM interface: In this scheme data is represented as transactions which flow in and out of component via special ports
called TLM interface.
Blocking Interfaces:
put(): This method is used to send a transaction to another component (<port>.put(trans)).
get(): This method is used to retrieve transaction from another component (<port>.get(trans) ).
peek(): This method obtain a transaction without consuming it (<port>.peek(trans) ).
Non-Blocking Interfaces:
try_put(): This method is used to send a transaction to another component without blocking the execution
(<port>.try_put(trans)). This method returns 1 If the component is ready to accept the transaction, otherwise it returns 0.
can_put(): This method call returns 1 if the component is ready to accept the transaction, otherwise, it returns 0
no argument must be passed to can_put, this method is used to see whether the other component is ready to accept the
transaction.
try_get: method is used to retrieve transaction from another component without blocking the execution. Calling
<port>.try_get(trans) retrieve transaction from another component, if possible. This method returns 1 if the
transaction is available, otherwise, it returns 0
can_get(): This method call returns 1 if the component can get transaction immediately, otherwise, it returns 0. No
argument must be passed to can_get, this method is used to see whether any transaction is available to get
transport(): Calling <port>.transport(req,resp) method executes the given request and returns the response in the
given output argument. It 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 for any reason the operation could not be executed immediately, then a
0 must be returned, otherwise 1.

TLM FIFO
The TLM FIFO provides storage for the transactions between two independently running processes.
In TLM FIFO, the sender pushes the transactions to FIFO and whenever it required reiver pops it out or fetches from the
FIFO
Transactions are put into the FIFO via the put_export method
Transactions are fetched from the FIFO via
the get_peek_export method
TLM FIFO METHODS:
new:
This is a constructor method used for the creation of TLM FIFO
function new(string name, uvm_component parent,int size=1);
size:
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 PORT:

TLM Analysis port enable broadcasting a transaction to one or many components.


This has a simple function called write.
//Step-1. Declaring analysis port
uvm_analysis_port#(transaction) analysis_port;

Step-2. Creating analysis port


analysis_port = new("analysis_port", this);

//Ste-3. Calling write method

analysis_port.write(trans);
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

uvm_tlm_analysis_fifo#(T) An analysis_fifo is a uvm_tlm_fifo#(T) with an unbounded size and a write


method.
Difference between mailbox and TLM

TLM MAILBOX
We don’t need to pass the handle of tlm It requires the user to pass the mailbox
port to any component, we just need to handle to the components which need to
instantiate with seq_item. be connected.

It can be both uni and bi directional. It is unidirectional.

It provides different models like It is like passing data between different


broadcasting, fifo operations, component components.
connections.
Objection mechanism: Components can raise and drop objections. A component that raise objections
remains in same phase till all the objections are dropped.

Raising and dropping of objections is done in the run phases.

phase.raise_objection(this); // to raise objection in this object


phase.drop_objection(this); // to drop objection in this object

Ex:
class agent extends uvm_agent;
task run_phase(uvm_phase phase);
phase.raise_objection(this);
#100;
phase.drop_objection(this);
endtask
endclass

In this the phase, the objection is raised and continue in the run_phase till #100 delay and then the objection is
dropped and control moves to next phase called extract phase.
UVM RAL MODEL:
When a design contains number of registers and memories, then to verify them effectively UVM provides a
base class library for the register management & access. This approach is known as RAL(Register Abstraction
Layer).

In general it is a high level object oriented abstraction layer to access design registers. RAL mimics the design
registers using UVM base class libraries.

uvm_reg_field: used to describe the individual register fields.


uvm_reg: set of fields to describe a register.
uvm_reg_map: used to map the entire registers with their names, addr, size etc.,
uvm_reg_file: used to group multiple registers into single set.
uvm_reg_block: used to represent a one block design having multiple registers, other blocks, other memories.
uvm_mem: equivalent TB representation of memory.

In order to access the DUT registers there are two ways.


Front Door Access:
Involves the usage of bus interface & consumes time. A register model based on UVM classes that accurately
reflect values of the design registers. An agent to drive actual bus transactions to the design based on some
protocol. An adapter to convert the read and write statements(register sequence) from the model to protocol
based bus transactions. A predictor to understand bus activity and update the register model to match the
design contents.
Adapter:
To convert read/write method calls into actual bus protocol accesses, the generic register item is converted to a
protocol specific bus transaction item by a component called as an adapter.
Adapter needs to be bidirectional in order to update the register model. This conversion process is facilitated
by the adapter via reg2bus() and bus2reg() functions.

Predictor:
A component called predictor can be placed on the target bus interface to monitor for any transactions and
update the register model accordingly.

Back Door Access:


It uses simulator database access routines and this happens in zero simulation time. To use back door access
with uvm register model,the user as to specify the hardware or hdl path to a signal that the register model
represent. So,it is the responsibility of the user to specify the signal path in the register model for every
register or for those registers which required backdoor access.
In this we can completely bypass the bus agent and write/read the register sequence directly to/ from the
DUT register.

That’s the reason the Back Door Access is faster than Front Door Access.
Thank you

You might also like