UVM Tutorial for Candy Lovers – 4. Agent

Last Updated: July 12, 2015

The last post concentrated on the transactions and sequences of the jelly-bean taster system. This post will explain the verification components in the verification environment further in depth.

Interface

In this segment, a general explanation of the interface (jelly_bean_if) and its role of binding verification components and the jelly-bean taster (DUT) will be provided.

The inside of the jelly_bean_if is shown below. In essence, the jelly_bean_if contains signals that have been transformed from the properties in the jelly_bean_transaction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface jelly_bean_if(input bit clk);
   logic [2:0] flavor;
   logic [1:0] color;
   logic       sugar_free;
   logic       sour;
   logic [1:0] taste;
 
   clocking master_cb @ (posedge clk);      default input #1step output #1ns;      output flavor, color, sugar_free, sour;      input  taste;   endclocking: master_cb 
   clocking slave_cb @ (posedge clk);      default input #1step output #1ns;      input  flavor, color, sugar_free, sour;      output taste;   endclocking: slave_cb 
   modport master_mp(input clk, taste, output flavor, color, sugar_free, sour);   modport slave_mp(input clk, flavor, color, sugar_free, sour, output taste);   modport master_sync_mp(clocking master_cb);   modport slave_sync_mp(clocking slave_cb);endinterface: jelly_bean_if

The timings of the respective signals are defined by the clocking blocks. The master_cb defines the timings from the bus-master point of view, while the slave_cb defines the timings from the bus-slave point of view.

The interface also has the modport lists, which define the directions of the signals within the interface. We define four modport lists:

Name Directions Seen from Timing Used by
master_mp Master Asynchronous DUT
slave_mp Slave Asynchronous DUT
master_sync_mp Master Synchronous with master_cb Test-bench
slave_sync_mp Slave Synchronous with slave_cb Test-bench

The DUT uses the asynchronous modport lists (master_mp and slave_mp), while the test-bench uses the synchronous modport lists (master_sync_mp and slave_sync_mp).

Update (April 2, 2014): We noticed that some simulators don’t like #1step (lines 9 and 15). You can change it to #1ns if that is the case.

DUT

The code below shows a simple structural example of the jelly_bean_taster. The line 1 specifies the jelly_bean_if we just defined above, and the slave_mp to select the appropriate directional information for the interface signals. The taster will only respond negatively to the sour chocolate flavor!

1
2
3
4
5
6
7
8
9
10
11
module jelly_bean_taster( jelly_bean_if.slave_mp jb_slave_if );   import jelly_bean_pkg::*;
   always @ ( posedge jb_slave_if.clk ) begin
      if ( jb_slave_if.flavor == jelly_bean_transaction::CHOCOLATE &&
           jb_slave_if.sour ) begin
         jb_slave_if.taste <= jelly_bean_transaction::YUCKY;
      end else begin
         jb_slave_if.taste <= jelly_bean_transaction::YUMMY;
      end
   end
endmodule: jelly_bean_taster

Alternatively, you can specify the modport list in the port connection with the module instance.

module jelly_bean_taster( jelly_bean_if jb_slave_if ); // no modport specified
  // ...
endmodule
 
module top;
  reg               clk;
  jelly_bean_if     jb_slave_if( clk );
  jelly_bean_taster jb_taster( jb_slave_if.slave_mp ); // modport specified
  // ...
endmodule

You can specify the modport list in both the module declaration and the module instance, but they must be identical if you do so. If no modport list is specified at all, the signals in the interface are assumed to have inout access.

Sequencer

The jelly-bean sequence is processed by the sequencer. The uvm_sequencer is used as is, as jelly-bean sequencer is not involved with any kind of extended features.

typedef uvm_sequencer#(jelly_bean_transaction) jelly_bean_sequencer;

Driver

The driver receives the jelly_bean_transaction through the seq_item_port on the line 22, leading to pin wiggling. The driver uses the interface between the driver and the DUT to drive the signals. The interface is stored in the uvm_resource_db (line 12).

Update (April, 2, 2014): In this tutorial, we used uvm_resource_db to retrieve the jelly_bean_if (lines 12 and 13). UVM recommends using uvm_config_db instead of uvm_resource_db as the former is more robust. You can replace the lines 12 and 13 with:

assert( uvm_config_db#( virtual jelly_bean_if )::
        get( .cntxt( this ), .inst_name( "" ), .field_name( "jelly_bean_if" ), .value( jb_vi ) ) );

