OVM/UVM: A Practical
Tutorial Using System Verilog
-Aviral Mittal
Driver Class.
Now we will write a 'Driver Class'. I have given snippets of code
below, the full code can be downloaded Here.
Note that you will have to provide at least your email ID, to enable
the download of the full code.
This is where we code the behavior of the protocol, which is in our
case AHB. This is the most
important part of writing a VIP, as this will define how your VIP
drives the interface.
This driver class must be derived directly or indirectly from the
uvm class 'uvm_driver'.
The class definition must also specify the 'transaction class type'
(here ahb_mtran).
Here the ahb_mdriver class is derived from the uvm base class called
uvm_driver, which is
parameterized by the transaction type class ahb_mtran.
Like the transaction class, it also has a function 'new', but it is
different as it takes two args unlike
the one in transaction class, which takes 1 arg.
This is because the driver class belongs to the UVM component
hierarchy, and its new function demands
the 'parent' component.
Do not get too much intermediated by it, just write the new function
as given.
Now this driver is actually supposed to drive the 'interface' to the
DUT, however, this driver has no knowledge
of where in all of the UVM/verilog hierarchy the actual physical
interface is, hence this driver will drive a
'pointer' to the the actual interface, which is also called a
'virtual interface'.
Somewhere later, we will map this 'virtual' interface to an actual
interface.
We will see that later.
So an virtual interface is defined as thus:
virtual ahb_if dut_vi;
The above line declares a pointer to an actual interface. The
virtual interface is called 'dut_vi' and it is
of the type ahb_if. ahb_if is defined previously in Interface Chapter.
It also has a function 'build_phase' again dont worry about it too
much, just write it as it is shown.
Phases and the use of 'build_phase' will be explained later.
class ahb_mdriver
extends uvm_driver #(ahb_mtran);
`uvm_component_utils(ahb_mdriver)
virtual ahb_if dut_vi; //this is supposed to be a
virtual interface
//virtual interface is a pointer to actual
interface object
function new(string name , uvm_component parent);
super.new(name,parent);
endfunction: new
function void build_phase(uvm_phase phase);
endfunction: build_phase
task run_phase (uvm_phase phase);
forever begin
ahb_mtran tx;
seq_item_port.get_next_item(tx);
tx.print();
.
.
@(posedge dut_vi.hclk);
dut_vi.haddr = tx.haddr;
dut_vi.hburst = tx.hburst;
dut_vi.hwrite = tx.hwrite;
.
.
.
seq_item_port.item_done();
end //forever
endtask
endclass: ahb_mdriver
Now it can be seen in the code snippet that what a driver class does
is
1. Declares an instance of the transaction type ahb_mtran
ahb_mtran tx;
2. Get the transaction using 'seq_item_port.get_next_item(tx).
The driver retrieves a transaction from the sequencer. This
transaction actually originates in the 'Sequence' code
and passed to the driver on demand by the 'Sequencer'.
All the driver has to do is use the following:
seq_item_port.get_next_item(tx)
AND
seq_item_port.item_done()
In between the above 2 lines, we write what the driver is going to
do with the transaction it receives from the
sequencer.
Here first we print it using tx.print(), this will print the fields
of generated transaction,
on the simulation window, when the simulation will be run. For
example:
-------------------------------------------------------------------------------------------------------------
Name
Type Size
Value
-------------------------------------------------------------------------------------------------------------
tx
ahb_mtran -
@2479
htrans
HTRANS_t 32
IDLE
hburst
HBURST_t 32
INCR
haddr
integral 32
'hb90e8ee4
BL
integral 32
'd13
b2b_delay
integral 32
'd1
en_b2b_delay
integral 1
1
hwrite
integral 1
'b0
begin_time
time 64
18215
depth
int 32
'd2
parent sequence (name)
string 8
idle_seq
parent sequence (full name)
string 61
uvm_test_top.ahb_env_h.ahb_magent_h.ahb_msequencer_h.idle_seq
sequencer
string 52
uvm_test_top.ahb_env_h.ahb_magent_h.ahb_msequencer_h
-------------------------------------------------------------------------------------------------------------
After printing the transaction, the driver waits for a clock
positive edge, and then drive the
virtual interface pins, with the field values of the transaction tx
which is of the type ahb_mtran.
The transaction 'tx' actually originates in 'Sequencer' where it is
'randomized'. The fields of
'tx' now gets 'constrained random' values and the driver retrieves
this 'tx',
and drive these constrained random values to the pins of the virtual
interface.
The virtual interface is then mapped to an actual interface which
drives the DUT.
This is how 'constrained random' values are driven to the DUT using
the UVM env.
Here a very simple driver is shown, but in actual driver, the
virtual interface pins will be driven following the AHB protocol.
The full driver code can be downloaded
here.
Notice all the interesting activities happen in the task
'run_phase'. This task will be run during the 'run_phase' of the UVM
TB execution.
Again do not worry too much about how it will be run, focus on what
it is supposed to do. How it will be run is a bit of UVM magic.
Just trust at the moment, that it will be run 'somehow', which you
need not worry about.
This completes this Driver Class Chapter.
<-
Previous(Transaction Class)
Next -> (Sequence
Class)
KeyWords: OVM, UVM, SystemVerilog, Constrained Random
Verification, Transaction Level Modelling.