How does a sequencer communicate with the driver ?
The driver class contains a TLM port called uvm_seq_item_pull_port
which is connected to a uvm_seq_item_pull_export
in the sequencer in the connect phase of a UVM agent. The driver can use TLM functions to get the next item from the sequencer when required.

Why do we need the driver sequencer API methods ?
These API methods help the driver to get a series of sequence_items from the sequencer's FIFO that contains data for the driver to drive to the DUT. Also, there is a way for the driver to communicate back with the sequence that it has finished driving the given sequence item and can request for the next item.
What is the port in driver called ?
Declaration for the TLM ports can be found in the class definitions of uvm_driver
and uvm_sequencer
as follows.
// Definition of uvm_driver
class uvm_driver #(type REQ=uvm_sequence_item,
type RSP=REQ) extends uvm_component;
// Port: seq_item_port
// Derived driver classes should use this port to request items from the
// sequencer. They may also use it to send responses back.
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
// Port: rsp_port
// This port provides an alternate way of sending responses back to the
// originating sequencer. Which port to use depends on which export the
// sequencer provides for connection.
uvm_analysis_port #(RSP) rsp_port;
REQ req;
RSP rsp;
// Rest of the code follows ...
endclass
What is the TLM port in sequencer called ?
A uvm_sequencer
has an inbuilt TLM pull implementation port called seq_item_export
which is used to connect with the driver's pull port.
// Definition of uvm_sequencer
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
extends uvm_sequencer_param_base #(REQ, RSP);
// Variable: seq_item_export
// This export provides access to this sequencer's implementation of the
// sequencer interface.
uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;
// Rest of the class contents follow ...
endclass
How is a driver connected to a sequencer ?
The port in uvm_driver
is connected to the export in uvm_sequencer
in the connect
phase of the UVM component in which both the driver and sequencer are instantiated. Typically, a driver and sequencer are instantiated in a uvm_agent
.
class my_agent extends uvm_agent;
`uvm_component_utils (my_agent)
my_driver #(my_sequence_item) m_drv;
uvm_sequencer #(my_sequence_item) m_seqr;
virtual function void connect_phase (uvm_phase phase);
// Always the port is connected to an export
m_drv.seq_item_port.connect(m_seqr.seq_item_export);
endfunction
endclass
The connect between a driver and sequencer is a one-to-one connection. Multiple drivers are not connected to a sequencer nor are multiple sequencers connected to a single driver. Once the connection is made, the driver can utilize API calls in the TLM port definitions to receive sequence items from the sequencer.

Design
module ring_ctr #(parameter WIDTH=4)
(
input clk,
input rstn,
output reg [WIDTH-1:0] out
);
always @ (posedge clk) begin
if (!rstn)
out <= 1;
else begin
out[WIDTH-1] <= out[0];
for (int i = 0; i < WIDTH-1; i=i+1) begin
out[i] <= out[i+1];
end
end
end
endmodule
Testbench
module tb;
parameter WIDTH = 4;
reg clk;
reg rstn;
wire [WIDTH-1:0] out;
ring_ctr u0 (.clk (clk),
.rstn (rstn),
.out (out));
always #10 clk = ~clk;
initial begin
{clk, rstn} <= 0;
$monitor ("T=%0t out=%b", $time, out);
repeat (2) @(posedge clk);
rstn <= 1;
repeat (15) @(posedge clk);
$finish;
end
endmodule
ncsim> run T=0 out=xxxx T=10 out=0001 T=50 out=1 000 T=70 out=01 00 T=90 out=001 0 T=110 out=0001 T=130 out=1 000 T=150 out=01 00 T=170 out=001 0 T=190 out=0001 T=210 out=1000 T=230 out=0100 T=250 out=0010 T=270 out=0001 T=290 out=1000 T=310 out=0100 Simulation complete via $finish(1) at time 330 NS + 0
A member declared as local
is available only to the methods of the same class, and are not accessible by child classes. However, nonlocal methods that access local
members can be inherited and overridden by child class.
Example
In the following example, we will declare two variables - one public
and another local
. We expect to see an error when a local member of the class is accessed from somewhere outside the class. This is because the keyword local
is used to keep members local and visible only within the same class.
When accessed from outside the class
class ABC;
// By default, all variables are public and for this example,
// let's create two variables - one public and the other "local"
byte public_var;
local byte local_var;
// This function simply prints these variable contents
function void display();
$display ("public_var=0x%0h, local_var=0x%0h", public_var, local_var);
endfunction
endclass
module tb;
initial begin
// Create a new class object, and call display method
ABC abc = new();
abc.display();
// Public variables can be accessed via the class handle
$display ("public_var = 0x%0h", abc.public_var);
// However, local variables cannot be accessed from outside
$display ("local_var = 0x%0h", abc.local_var);
end
endmodule
As expected, the compiler gives out a compilation error pointing to the line where a local member is accessed from outside the class.
$display ("local_var = 0x%0h", abc.local_var);
|
ncvlog: *E,CLSNLO (testbench.sv,24|47): Access to local member 'local_var' in class 'ABC' is not allowed here.
irun: *E,VLGERR: An error occurred during parsing. Review the log file for errors with the code *E and fix those identified problems to proceed. Exiting with code (status 1).
In the above example, we can remove the line that causes a compilation error and see that we get a good output. The only other function that accesses the local
member is the display() function.
module tb;
initial begin
ABC abc = new();
// This should be able to print local members of class ABC
// because display() is a member of ABC also
abc.display();
// Public variables can always be accessed via the class handle
$display ("public_var = 0x%0h", abc.public_var);
end
endmodule
ncsim> run public_var=0x0, local_var=0x0 public_var = 0x0 ncsim: *W,RNQUIE: Simulation is complete.
When accessed by child classes
In this example, let us try to access the local
member from within a child class. We expect to see an error here also because local
is not visible to child classes either.
// Define a base class and let the variable be "local" to this class
class ABC;
local byte local_var;
endclass
// Define another class that extends ABC and have a function that tries
// to access the local variable in ABC
class DEF extends ABC;
function show();
$display ("local_var = 0x%0h", local_var);
endfunction
endclass
module tb;
initial begin
// Create a new object of the child class, and call the show method
// This will give a compile time error because child classes cannot access
// base class "local" variables and methods
DEF def = new();
def.show();
end
endmodule
As expected, child classes cannot access the local
members of their parent class.
$display ("local_var = 0x%0h", local_var);
|
ncvlog: *E,CLSNLO (testbench.sv,10|43): Access to local member 'local_var' in class 'ABC' is not allowed here.
irun: *E,VLGERR: An error occurred during parsing. Review the log file for errors with the code *E and fix those identified problems to proceed. Exiting with code (status 1).
Class definitions can become very long with a lot of lines between class
and endclass
. This makes it difficult to understand what all functions and variables exist within the class because each function and task occupy quite a lot of lines.
Using extern
qualifier in method declaration indicates that the implementation is done outside the body of this class.
Example
class ABC;
// Let this function be declared here and defined later
// by "extern" qualifier
extern function void display();
endclass
// Outside the class body, we have the implementation of the
// function declared as "extern"
function void ABC::display();
$display ("Hello world");
endfunction
module tb;
// Lets simply create a class object and call the display method
initial begin
ABC abc = new();
abc.display();
end
endmodule
ncsim> run
Hello world
ncsim: *W,RNQUIE: Simulation is complete.
A UVM transaction class typically defines all the input and output control signals that can be randomized and driven to the DUT.
Steps to create a UVM transaction object
1. Create custom class inherited fromuvm_sequence_item
, register with factory and call new
// my_object is user-given name for this class that has been derived from "uvm_sequence_item"
class my_object extends uvm_sequence_item;
// This is standard code for all components
function new (string name = "my_object", uvm_component parent = null);
super.new (name, parent);
endfunction
// Code for rest of the steps come here
endclass
2. Declare variables related to this transaction class
// Declare variables and make the ones that has to be randomized as "rand"
rand bit [15:0] addr;
rand bit [31:0] data;
rand bit [2:0] burst;
3. Register the class with factory and apply UVM field macros if required
`uvm_object_utils_begin (my_object)
// Apply the given object macros to the corresponding variables
`uvm_field_int (addr, UVM_ALL_ON | UVM_NO_COMPARE)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (burst, UVM_ALL_ON)
`uvm_object_utils_end
4. Add constraints as required
constraint c_addr { addr <= 16'hBFFF; }
constraint c_burst { burst inside {[0:4]}; }
Define convert2string
function
function string convert2string();
return $sformatf("addr=0x%0h data=0x%0h burst=0x%0h", addr, data, burst);
endfunction