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.