For more information about the configuration database, please see Configuration Database and Configuration Database Revisited.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class jelly_bean_driver extends uvm_driver#(jelly_bean_transaction);
   `uvm_component_utils(jelly_bean_driver)
 
   virtual jelly_bean_if jb_vi;
 
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction: new
 
   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      void'(uvm_resource_db#(virtual jelly_bean_if)::read_by_name           (.scope("ifs"), .name("jelly_bean_if"), .val(jb_vi)));   endfunction: build_phase
 
   task run_phase(uvm_phase phase);
      jelly_bean_transaction jb_tx;
 
      forever begin
         @jb_vi.master_cb;
         jb_vi.master_cb.flavor <= jelly_bean_transaction::NO_FLAVOR;
         seq_item_port.get_next_item(jb_tx);         @jb_vi.master_cb;         jb_vi.master_cb.flavor     <= jb_tx.flavor;         jb_vi.master_cb.color      <= jb_tx.color;         jb_vi.master_cb.sugar_free <= jb_tx.sugar_free;         jb_vi.master_cb.sour       <= jb_tx.sour;         seq_item_port.item_done();      end
   endtask: run_phase
endclass: jelly_bean_driver

The lines 20 and 23 are so-called clocking block events. They are equivalent to @( posedge jb_vi.clk ).

Monitor

The flow of data to the monitor is the opposite direction, yet similar to the driver. The interface to the DUT (jelly_bean_if) is found in the uvm_resource_db (line 14). The monitor closely monitors the jelly_bean_if, and takes in the value of the signals. Our monitor watches a non NO_FLAVOR jelly bean (line 23) and creates a jelly_bean_transaction (line 24). The created transaction is sent via the analysis port to the subscribers on line 31.

Update (April, 2, 2014): We used the uvm_resource_db to retrieve the jelly_bean_if (lines 14 and 15). Please see the above section for how to use uvm_config_db instead of the uvm_resource_db.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class jelly_bean_monitor extends uvm_monitor;
   `uvm_component_utils(jelly_bean_monitor)
 
   uvm_analysis_port#(jelly_bean_transaction) jb_ap;
 
   virtual jelly_bean_if jb_vi;
 
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction: new
 
   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      void'(uvm_resource_db#(virtual jelly_bean_if)::read_by_name           (.scope("ifs"), .name("jelly_bean_if"), .val(jb_vi)));      jb_ap = new(.name("jb_ap"), .parent(this));
   endfunction: build_phase
 
   task run_phase(uvm_phase phase);
      forever begin
         jelly_bean_transaction jb_tx;
         @jb_vi.slave_cb;
         if (jb_vi.slave_cb.flavor != jelly_bean_transaction::NO_FLAVOR) begin            jb_tx = jelly_bean_transaction::type_id::create(.name("jb_tx"), .contxt(get_full_name()));            jb_tx.flavor     = jelly_bean_transaction::flavor_e'(jb_vi.slave_cb.flavor);            jb_tx.color      = jelly_bean_transaction::color_e'(jb_vi.slave_cb.color);            jb_tx.sugar_free = jb_vi.slave_cb.sugar_free;            jb_tx.sour       = jb_vi.slave_cb.sour;            @jb_vi.master_cb;            jb_tx.taste = jelly_bean_transaction::taste_e'(jb_vi.master_cb.taste);            jb_ap.write(jb_tx);         end      end
   endtask: run_phase
endclass: jelly_bean_monitor

Agent

