Clue Logic
Clue Logic
Overview
Accellera’s recently released UVM may change the future of verification, as verification
methodology seems to be consolidated in this UVM. This post will provide a simple
tutorial on this new verification methodology. Rather than focusing on AXI, OCP, or
other system buses in existence, this tutorial will be based on the hypothetical example
of a jelly-bean generator. The test bench will generate many jelly-bean flavors in a
constrained random manner and the system will evaluate palatable flavors. This does
not require the knowledge of any system bus.
The verification components used in the process will be described below.
Verification Components
The left figure shows the relationship of the verification components.
The jelly_bean_taster:-is the design-under-test (DUT) module.
The jelly_bean_sequencer will create jelly-bean recipes and send them to
the jelly_bean_driver.From the information provided in the recipe, the driver creates
jelly beans. The driver passes the jelly beans through the jelly-bean interface
(jelly_bean_if) to the jelly_bean_taster, which will check the jelly-bean taste.
Concurrently, as the jelly beans are being created, the jelly_bean_monitor will
capture the flavor and color of the recently produced. This information will be passed
down to the jelly-bean functional coverage subscriber, referred to as
the jelly_bean_fc_subscriber. The subscriber records and totals the jelly beans
based on their color and flavor.
The jelly_bean_scoreboard is a component that checks if
the jelly_bean_taster is responding correctly. The scoreboard subscribes the
information from the jelly_bean_monitor.
Can you please elaborate the pros and cons of config_db and resource_db. And, the
recommended one?
A general rule of thumb is that you should use uvm_config_db if a hierarchical context
is important, otherwise uvm_resource_db should be used. For example,
the set function of uvm_config_db takes a uvm_component as the first argument to
facilitate the specification of the hierarchical context.
static function void uvm_config_db#(T)::set( uvm_component
cntxt,
string inst_name,
string field_name,
T value );
whereas the set function of uvm_resource_db doesn’t:
static function void uvm_resource_db#(T)::set( string scope,
// any string
string name,
T val,
uvm_object accessor = null );
Having said that, we used uvm_resource_db in our tutorial. We should have
used uvm_config_db instead.
Would you please tell me is there any reason why we use this piece of code?
`ifndef clk_trans
`define clk_trans
....
`endif
It is called include guard, which avoids compiling the same definition more than once.
______________________________________________________________________________
______________________________________________________________________________
While the last post clarified the verification components of the jelly-bean taster, this post
will provide a focus for the jelly-bean recipe.
The jelly-bean recipe is passed as a transaction from the jelly_bean_sequencer to
the jelly_bean_driver. The transaction is referred to as the
jelly_bean_transaction. The transaction defines the jelly bean, based on its flavor,
color, and other characteristics. The similar jelly_bean_transaction is passed from
the jelly_bean_monitor to every subscriber as well.
The jelly_bean_sequencer does not only produce the recipe of the individual jelly
bean – rather it is able to produce a similar flavor of multiple jelly beans, as a sequence
of transactions, and a collection of multiple-flavored jelly beans in the form of a gift box,
as a sequence of sequences .
The class diagram of the transaction and sequence is shown below. The colors of this
diagram correspond to the colors listed in the previous post. The light blue boxes refer
to the classes in the UVM basic class library, while the darker boxes indicate the
classes created in this tutorial.
Class Diagram of Verification Objects
Verification Components
Though this post ends on this note, the next will provide a focus on the SystemVerilog
code itself.
______________________________________________________________________________
______________________________________________________________________________
UVM Tutorial for Candy Lovers – 3. Transactions
and Sequences
This post will provide an explanation on the SystemVerilog code itself. Please
see Recipe for the class diagram.
Transactions
Jelly-Bean Transaction
The jelly_bean_transaction class defines the jelly-bean recipe based on flavor,
color, and other characteristics. It also has the property called taste to hold the
reaction of the jelly_bean_taster (DUT) to the flavor. The relationship between the
flavor and color is defined in the constraint block (line 12). For example, if the flavor is
apple, the color must be red or green (not blue).
Update (April 2, 2014): In this example, we used so-called UVM field macros (lines 23
to 27) to save the effort of writing “do-hook” functions. I would recommend writing your
own “do-hook” functions instead of using these macros once you become familiar with
UVM. For more information, please see Field Macros and “Do” Hooks.
1 class jelly_bean_transaction extends uvm_sequence_item;
2 typedef enum bit[2:0] { NO_FLAVOR, APPLE, BLUEBERRY,
3 BUBBLE_GUM, CHOCOLATE } flavor_e;
4 typedef enum bit[1:0] { RED, GREEN, BLUE } color_e;
5 typedef enum bit[1:0] { UNKNOWN, YUMMY, YUCKY } taste_e;
6
7 rand flavor_e flavor;
8 rand color_e color;
9 rand bit sugar_free;
10 rand bit sour;
11 taste_e taste;
12
13 constraint flavor_color_con { flavor != NO_FLAVOR;
14 flavor == APPLE -> color != BLUE; flavor == BLUEBERRY
15 -> color == BLUE; }
16 function new(string name = "");
17 super.new(name);
18 endfunction: new
19
20 `uvm_object_utils_begin(jelly_bean_transaction)
21 `uvm_field_enum(flavor_e, flavor, UVM_ALL_ON)
22 `uvm_field_enum(color_e, color, UVM_ALL_ON)
23 `uvm_field_int(sugar_free, UVM_ALL_ON)
24 `uvm_field_int(sour, UVM_ALL_ON)
25 `uvm_field_enum(taste_e, taste, UVM_ALL_ON)
26 `uvm_object_utils_end
27 endclass: jelly_bean_transaction
28
29
Sequences
One Jelly Bean
The sequence creates the recipes of the jelly beans being generated. The first
sequence is the simplest, as it is designed to create a single jelly bean. The line 10
creates a single jelly-bean transaction (a recipe), then the line 12 randomizes the
recipe.
1 class one_jelly_bean_sequence extends
2 uvm_sequence#(jelly_bean_transaction);
3 `uvm_object_utils(one_jelly_bean_sequence)
4
5 function new(string name = "");
6 super.new(name);
7 endfunction: new
8
9 task body();
10 jelly_bean_transaction jb_tx;
11 jb_tx =
12 jelly_bean_transaction::type_id::create(.name("jb_tx"),
13 .contxt(get_full_name())); start_item(jb_tx);
14 assert(jb_tx.randomize()); finish_item(jb_tx);
15 endtask: body
endclass: one_jelly_bean_sequence
Now let’s create the multiple jelly beans of the same flavor. The number of jelly beans
being created is specified with the class property called num_jelly_beans. The line 4
constrains the num_jelly_beans to be between 2 and 4. The line 14 creates a single
jelly bean, and the line 15 randomizes its color and flavor. With this jelly bean as the
standard, the line 18 creates jelly beans of the same flavor, as many as specified by
the num_jelly_beans.In-line constraint on the line 21 guarantees the same flavor to be
generated.
This post has an emphasis on the transactions and sequences in the jelly-bean
verification environment. The next post will provide an explanation on the verification
components.
______________________________________________________________________________
______________________________________________________________________________
what advantage does registering all of your fields for automation have.i.e why not just
use ‘uvm_object_utils(jelly_bean_transaction)?
The `uvm_field_* macros automatically implement methods like copy and compare.
But better approach would be creating your own do_copy and do_compare methods.
Please see “do” Hooks for more detail.
The benefits of using the “do” hooks over the field macro are more flexibility and much
less overhead. I think the same benefits apply to sequences, too.
Can you please explain the usage and importance of p_sequencer and m_sequencer in
UVM??
Each sequence has the m_sequencer property that points to the sequencer on which
the sequence is running. The m_sequencer can be used to access configuration
information and other resources in the component hierarchy from the sequence.
For example, if you start a sequence called foo_sequence on a sequencer called
bar_sequencer, foo_sequence.start( bar_sequencer ), then the start() task
will assign foo_sequence.m_sequencer to be the bar_sequencer. Now
the foo_sequence can use the m_sequencer to access the component hierarchy. For
example, you can get the full hierarchical name of the sequencer by
calling m_sequencer.get_full_name(). Note that the type of
the m_sequencer is uvm_sequencer_base. This means if you have your own
sequencer and you have defined new properties in your sequencer, you cannot access
them through the m_sequencer because it does not know them. Now
the p_sequencer comes to the rescue.
The p_sequencer is instantiated if you use `uvm_declare_p_sequencer macro in your
sequence.
For example, you can use the macro in the foo_sequence like this:
The macro declares the p_sequencer using the sequencer type you provided (in this
case, bar_sequencer_class). Then the macro defines the function
called m_set_p_sequencer which casts the m_sequencer to the p_sequencer. Now
you can access the properties defined in the bar_sequencer_class through
the p_sequencer like p_sequencer.my_property from inside the body task of
the foo_sequence.
Let me know if you have further questions.
can i have a p_sequencer in single agent,As you have told above that if we have own
sequencer we can use p_sequencer.As my own sequencer will be extending from the
uvm_sequencer class so it can have access to all configuration details .Then why
p_sequencer is required.
Yes, once you have a handle to your own sequencer, you can access your own
properties in it. The p_sequencer is the handle to your own sequencer from your
sequence.For example,
foo_sequence accesses my_property in bar_sequencer using the p_sequencer
handle as shown below.
I noticed that in the section “Same-Flavored Jelly Beans”, inside of the repeat cycle,
every time a new transaction is created, the methods start_item() and finish_item() are
called in the code (lines 20 and 22) but before that, no start_item() and finish_item()
were called for the single transaction. Why?
The lines 14 to 16 are used only for randomizing the flavor. Then, the line 21 uses this
flavor in the in-line constraint so that all jelly beans have the same flavor. It is fine to
“sandwich” the line 15 with start_item() and finish_item() as you mentioned. That
will create (num_jelly_beans+1) jelly beans, though.
In the Same-Flavored Jelly Beans example, you specified “The line 14 creates a single
jelly bean, and the line 15 randomizes its color and flavor”. But in the transaction item
there are other properties or types like taste, sugar free etc. defined as rand. Won’t they
also get randomized.
Yes, the line 15 randomizes all the properties of jelly_bean_transaction. The line 21
guarantees the same flavor, but other properties such as color are still randomized.
I am trying to build a AHB vip, just to get hold of the basics of UVM. I am finding
difficulty in randomizing the various AHB protocol signals. For e.g, I can randomize
HBURST type , HSIZE type and the starting address. But how do i randomize or create
the HTRANS, HADDR for the same burst type. Based on the burst type, my HADDR
values will change; similarly the HTRANS values. I am presently handling such things in
the driver by generating a loop for the number of beat transfer as dictated by HBURST
type. What will be the best way to handle this?
I think you are doing fine. You don’t need to randomize the HADDR for each beat
because the driver can calculate the next HADDR based on the other information. For
the HTRANS, you can also let the driver insert random BUSY cycles. However, this
approach will not give you the controllability of the delays. I would create an array of
BUSY cycles in the AHB transaction so that I can control the BUSY cycle of each beat.
For example, you could have rand int unsigned busy_cycles[]; in the transaction.
You should also have a constraint for the busy_cycles[] so that you can have no
BUSY-, some BUSY-, or many BUSY-cycle scenarios.
task body();
same_flavored_jelly_beans_sequence jb_seq;
repeat (num_jelly_bean_flavors) begin
jb_seq =
same_flavored_jelly_beans_sequence::type_id::create(.name("jb_se
q"), .contxt(get_full_nam()));
assert(jb_seq.randomize());
jb_seq.start(.sequencer(m_sequencer), .parent_sequence(this));
end
endtask: body
why are you using the start method within the sequence, I think its good to do that in
test. Correct me if I am wrong
You can start another sequence from within a sequence. In this example, jb_seq is
started using the same sequencer (m_sequencer) that
the gift_boxed_jelly_beans_sequence is running on.
If we are having our own sequencer where we have defined new properties then can we
use the same m_sequencer – “jb.seq.start (m_sequencer)”?? Or we need to use
p_sequencer ?? If we need to use p_sequencer then can you please tell us how to use
that. Please correct me if I am wrong.
You can use the m_sequencer if you just want to start a sequence. But if you want to
access the new properties, you need to use the p_sequencer.
why sequence is required? I mean if the testcase is simple then driver and interface
combination can solve my query…please help like how sequence and driver ia
differentiated.
Traditional driver-BFMs use functions and tasks to drive DUT pins. One of the
disadvantages of the BFMs is that it is not easy to randomize stimulus. UVM uses
sequence objects as the scenario of stimulus. Since they are objects, it is easy to
randomize and we often obtain interesting scenarios we have not thought of. UVM
drivers get the sequences and converts them into pin-level activity.
Can You please explain in detail the difference b/w `uvm_do macros with clear
examples ?
All `uvm_do* macros call `uvm_do_on_pri_with macro internally.
The `uvm_do_on_pri_with macro takes four arguments
(SEQ_OR_ITEM, SEQR, PRIORITY, and CONSTRAINTS). Each `uvm_do* macro
specifies a subset of the arguments as follows:
SEQ_OR_ITE PRIORIT CONSTRAINT
Macro\Argument SEQR
M Y S
m_sequence
`uvm_do -1 {}
r
m_sequence
`uvm_do_with -1
r
SEQ_OR_ITE PRIORIT CONSTRAINT
Macro\Argument SEQR
M Y S
m_sequence
`uvm_do_pri {}
r
m_sequence
`uvm_do_pri_with
r
`uvm_do_on -1 {}
`uvm_do_on_with -1
`uvm_do_on_pri {}
`uvm_do_on_pri_wit
h
The `uvm_do_on_pri_with creates a SEQ_OR_ITEM, randomize it
with CONSTRAINTS, and start it on the SEQR with specified PRIORITY.
endclass
A_seq a_seq;
begin
end
endtask
===============================
so when the simulation starts, the data_master in A_seq will be 1. is that correct?
Your `uvm_do_with macro does not constrain the data_master. Here is why.
The `uvm_do_with macro expands to a randomize() function:
a_seq.randomize() with { a_seq.data_master == data_master; };
The names in the in-lined constrait block are searched first in the scope of
the randomize() with object class (A_seq) followed by the scope containing the
method call (B_seq). This means both data_master in the above constraint refer to the
same variable in the A_seq, so you do not constrain them. To solve this issue, you can
use local:: scope resolution:
a_seq.randomize() with { data_master == local::data_master; };
// means a_seq.data_master == this.data_master
This constrains the data_master of a_seq to be equal to the value
of data_master of B_seq.
You can see the entire code here.
I see that you have made a separate class for sugar-free jelly bean and in that class you
have a single constraint. However, usually in a complex system, you might have tens or
hundreds of these kind of “more restricted” class. In your example, you can apple-flavor-
only jelly bean, blue-color-only jelly bean and so on… That basically means you’ll have
tens or hundreds files for each type of special jelly bean you make.
To solve this case, I would usually make these variables (i.e. flavor, color, etc..) as
command line variable. For example, +JELLY_BEAN_FLAVOR=APPLE.
Correspondingly, in the sequence class, when you are randomizing the transaction
using “randomize() with {}”, you can put if-statement to see if these command line
arguments are set, and if set, you can constrain the flavor, color, etc… accordingly.
Is this how you would go about solving this too?
I agree using plusargs would be a convenient way to specify constraint knobs. I usually
define the knobs in a transaction class itself so that the user of the class (a sequence,
for example) does not have to use “randomize() with {}“. The reason I don’t use
“randomize with” is that we cannot override the constraints defined by “with” as they
are hard-coded. The following is what I would do for
the jelly_bean_transaction class to make it more controllable.
1 class jelly_bean_transaction extends uvm_sequence_item;
2 typedef enum bit[2:0] { NO_FLAVOR, APPLE, BLUEBERRY,
3 BUBBLE_GUM, CHOCOLATE } flavor_e;
4 typedef enum bit[1:0] { RED, GREEN, BLUE } color_e;
5 typedef enum bit[1:0] { UNKNOWN, YUMMY, YUCKY } taste_e;
6
7 rand flavor_e flavor;
8 rand color_e color;
9 rand bit sugar_free;
10 rand bit sour;
11 taste_e taste;
12
13 // knobs
14
15 byte unsigned apple_wt = 10;
16 byte unsigned blueberry_wt = 10;
17 byte unsigned bubble_gum_wt = 10;
18 byte unsigned chocolate_wt = 10;
19
20 byte unsigned red_wt = 10;
21 byte unsigned green_wt = 10;
22 byte unsigned blue_wt = 10;
23
24 byte unsigned sugar_free_pct = 50; // between 0 and
25 100
26 byte unsigned sour_pct = 50;
27
28 constraint flavor_con {
29 flavor dist { APPLE := apple_wt,
30 BLUEBERRY := blueberry_wt,
31 BUBBLE_GUM := bubble_gum_wt,
32 CHOCOLATE := chocolate_wt };
33 }
34
35 constraint color_con {
36 color dist { RED := red_wt,
37 GREEN := green_wt,
38 BLUE := blue_wt };
39 }
40
41 constraint sugar_free_con {
42 sugar_free dist { 0 := ( 100 - sugar_free_pct ),
43 1 := sugar_free_pct };
44 }
45
46 constraint sour_con {
47 sour dist { 0 := ( 100 - sour_pct ),
48 1 := sour_pct };
49 }
50
51 constraint flavor_color_con {
52 flavor != NO_FLAVOR;
53 flavor == APPLE -> color != BLUE;
54 flavor == BLUEBERRY -> color == BLUE;
55 }
56
57 function new(string name = "");
58 super.new(name);
59 void'( $value$plusargs( "APPLE_WT=%d", apple_wt
60 ) );
61 void'( $value$plusargs( "BLUEBERRY_WT=%d", blueberry_wt
62 ) );
63 void'( $value$plusargs( "BUBBLE_GUM_WT=%d",
64 bubble_gum_wt ) );
65 void'( $value$plusargs( "CHOCOLATE_WT=%d", chocolate_wt
66 ) );
67
68 void'( $value$plusargs( "RED_WT=%d", red_wt ) );
69 void'( $value$plusargs( "GREEN_WT=%d", green_wt ) );
70 void'( $value$plusargs( "BLUE_WT=%d", blue_wt ) );
71
72 void'( $value$plusargs( "SUGAR_FREE_PCT=%d",
sugar_free_pct ) );
void'( $value$plusargs( "SOUR_PCT=%d", sour_pct
) );
endfunction: new
// ...
endclass: jelly_bean_transaction
______________________________________________________________________
______________________________________________________________________
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 interface jelly_bean_if(input bit clk);
5 logic [2:0] flavor;
6 logic [1:0] color;
7 logic sugar_free;
8 logic sour;
9 logic [1:0] taste;
10
11 clocking master_cb @ (posedge clk); default input
12 #1step output #1ns; output flavor, color, sugar_free,
13 sour; input taste; endclocking: master_cb
14 clocking slave_cb @ (posedge clk); default input
15 #1step output #1ns; input flavor, color, sugar_free,
16 sour; output taste; endclocking: slave_cb
17 modport master_mp(input clk, taste, output flavor, color,
18 sugar_free, sour); modport slave_mp(input clk, flavor,
19 color, sugar_free, sour, output taste); modport
20 master_sync_mp(clocking master_cb); modport
21 slave_sync_mp(clocking slave_cb);endinterface: jelly_bean_if
22
23
24
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
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!
Alternatively, you can specify the modport list in the port connection with the module
instance.
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.
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:
For more information about the configuration database, please see Configuration
Database and Configuration Database Revisited.
1 class jelly_bean_driver extends
2 uvm_driver#(jelly_bean_transaction);
3 `uvm_component_utils(jelly_bean_driver)
4
5 virtual jelly_bean_if jb_vi;
6
7 function new(string name, uvm_component parent);
8 super.new(name, parent);
9 endfunction: new
10
11 function void build_phase(uvm_phase phase);
12 super.build_phase(phase);
13 void'(uvm_resource_db#(virtual
14 jelly_bean_if)::read_by_name (.scope("ifs"),
15 .name("jelly_bean_if"), .val(jb_vi))); endfunction:
16 build_phase
17
18 task run_phase(uvm_phase phase);
19 jelly_bean_transaction jb_tx;
20
21 forever begin
22 @jb_vi.master_cb;
23 jb_vi.master_cb.flavor <=
24 jelly_bean_transaction::NO_FLAVOR;
25 seq_item_port.get_next_item(jb_tx);
26 @jb_vi.master_cb; jb_vi.master_cb.flavor <=
27 jb_tx.flavor; jb_vi.master_cb.color <=
28 jb_tx.color; jb_vi.master_cb.sugar_free <=
29 jb_tx.sugar_free; jb_vi.master_cb.sour <=
30 jb_tx.sour; seq_item_port.item_done(); end
31 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.
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.
Though this post ends here, the next will continue to provide an explanation for other
verification components.
______________________________________________________________________
______________________________________________________________________
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 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.
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 .. ?
Please see the diagram below. The red bars show blocking tasks.
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 call get_next_item.
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.
In your monitor code, line 29 and 30, why do you use master_cb rather than slave_cb?
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
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 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.
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.
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.
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”?
@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).
Why synchronous modport is used for testbench while asynchronous modport for DUT?
Please see this discussion. Let me know if you need further clarification.
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.
______________________________________________________________________
______________________________________________________________________
Subscribers
Functional Coverage
The functional coverage subscriber (jelly_bean_fc_sucbscriber) identifies the
generated jelly beans to take a total tally. The jelly_bean_transaction sent from the
monitor is sampled by the write function on the line 21, and takes the cross coverage
of the flavor, color, and other characteristics of the jelly bean.
1
2
3 class jelly_bean_fc_subscriber extends
4 uvm_subscriber#(jelly_bean_transaction);
5 `uvm_component_utils(jelly_bean_fc_subscriber)
6
7 jelly_bean_transaction jb_tx;
8
9 covergroup jelly_bean_cg; flavor_cp: coverpoint
10 jb_tx.flavor; color_cp: coverpoint jb_tx.color;
11 sugar_free_cp: coverpoint jb_tx.sugar_free; sour_cp:
12 coverpoint jb_tx.sour; cross flavor_cp, color_cp,
13 sugar_free_cp, sour_cp; endgroup: jelly_bean_cg
14 function new(string name, uvm_component parent);
15 super.new(name, parent);
16 jelly_bean_cg = new;
17 endfunction: new
18
19 function void write(jelly_bean_transaction t); jb_tx =
20 t; jelly_bean_cg.sample(); endfunction: writeendclass:
21 jelly_bean_fc_subscriber
22
23
Scoreboard
Many consider it a hassle to create a separate scoreboard class. In our case, it may be
more straightforward to check expected response in the jelly_bean_sb_subscriber,
because the expected response is created from the inside. Yet, there are times when
two analysis ports are necessary, one for expected data and the other for actual data.
The write function of a subscriber does not support multiple analysis ports. One
solution to this problem is to develop a scoreboard class with two subscribers. Thinking
about the extendability in the future, creating two layers like our scoreboard might be a
good idea.
Environment
To provide a conclusion, this section will explain the verification environment that
contains all the verification components. Simply stated, the environment connects the
previously explained agent and the subscribers. Taking a look at the first post of the
series will help better understand this concept.
______________________________________________________________________
________________________________________________________________
What is the need for the scoreboard subscriber? You don’t create a handle to it in the
env, the scoreboard already has an analysis export…..
Is it because a scoreboard class doesn’t have a built in write() method?
Also in the fc subscriber you don’t create a handle to an analysis export, nor do you
reference it in the connect and build phases (the same goes for the scoreboard
subscriber) is that because these are extended uvm_subscribers and have this analysis
export by default ( called analysis_export)? I.e from the uvm base class
The jelly_bean_scoreboard instantiates the local jelly_bean_sb_subscriber. For
this tutorial, the jelly_bean_scoreboard class is not indispensable. I could have used
the jelly_bean_sb_subscriber only and moved
the check_jelly_bean_taste function from the jelly_bean_scoreboard to
the jelly_bean_sb_subscriber. As you mentioned, the reason I had a subscriber was
I wanted the write method in the scoreboard. I also wanted expandability of the
scoreboard so that I could add another subscriber in case I needed the second write.
Another approach to have the second write would be
using `uvm_analysis_imp_decl macro.
Regarding the analysis_export, the uvm_subscriber class (the base class of the fc
and sb subscribers) has the analysis export called analysis_export.
Also, can you give me a brief idea why we reuiqre a subscriber ? or waht is its
significance ?
Is it a gateway between the components in agent and outside ??
A subscriber is a component typically used with a monitor. It “subscribes” the
transactions the monitor sends via an analysis_port. Every time a transaction is sent
by the monitor, the write function of the subscriber is executed. You can use
the write function to collect functional coverage or check the validity of the transaction,
for example.
Can you please explain, why are you casting with m_parent at line no 13 in
jelly_bean_sb_subscriber and also could you please explain the significance of
m_parent there.
Each uvm_component has a property called m_parent, which points to the parent
component that created the component. In this example,
the m_parent of jelly_bean_sb_subscriber points to the jelly_bean_scoreboard.
Please see the line 14 of jelly_bean_scoreboard.
The $cast is required because the type of m_parent is uvm_component, which does
not know anything about the check_jelly_bean_taste function.
I have one small doubt, In jelly_bean_fc_subscriber we are having handle jb_tx of type
jelly_bean_transaction, So why we are not using create/new method to that handle.
The jb_tx is an object handle that points to the same object passed by
the write function (see the line 20 of the jelly_bean_fc_subscriber). Since we do
not create a new object, we call neither new nor create.
I somehow remembered only the uvm_analysis_imp can provide the write method, but
looks here the subscriber’s analysis_export is hooked up to the scoreboard. Could you
please explain why the write method is used with analysis_export?
Another thing I’m not clear is that in subscriber, the scoreboard is declared in “write”
function, in scoreboard, the subscriber is declared as local and further created in the
build_phase. Is there any problem with the double referencing to each other?
The type of the analysis_export of the uvm_subscriber is
actually uvm_analysis_imp.
As you mentioned, the jelly_bean_sb_subscriber and
the jelly_bean_scoreboard each need a handle to the other.
The typedef (the first line) of the jelly_bean_sb_subscriber provides a
forward declaration for the jelly_bean_scoreboard. The typedef lets the
compiler know the actual class definition of the jelly_bean_scoreboard will
follow.
______________________________________________________________________
_________________________________________________________________
UVM Tutorial for Candy Lovers – 6. Tasting
The anticipated culmination of the UVM for Candy Lovers series is revealed in this post.
Using the created verification components and writing out a test class, the actual
simulation is prepared to run.
Test
This particular example will show you how to develop a variety of sugar-free jelly beans
in the form of a gift box. The test class is described below.
jelly_bean_env jb_env;
jb_cfg = new;
assert(jb_cfg.randomize());
uvm_config_db#(jelly_bean_configuration)::set
(.cntxt(this), .inst_name("*"), .field_name("config"),
.value(jb_cfg));
jelly_bean_transaction::type_id::set_type_override(sugar_free_je
lly_bean_transaction::get_type()); jb_env =
jelly_bean_env::type_id::create(.name("jb_env"), .parent(this));
end
endfunction: build_phase
phase.raise_objection(.obj(this));
jb_seq =
gift_boxed_jelly_beans_sequence::type_id::create(.name("jb_seq")
, .contxt(get_full_name())); assert(jb_seq.randomize());
`uvm_info("jelly_bean_test", { "\n", jb_seq.sprint() }, UVM_LOW)
jb_seq.start(jb_env.jb_agent.jb_seqr); #10ns ;
phase.drop_objection(.obj(this));
endtask: run_phase
endclass: jelly_bean_test
Configuration
For reference, the source code for the jelly_bean_configuration class is inserted
below.
1 class jelly_bean_configuration extends uvm_object;
2 `uvm_object_utils(jelly_bean_configuration)
3
4 function new(string name = "");
5 super.new(name);
6 endfunction: new
7 endclass: jelly_bean_configuration
Top
Before proceeding with the simulation, there is a need to write the top module, which
instantiates the DUT module, registers the jelly_bean_if in the resource database,
and runs the test. The top module is responsible for clock generation as well.
module top;
1
import uvm_pkg::*;
2
3
reg clk;
4
jelly_bean_if jb_slave_if(clk);
5
jelly_bean_taster jb_taster(jb_slave_if);
6
7
initial begin // clock generation
8
clk = 0;
9
#5ns ;
10
forever #5ns clk = ! clk;
11
end
12
13
initial begin
14
uvm_resource_db#(virtual jelly_bean_if)::set
15
(.scope("ifs"), .name("jelly_bean_if"),
16
.val(jb_slave_if));
17
run_test();
18
end
19
endmodule: top
Simulation
It is now time to compile and run the simulation, as part of the finale. Posted below are
the results for the simulation.
______________________________________________________________________
______________________________________________________________________
Can you tell me the need to randomize the sequence in the test?
The jb_seq is an object of gift_boxed_jelly_beans_sequence class.
The gift_boxed_jelly_beans_sequence class has one rand property,
called num_jelly_bean_flavors. The jb_seq.randomize() randomizes
this num_jelly_bean_flavors.
But have doubt like why have u randomised gift_box_jelly_bean sequence because it is
getting randmised in the same sequence class.
And one more thing.can u please explain the concept of config db and resource db and
the syntax direction to use it
The gift_boxed_jelly_beans_sequence is randomized in order to randomize its knob
(num_jelly_bean_flavors). Regarding the configuration database, I have written a
new article. Let me know if you have further questions. The uvm_config_db provides a
convenience interface to the uvm_resource_db. You don’t need to use
the uvm_resource_db.
Do you have any good idea for a chip level pin automation configuration in UVM
verification environment? For example, we always have some pin config to put the chip
into some specific working mode, such as Normal Function mode, Function Test mode,
Scan mode, etc. we could use pullup/pulldown as well as register configuration setting
to implement this function in module based testbench. But how do I do it in class based
testbench UVM?
If you need to drive these primary pins from a class-based object, I would probably
create an interface that includes these pins and pass it to the testbench using
a uvm_config_db.
// your interface
interface pin_interface;
logic test_mode;
logic scan_mode;
...
// your DUT
DUT dut( .test_mode( pin_if.test_mode ),
.scan_mode( pin_if.scan_mode ),
...
initial begin
uvm_config_db#( virtual pin_interface )::set( .cntxt( null
),
.inst_name( "uvm_test_top*" ), .field_name( "pin_if" ),
.value( pin_if ) );
end
...
The UVM-side gets the interface via the uvm_config_db and stores it as a virtual
interface and drives it as needed.
// your UVM driver
class your_driver extends uvm_driver#(your_transaction);
virtual pin_interface pin_if;
logic [10:0] p;
real q;
int r;
p = c_function(q,r);
endtask: run_phase
You can use $display("%h",p) or the `uvm_info macro as usual. Calling a C-
function is transparent to the caller. You can use a C-function as if it is a SystemVerilog
function.
How to decide what string to use for “.inst_name” argument in the uvm_config_db’s set
function? Can we use any arbitrary string for this argument?
You can use an arbitrary string for the inst_name as long as the object who gets the
field (getter) also knows the inst_name. But typically we use the instance name of the
getter (often with a wildcard, *, if you want multiple objects to get the field) to avoid
unnecessary dependency.
Thanks for all your answers. I have an issue while i try to run the jelly bean example in
this tutorial. The exact error is
UVM_ERROR @ 50: run [TEST_DONE_NOHIER] A non-hierarchical object,
‘common.run’ () was used in a call to uvm_test_done.drop_objection(). For this
objection, a sequence or component is required.
UVM_FATAL @ 50: run [OBJTN_ZERO] Object “common.run” attempted to drop
objection ‘run’ count below zero
I know that without looking at the code it will be difficult to say what is going wrong but
any idea on what could be going wrong? I don’t know where to start looking for to fix
this. Also how do we debug a uvm test bench, specifically is there a switch which will
enable printing of the sequence of task call like a call stack in other languages?
It seems that the first argument of the drop_objection call is incorrect. You can
specify +UVM_OBJECTION_TRACE to trace objection activities when you run a simulation.
Thanks Keisuke. I had make a mistake in the drop_objection argument. I had given
“phase” as the argument instead of the the test object
Can you Please clarify about the difference when we call seq in run_phase (seq.start())
and specifying the default sequence in build phase. Can you Please explain.
If you have a default sequence, it is executed automatically at the beginning of the
specified phase (such as the main_phase). If you use start(), you can fully control its
execution (timing, conditional execution, and the number of executions, etc.).
Did you know the mix of two lemon and two coconut jelly beans will create the flavor of
lemon meringue pie? And the mix of two strawberry and two vanilla jelly beans will
create the flavor of strawberry shortcake? This post will provide an explanation on the
virtual sequence to create these new jelly-bean recipes.
Overview
The first figure shows the relationship of the verification components used in this post.
The jelly_bean_taster (DUT) from the previous posts was “enhanced” to take two
jelly-bean flavors at the same time through two jelly_bean_ifs. This new DUT is
referred to as the jelly_bean_taster_subsystem. To drive the two interfaces, two
instances of jelly_bean_agent are used.
The jelly_bean_recipe_virtual_sequence orchestrates the creation of jelly-bean
flavors in order to make a new flavor. The second figure at the bottom of the page
shows the verification components in a class diagram, and the third figure shows the
verification objects in a class diagram.
Verification Platform
Virtual Sequence
Test
Simulation
Let’s run a simulation to see what flavors the virtual sequence generates. In my case,
the sequence generated a CANDY_APPLE recipe. It in turn made the first sequence
(jb_seq1) generate two APPLE jelly beans, and made the second sequence (jb_seq2)
generate one CINNAMON jelly bean.
Could also explain about Virtual Sequencers ? As per your explanation, I understood
that Virtual Sequences co-ordinates multiple sequences on different sequencers. how
does it differ from Virtual Sequencer.
Yes, a virtual sequence is nothing but a sequence. The only difference is that it co-
ordinates multiple sequences on different sequencers. A virtual sequencer also has the
handles to the coordinated sequencers, and you can create a virtual sequence that runs
on the virtual sequencer. However, this approach adds an unnecessary component
(virtual sequencer) to the verification environment and it negatively affects the
reusability of the environment. I would recommend not to use the virtual sequencer.
Why do we need virtual sequencer when the same can be done with the above
example.
What are the disadvantage of using the above example
A virtual sequencer is not needed. Please see my comment above.
What’s the benefit of using the virtual sequence? We can do pretty much do the same
thing in the test, right? Is it just to group the sequences? Thanks, Cate
There are a couple of benefits using the virtual sequence over doing the same in a test.
It is easier to reuse. You can reuse the virtual sequence used in the module level
to the full-chip level, for example. If you coordinate the sequences in a test, you
will need to modify the test because it is tightly coupled with the component
hierarchy.
You can easily create hierarchical sequences if you need to do so in the future.
Since the bus_item class has the default parameter values, you can also write:
class d_bus_item extends bus_item; // < -- same as writing
bus_item#( 8, 8 )
Similarly, when you did:
class my_seq extends uvm_sequence#( packet );
you specialized the uvm_sequence class with the packet type.
You can add new parameters to the derived class, too.
For a derived class also can change the parameters, I need to declare it this way.
class A_item#(int a) extends B_item#(int a)
`uvm_object_param_utils(A_item#(int a)) <– I used the param_utils, and I included the
param.
If my drived class will use a fixed parameter like 8, I need declare it this way.
class A_item extends B_item#(8) <– I used the fix parameter
`uvm_object_utils(A_item) <– this is not parameterized class
You are right.
Hi, I am just eager how a sequence will be run if sequencer handle is not passed.
Say, if vir_seq.start(null) is called , please explain how does it start() method works.
Basically the start method executes the body task of the vir_seq, but no sequencer is
involved when the sequencerargument is null.
How I set a value to variable in the sequence, when i start the sequence with
`uvm_do_* macro? Because I don’t need to create the sequence when I use uvm_do. I
tried setting the variable as rand type and tried to use uvm_do_with, but it doesn’t work.
If you could paste how did you call the `uvm_do_with, I might be able to help.
hanks, I was able to make it work with `uvm_do_with. But my requirement is that I don’t
want to set the variable always, but only on certain occasions. Making it as rand type
randomizes it always(even when I just use `uvm_do).
How about disabling random variables
with rand_mode like your_variable.rand_mode(0)?
1) I recently came across an interview question regarding Virtual sequencer.I know the
application of virtual sequencer.
Interview Question : What is virtual sequencer? Is it possible to drive two different
sequences with out using virtual sequences/sequencers on different agents?
– If Yes,Then what is the purpose of virtual sequences/sequencers?
– If No,what makes the advantage using it.
can you let me know with some scenario ?
You can start sequences on multiple sequencers without using a virtual sequence. But if
you do, you can extend it, reuse it, and create hierarchical sequences with it.
I have a question: can we start a virtual sequence in another virtual sequence (like in
no3 I see sequence of sequences by using m_sequencer ). Can we do similar things
with two (or more) virtual sequences. If possible could you please recommend the way
?
Yes, you can. This is a virtual sequence
(gift_boxed_jelly_beans_virtual_sequence) that runs another virtual sequence
(jelly_bean_recipe_virtual_sequence).
class gift_boxed_jelly_beans_virtual_sequence extends
uvm_sequence;
`uvm_object_utils( gift_boxed_jelly_beans_virtual_sequence )
constraint num_jelly_bean_flavors_con {
num_jelly_bean_flavors inside { [2:3] }; }
task body();
repeat ( num_jelly_bean_flavors ) begin
assert( jb_seq.randomize() );
jb_seq.start( .sequencer( null ) ); // start another
virtual sequence
end
endtask: body
endclass: gift_boxed_jelly_beans_virtual_sequence
And this is a test that runs the above virtual sequence.
class jelly_bean_recipe_test extends jelly_bean_base_test;
`uvm_component_utils( jelly_bean_recipe_test )
Hi, If I create a virtual_seq with only one sequence and start this virtual_seq in my test
with actual sequencer (instead of null), then will that be considered as normal
sequence? If yes, then only way simulator knows it is a virtual sequence is by the way
start is called?
I would say your sequence is a non-virtual sequence, not because you started the
sequence on a (non-null) sequencer, but because it is a sequence of another
sequence.
You can start a virtual sequence on a (non-null) sequencer. For example, the virtual
sequence can send sequence items to the sequencer while it starts other sequences on
their sequencers. My definition of virtual sequence is that it starts sub-sequences
on multiple sequencers.
Can we run a sequence on multiple sequencers in parallel, can you please explain.
You cannot start the same sequence on multiple sequencers in parallel unless you
clone it because the sequence keeps track of its sequence state (once a sequence is
started, it cannot be started again until it is finished or stopped).
virtual sequencer and physical sequencer exit from main phase prematurely before test
completes. have you seen this? if so, how to resolve it? Thank you.
Sorry for the very slow reply. It’s most likely objections are dropped prematurely. You
could use +UVM_OBJECTION_TRACE command-line argument to trace objection activity.
// knobs
task body();
jelly_bean_transaction jb_tx;
repeat ( num_jelly_beans ) begin
jb_tx = jelly_bean_transaction::type_id::create( .name(
"jb_tx" ) );
start_item( jb_tx );
assert( jb_tx.randomize() with { this.flavor ==
self.flavor; } );
`uvm_info( get_name(), { "n", jb_tx.sprint() }, UVM_LOW )
finish_item( jb_tx );
end
endtask: body
`uvm_object_utils_begin( same_flavored_jelly_beans_sequence )
`uvm_field_int ( num_jelly_beans,
UVM_ALL_ON )
`uvm_field_enum( jelly_bean_transaction::flavor_e, flavor,
UVM_ALL_ON )
`uvm_object_utils_end
endclass: same_flavored_jelly_beans_sequence
For virtual sequence reused by different tests, should we put the virtual sequence in
env? Is that a good way? Otherwise is there any better way to handle the virtual
sequence?
I would create a package for the virtual sequence and let the test import it.
Will please explain the difference between import a package and include a file with a
good example?
The `include is a compiler directive used to insert a file as if it is copy & pasted.
`include "jelly_bean_pkg.sv" // contents of the
jelly_bean_pkg.sv is inserted here
On the other hand, the import declaration DOES NOT insert the contents of the
package. It just provides direct visibility of identifiers within a package. For example,
unless you import the package, you need to use the package name qualifier when you
use the jelly_bean_transaction declared in the jelly_bean_pkg.
jelly_bean_pkg::jelly_bean_transaction jb_xact; // note the
"jelly_bean_pkg::"
With the import declaration, you can use the jelly_bean_transaction without the
package name qualifier.
import jelly_bean_pkg::*; // all identifiers in the package are
"visible" in this scope