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 // ... endmoduleYou can specify the
modport
list in both the module declaration and the module instance, but they must be identical if you do so. If nomodport
list is specified at all, the signals in the interface are assumed to haveinout
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 thejelly_bean_if
(lines 12 and 13). UVM recommends usinguvm_config_db
instead ofuvm_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 thejelly_bean_if
(lines 14 and 15). Please see the above section for how to useuvm_config_db
instead of theuvm_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.
Hi Keisuke,
Can you elaborate in detail why a sequencer is needed, Also what the above one line code of the sequencer do ??
Typically a sequence generates requests and a sequencer transfers the requests to a driver.
When we start a sequence, its
start()
task calls thebody()
task of itself. Ourone_jelly_bean_sequence
generates ajelly_bean_transaction
in thebody()
task. Thebody()
task callsstart_item()
followed byfinish_item()
. Thestart_item()
asks a grant for the sequencer. Thefinish_item()
sends the request to the sequencer. The sequencer pushes the request to its request FIFO. The driver callsget_next_item()
to the sequencer to fetch the request. When the driver finishes the request, it callsitem_done()
and the sequencer pops the request out of the request FIFO. The illustration below might be helpful to understand the flow.The
uvm_sequencer
is a parameterized class. Our sequencer transfers the requests of thejelly_bean_transaction
type to the driver, so we specialized theuvm_sequencer
with thejelly_bean_transaction
type. You can useuvm_sequencer#(jelly_bean_transaction)
when you instantiate the sequencer, but it is verbose, so we usedtypedef
to give it a shorter name (jelly_bean_sequencer
). This is what the one-linetypedef
does.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
Please see the diagram below. The red bars show blocking tasks.
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
The request FIFO is used to store and arbitrate multiple sequences in the sequencer. The
wait_for_grant
andget_next_item
are independent. Either one can come first.Hi Keisuke,
Can you please give me one scenario where wait for grant comes first with out get_next_item in driver is called
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 callget_next_item
.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?
That is correct. Sequence Arbitration explains the sequence selection in more detail.
I am trying to pass interface to my DUT as you show in your example but model sim errors out with
vlog 2110 : “Illegal reference to interface “intf””
I might be able to help you if you can paste a few more lines ModelSim complained about.
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
Please see this comment.
Keisuke,
In your monitor code, line 29 and 30, why do you use master_cb rather than slave_cb?
Thanks,
Will
Hi Will,
To capture the value of a signal, the signal has to be an input. The
master_cb
declares thetaste
as an input, so I used themaster_cb
instead of theslave_cb
. You could also definemonitor_cb
as follows and use it throughout the monitor instead of mixing themaster_cb
andslave_cb
.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
The type of
jb_vi.slave_cb.flavor
islogic[2:0]
and the type ofjb_vi.slave_sb.color
islogic[1:0]
, whereas the type ofjb_tx.flavor
isjelly_bean_transaction::flavor_e
and the type ofjb_tx.color
isjelly_bean_transaction::color_e
. To assign alogic
value to anenum
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 ofjb_tx.sugar_free
andjb_tx.sour
isbit
, so you don’t need a type cast on lines 27 and 28.Minor item – In your “Agent” section, you have build_phrase instead of build_phase.
I have corrected the typo. Thank you for pointing this out.
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.
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.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?
No. The
uvm_analysis_export
is to export a lower-leveluvm_analysis_imp
to its parent, not to broadcast a transaction to subscribers. The latter is done by theuvm_analysis_port
.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
These articles might help:
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
Yes, you can.
Hi keisuke,
I want to access signals of one agent from another agent…means how to modify signals of another agent?
I don’t know if it is a good idea, but if you pass a virtual interface to the agent, it can modify the signals in the interface.
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
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.
what does this line mean? “typedef int cpu_transaction;”
is it a typo?
No, this is not a typo. Because I donβt know the type of
cpu_transaction
, I just defined it asint
(I was lazy). Normallycpu_transaction
is a class that extendsuvm_sequence_item
.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,
I’m afraid I don’t fully understand your question, but you can create a sequence that generates the transaction, then do:
as usual. Let me know if I misunderstood your question.
Check for the response from driver through response port in sequence and decide accordingly π
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
@jb_vi.master_cb
is equivalent to@( posedge jb_vi.clk )
in our case (see the clocking declaration ofmaster_cb
; line 8 ofjelly_bean_if
) whereas@jb_vi.clk
waits for any value change of thejb_vi.clk
(not justposedge
).Why synchronous modport is used for testbench while asynchronous modport for DUT?
Please see this discussion. Let me know if you need further clarification.
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
I used
NO_FLAVOR
for an idle or no-operation cycle. The monitor creates ajelly_bean_transaction
only if there is some flavor.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?
The sequencer does not randomize sequences. It controls the flow of sequences. The driver pulls sequence items from the sequencer and convert them into pin wiggles.