The sequencer, driver, monitor – all are collected in the agent. These components are created in the build_phase, and the created components are connected in the connect_phase. As the subscriber is not an asset included in the agent, an analysis port is created to communicate with the subscriber. The analysis ports, in both the agent and the monitor, are connected to each other on line 26.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class jelly_bean_agent extends uvm_agent;
   `uvm_component_utils(jelly_bean_agent)
 
   uvm_analysis_port#(jelly_bean_transaction) jb_ap;
 
   jelly_bean_sequencer jb_seqr;
   jelly_bean_driver    jb_drvr;
   jelly_bean_monitor   jb_mon;
 
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction: new
 
   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
 
      jb_ap = new(.name("jb_ap"), .parent(this));
      jb_seqr = jelly_bean_sequencer::type_id::create(.name("jb_seqr"), .parent(this));
      jb_drvr = jelly_bean_driver::type_id::create(.name("jb_drvr"), .parent(this));
      jb_mon  = jelly_bean_monitor::type_id::create(.name("jb_mon"), .parent(this));
   endfunction: build_phase
 
   function void connect_phase(uvm_phase phase);      super.connect_phase(phase);      jb_drvr.seq_item_port.connect(jb_seqr.seq_item_export);      jb_mon.jb_ap.connect(jb_ap);   endfunction: connect_phaseendclass: jelly_bean_agent

Though this post ends here, the next will continue to provide an explanation for other verification components.

Get source code

42 thoughts on “UVM Tutorial for Candy Lovers – 4. Agent”

    1. Typically a sequence generates requests and a sequencer transfers the requests to a driver.

      When we start a sequence, its start() task calls the body() task of itself. Our one_jelly_bean_sequence generates a jelly_bean_transaction in the body() task. The body() task calls start_item() followed by finish_item(). The start_item() asks a grant for the sequencer. The finish_item() sends the request to the sequencer. The sequencer pushes the request to its request FIFO. The driver calls get_next_item() to the sequencer to fetch the request. When the driver finishes the request, it calls item_done() and the sequencer pops the request out of the request FIFO. The illustration below might be helpful to understand the flow.
      How Sequence Works

      The uvm_sequencer is a parameterized class. Our sequencer transfers the requests of the jelly_bean_transaction type to the driver, so we specialized the uvm_sequencer with the jelly_bean_transaction type. You can use uvm_sequencer#(jelly_bean_transaction) when you instantiate the sequencer, but it is verbose, so we used typedef to give it a shorter name (jelly_bean_sequencer). This is what the one-line typedef does.

      1. Hi Keisuke,
        Can you elaborate more on wait for grant() and wait for item_done() ? say.. which statement in sequence body will be blocked and when will be that blocked statement will be proceeded further .. ?

        Thanks,
        Naveen

          1. Hi Keisuke,
            Thanks for the blog. I have small doubter regardiing sequencer driver interaction.
            why fifo is required in the sequencer any only one item is processed in the driver. In the above diagram wait for grant is shown from sequencer . when wait for grant is given by sequencer before or after execution get_next_item in driver.

            Thanks,
            Sampath

          2. The request FIFO is used to store and arbitrate multiple sequences in the sequencer. The wait_for_grant and get_next_item are independent. Either one can come first.

          3. Hi Keisuke,

            Can you please give me one scenario where wait for grant comes first with out get_next_item in driver is called

          4. Let’s say the driver is in low-power mode or in hibernate, and it takes some time to wake it up. When you send a sequence to the sequencer, it calls wait_for_grant, but since the driver is not ready, it does not call get_next_item.

          5. ok thanks for the comment.
            If I execute 3 sequences parallely then wait for grant will come for 3 sequences based on priority it will store in fifo. Am I right?

  1. I am trying to pass interface to my DUT as you show in your example but model sim errors out with

    interface my_interface (input bit clock);
    ...
    ..
    endinterface
    
    module my_dut ( my_interface.cb intf);
    ....
    
    endmodule
    

    vlog 2110 : “Illegal reference to interface “intf””

  2. Hi

    Hi Keisuke,

    Would you please explain the exact difference between p_sequencer and m_sequencer?
    Please explain it with few example codes.

    Regards,
    Siva

    1. Hi Will,

      To capture the value of a signal, the signal has to be an input. The master_cb declares the taste as an input, so I used the master_cb instead of the slave_cb. You could also define monitor_cb as follows and use it throughout the monitor instead of mixing the master_cb and slave_cb.

      clocking monitor_cb @ (posedge clk);
         default input #1step output #1ns;
         input flavor, color, sugar_free, sour, taste;
      endclocking: monitor_cb
  3. Keisuke,
    In Monitor Code ,line no 24 to 28 you are capturing the signal value from interface(jb_vi.slave_cb) but ,not understanding why you are creating & using the jelly_bean_transaction for flavour & colour, but not for sugar_free, sour ? can you explain this breif

    1. The type of jb_vi.slave_cb.flavor is logic[2:0] and the type of jb_vi.slave_sb.color is logic[1:0], whereas the type of jb_tx.flavor is jelly_bean_transaction::flavor_e and the type of jb_tx.color is jelly_bean_transaction::color_e. To assign a logic value to an enum value, a type cast (parentheses that are prefixed with the casting type and an apostrophe) is required (lines 25 and 26). On the other hand, the type of jb_tx.sugar_free and jb_tx.sour is bit, so you don’t need a type cast on lines 27 and 28.

  4. Hello Keisuke,

    I was wondering what would happen if you use slave_sync_mp interface modport to connect to the dut instead of slave_mp. I wanted to know why is it necessary here to define a asynchronous modport and sync modport. Why sync modport alone is not sufficient.

    1. If you use the slave_sync_mp for the DUT, it won’t compile (you can try it). While the synchronous modport is used by the test-bench to have a higher level of timing abstraction, the DUT uses the asynchronous modport because the timing is design dependent.

  5. Hello Keisuke,

    In the agent class there is an analysis port declared “uvm_analysis_port#(jelly_bean_transaction) jb_ap”. So this becomes analysis port in the agent to analysis port in monitor connection. Can I instead use uvm_analysis_export object since i have to export the transaction to subscriber?

      1. hello, Keisuke, thanks a lot for your posting, i am a freshman using UVM, and could u give some examples about uvm_analysis_export/ uvm_analysis_port / uvm_analysis_imp, most of time when I use these ports to do connection, l have some confusion about that which port could be connected , and could u give some introduce ? thank u so much

  6. Hi Keisuke,

    Thank you for your tutorial,
    Could we use model to control inside driver ?
    I mean we instance a model inside a drive to control dut.

    Thanks you

  7. Hi keisuke,

    I want to access signals of one agent from another agent…means how to modify signals of another agent?

  8. Hi Keisuke,

    When i create driver using the following statement :

    cpu_driver cpu_driver_m;

    cpu_driver_m = cpu_driver::type_id::create(“cpu_driver_m”,this);

    where driver is

    class cpu_driver extends uvm_driver #(cpu_transaction,cpu_transaction);

    I get an error saying:
    ERROR VCP2852 “Incompatible types at assignment: .this.cpu_driver_m <- create(""cpu_driver_m"",this,"""").

    I am not able to understand what is wrong here. Can you help me.

    Simulator used is Riveira Pro EDU on eda playground

    Thanks

    1. Hi Vabby,

      I created a simple example using your class and it compiles fine (you can try this here). If you can point me the path to your EDA Playground, I might be able to help.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      
      import uvm_pkg::*;
      `include "uvm_macros.svh"
       
      typedef int cpu_transaction;
       
      class cpu_driver extends uvm_driver #( cpu_transaction, cpu_transaction );
        `uvm_component_utils( cpu_driver )
       
        function new( string name, uvm_component parent );
          super.new( name, parent );
        endfunction
      endclass
       
      class cpu_agent extends uvm_agent;
        `uvm_component_utils( cpu_agent )
        cpu_driver cpu_driver_m;
       
        function new( string name, uvm_component parent );
          super.new( name, parent );
        endfunction
       
        function void build_phase( uvm_phase phase );
          super.build_phase( phase );
          cpu_driver_m = cpu_driver::type_id::create( "cpu_driver_m", this );
        endfunction
      endclass
  9. Hello,

    I am generating stimulus and passing to sequencer – > driver. Checking is done in scoreboard. In scoreboard there is a stimulus field which I am matching with user defined value. If it matched I have to send only once the broadcasted transaction via sequencer -> driver. Please tell me how can I add the transaction item in sequencer queue?

    Thanks,

    1. I’m afraid I don’t fully understand your question, but you can create a sequence that generates the transaction, then do:

      the_sequence_that_generates_the_transaction.start( your_sequencer );

      as usual. Let me know if I misunderstood your question.

  10. Hello Keisuke,
    I have a question related to clocking blocks. In the code for the driver, after the “forever” statement, I see the clock used like this “@jb_vi.master_cb”. I wanted to ask what would be different if i write “@jb_vi.clk”?
    Regards,
    Gautam

    1. @jb_vi.master_cb is equivalent to @( posedge jb_vi.clk ) in our case (see the clocking declaration of master_cb; line 8 of jelly_bean_if) whereas @jb_vi.clk waits for any value change of the jb_vi.clk (not just posedge).

  11. Hi Keisuke,

    Thanks for the tutorials. could you please tell me the reason behind creating NO_FLAVOR and why uvm_monitor should poll for NO_FLAVOR in the jelly_bean_if interface.

    regards,
    naveen

  12. Hi,
    I understand the relation between sequence-sequencer-driver..

    If i see it correctly the sequencer is in charge of the randomization of the sequence (while sequence may contain constraint),
    and to pass is through the fifo to the driver.

    1. Is this correct?
    2. So why actually do we need the driver? the driver could do the generation part and thats it?

    So what am i missing here?

Leave a Reply