Jelly Beans Example Uvm Explained
Jelly Beans Example Uvm Explained
Jelly Beans Example Uvm Explained
Overview
July 24, 2011 Keisuke Shimizu
Last Updated: April 4, 2014
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.
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_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.
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.
`uvm_object_utils_begin(jelly_bean_transaction)
`uvm_field_enum(flavor_e, flavor, UVM_ALL_ON)
`uvm_field_enum(color_e, color, UVM_ALL_ON)
`uvm_field_int(sugar_free, UVM_ALL_ON)
`uvm_field_int(sour, UVM_ALL_ON)
`uvm_field_enum(taste_e, taste, UVM_ALL_ON)
`uvm_object_utils_end
endclass: jelly_bean_transaction
Sugar-Free Jelly Bean
The jelly_bean_transaction class can be extended to various classes. For example, to create
only sugar-free jelly beans, the child class can define the constraint as shown in line 4.
1
2
3 class sugar_free_jelly_bean_transaction extends jelly_bean_transaction;
4 `uvm_object_utils(sugar_free_jelly_bean_transaction)
5
6 constraint sugar_free_con { sugar_free == 1; }
7 function new(string name = "");
8 super.new(name);
9 endfunction: new
10 endclass: sugar_free_jelly_bean_transaction
11
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.
task body();
jelly_bean_transaction jb_tx;
jb_tx = jelly_bean_transaction::type_id::create(.name("jb_tx"), .contxt(get_full_name()));
start_item(jb_tx);
assert(jb_tx.randomize()); finish_item(jb_tx);
endtask: body
endclass: one_jelly_bean_sequence
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.
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.
The interface also has the modport lists, which define the directions of the signals within the
interface. We define four modport lists:
Directions Seen
Name Timing Used by
from
Synchronous
master_sync_mp Master Test-bench
with master_cb
Synchronous
slave_sync_mp Slave Test-bench
with slave_cb
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!
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.
1
class jelly_bean_driver extends uvm_driver#(jelly_bean_transaction);
2
`uvm_component_utils(jelly_bean_driver)
3
4
virtual jelly_bean_if jb_vi;
5
6
function new(string name, uvm_component parent);
7
super.new(name, parent);
8
endfunction: new
9
10
function void build_phase(uvm_phase phase);
11
super.build_phase(phase);
12
void'(uvm_resource_db#(virtual jelly_bean_if)::read_by_name
13
(.scope("ifs"), .name("jelly_bean_if"), .val(jb_vi))); endfunction:
14
build_phase
15
16
task run_phase(uvm_phase phase);
17
jelly_bean_transaction jb_tx;
18
19
forever begin
20
@jb_vi.master_cb;
21
jb_vi.master_cb.flavor <= jelly_bean_transaction::NO_FLAVOR;
22
23 seq_item_port.get_next_item(jb_tx); @jb_vi.master_cb;
24 jb_vi.master_cb.flavor <= jb_tx.flavor;
25 jb_vi.master_cb.color <= jb_tx.color;
26 jb_vi.master_cb.sugar_free <= jb_tx.sugar_free;
27 jb_vi.master_cb.sour <= jb_tx.sour;
28 seq_item_port.item_done(); end
29 endtask: run_phase
30 endclass: jelly_bean_driver
31
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 class jelly_bean_monitor extends uvm_monitor;
3 `uvm_component_utils(jelly_bean_monitor)
4
5 uvm_analysis_port#(jelly_bean_transaction) jb_ap;
6
7 virtual jelly_bean_if jb_vi;
8
9 function new(string name, uvm_component parent);
10 super.new(name, parent);
11 endfunction: new
12
13 function void build_phase(uvm_phase phase);
14 super.build_phase(phase);
15 void'(uvm_resource_db#(virtual jelly_bean_if)::read_by_name
16 (.scope("ifs"), .name("jelly_bean_if"), .val(jb_vi)));
17 jb_ap = new(.name("jb_ap"), .parent(this));
18 endfunction: build_phase
19
20 task run_phase(uvm_phase phase);
21 forever begin
22 jelly_bean_transaction jb_tx;
23 @jb_vi.slave_cb;
24 if (jb_vi.slave_cb.flavor != jelly_bean_transaction::NO_FLAVOR)
25 begin jb_tx =
26 jelly_bean_transaction::type_id::create(.name("jb_tx"),
27 .contxt(get_full_name()));
28
29 jb_tx.flavor = jelly_bean_transaction::flavor_e'(jb_vi.slave_cb.flavor);
30 jb_tx.color = jelly_bean_transaction::color_e'(jb_vi.slave_cb.color);
31 jb_tx.sugar_free = jb_vi.slave_cb.sugar_free;
32 jb_tx.sour = jb_vi.slave_cb.sour;
33 @jb_vi.master_cb;
34 jb_tx.taste = jelly_bean_transaction::taste_e'(jb_vi.master_cb.taste);
35 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.
This post will provide a continued explanation on the rest of the verification
components.
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 typedef class jelly_bean_scoreboard;
3
4 class jelly_bean_sb_subscriber extends uvm_subscriber#(jelly_bean_transaction);
5 `uvm_component_utils(jelly_bean_sb_subscriber)
6
7 function new(string name, uvm_component parent);
8 super.new(name, parent);
9 endfunction: new
10
11 function void write(jelly_bean_transaction t);
12 jelly_bean_scoreboard jb_sb;
13 $cast( jb_sb, m_parent );
14 jb_sb.check_jelly_bean_taste(t);
15 endfunction: write
16 endclass: jelly_bean_sb_subscriber
The check_jelly_bean_taste function expects the DUT module to “respond negatively to the sour
chocolate-flavor jelly bean, while reacting positively to the other combinations.” When the DUT
responds properly, the jelly-bean flavor and color are printed. When the DUT is not functioning
correctly, the function will print an error message.
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.
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.
1. the registration of the configuration of the simulation into the database (line 17)
2. the direction on the usage of the sugar_free_jelly_bean transaction rather than
the jelly_bean_transaction (line 19)
3. the creation of the jelly_bean_env (line 20)
Lines 13 to 18 were provided for future configuration uses, and were added as placeholders. An
explanation of the configuration was omitted from this series due to the lack of configuration needs.
Please see Configurations if you are interested in this topic.
Configuration
For reference, the source code for the jelly_bean_configuration class is inserted below.
1
2 class jelly_bean_configuration extends uvm_object;
3 `uvm_object_utils(jelly_bean_configuration)
4
5 function new(string name = "");
6 super.new(name);
7 endfunction: new
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.
1
2 module top;
3 import uvm_pkg::*;
4
5 reg clk;
6 jelly_bean_if jb_slave_if(clk);
7 jelly_bean_taster jb_taster(jb_slave_if);
8
9 initial begin // clock generation
10 clk = 0;
11 #5ns ;
12 forever #5ns clk = ! clk;
13 end
14
15 initial begin
16 uvm_resource_db#(virtual jelly_bean_if)::set
17 (.scope("ifs"), .name("jelly_bean_if"), .val(jb_slave_if));
18 run_test();
19 end
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.
UVM_INFO jb.sv(406) @ 40: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
jb_tx sugar_free_jelly_bean_transaction - @818
flavor flavor_e 3 CHOCOLATE
color color_e 2 RED
sugar_free integral 1 'h1
sour integral 1 'h1
taste taste_e 2 YUCKY
----------------------------------------------------------------
UVM_INFO jb.sv(406) @ 60: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
jb_tx sugar_free_jelly_bean_transaction - @834
flavor flavor_e 3 CHOCOLATE
color color_e 2 GREEN
sugar_free integral 1 'h1
sour integral 1 'h0
taste taste_e 2 YUMMY
----------------------------------------------------------------
UVM_INFO jb.sv(406) @ 80: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------
jb_tx sugar_free_jelly_bean_transaction - @842
flavor flavor_e 3 APPLE
color color_e 2 GREEN
sugar_free integral 1 'h1
sour integral 1 'h1
taste taste_e 2 YUMMY
------------------------------------------------------------
UVM_INFO jb.sv(406) @ 100: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------
jb_tx sugar_free_jelly_bean_transaction - @850
flavor flavor_e 3 APPLE
color color_e 2 RED
sugar_free integral 1 'h1
sour integral 1 'h0
taste taste_e 2 YUMMY
------------------------------------------------------------
With these six posts, the basic UVM tutorial is complete. While this jelly-bean machine is
hypothetical, it has the ability of being rephrased with other DUT. In the next possible opportunity, I
would like to review other topics omitted from these posts, such as Virtual Sequence. I hope this
tutorial assisted in your further understanding on UVM.
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
The virtual sequence defines three new jelly-bean recipes
(LEMON_MERINGUE_PIE, STRAWBERRY_SHORTCAKE, and CANDY_APPLE) on the second line. Each
recipe requires two jelly-bean flavors. For example, to create the LEMON_MERINGUE_PIE recipe,
two LEMON jelly beans and two COCONUT jelly beans are necessary. Two sub-sequences
(same_flavored_jelly_beans_sequence) are created (line 19) to generate two flavors.
The case statement on the line 21 prepares two jelly-bean flavors based on the recipe. At the end of
the body task, the two sub-sequences are started in parallel (line 42).
class jelly_bean_recipe_virtual_sequence extends uvm_sequence#(
uvm_sequence_item );
typedef enum bit[1:0] { LEMON_MERINGUE_PIE, // 2 LEMON + 2 COCONUT
STRAWBERRY_SHORTCAKE, // 2 STRAWBERRY + 2 VANILLA
9 CANDY_APPLE // 2 APPLE + 1 CINNAMON
10 } recipe_e; rand recipe_e recipe;
11
12 jelly_bean_sequencer jb_seqr1;
13 jelly_bean_sequencer jb_seqr2;
14
15 same_flavored_jelly_beans_sequence jb_seq1;
16 same_flavored_jelly_beans_sequence jb_seq2;
17
18 function new( string name = "" );
19 super.new( name );
20 endfunction: new
21
22 task body();
23 jb_seq1 = same_flavored_jelly_beans_sequence::type_id::create( .name(
24 "jb_seq1" ), .contxt( get_full_name() ) );
25 jb_seq2 = same_flavored_jelly_beans_sequence::type_id::create( .name(
26 "jb_seq2" ), .contxt( get_full_name() ) );
27 case ( recipe ) LEMON_MERINGUE_PIE:
28 begin
29 jb_seq1.flavor = jelly_bean_transaction::LEMON;
30 jb_seq2.flavor = jelly_bean_transaction::COCONUT;
31 jb_seq1.num_jelly_beans = 2;
32 jb_seq2.num_jelly_beans = 2;
33 end
34 STRAWBERRY_SHORTCAKE: begin j
35 b_seq1.flavor = jelly_bean_transaction::STRAWBERRY;
36 jb_seq2.flavor = jelly_bean_transaction::VANILLA;
37 jb_seq1.num_jelly_beans = 2;
38 jb_seq2.num_jelly_beans = 2;
39 end
40 CANDY_APPLE: begin
41 jb_seq1.flavor = jelly_bean_transaction::APPLE;
42 jb_seq2.flavor = jelly_bean_transaction::CINNAMON;
43 jb_seq1.num_jelly_beans = 2;
44 jb_seq2.num_jelly_beans = 1;
45 end
46 endcase // case ( recipe )
47 `uvm_info( get_name(), { "\n", this.sprint() }, UVM_LOW )
48 fork
49 jb_seq1.start( .sequencer( jb_seqr1 ), .parent_sequence( this ) );
50 jb_seq2.start( .sequencer( jb_seqr2 ), .parent_sequence( this ) );
51 join endtask: body
52
53 `uvm_object_utils_begin( jelly_bean_recipe_virtual_sequence )
`uvm_field_enum ( recipe_e, recipe, UVM_ALL_ON )
`uvm_field_object( jb_seq1, UVM_ALL_ON )
`uvm_field_object( jb_seq2, UVM_ALL_ON )
`uvm_object_utils_end
endclass: jelly_bean_recipe_virtual_sequence
Test
The jelly_bean_recipe_test class creates the above mentioned virtual sequence. Firstly, the test
assigns two jelly_bean_sequencers to the virtual sequence (line 13 and 14). By doing this, the
sub-sequence, jb_seq1, will run on the sequencer in the agent #1, and the sub-sequence, jb_seq2,
will run on the sequencer in the agent #2. The test randomizes the virtual sequence and starts the
sequence on the line 15 and 16. Note that the sequencer argument of the start task
takes null since there is no sequencer associated with the virtual sequence.
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.
This post will give an explanation on UVM configuration objects, since the earlier posts did not cover
much on them. The jelly-bean verification platform uses two kinds of configuration
objects, jelly_bean_agent_config and jelly_bean_env_config. The former configures
the jelly_bean_agent and the latter configures the jelly_bean_env. The figures below show the
verification platform and the class diagram of the configuration-related classes.
Verification Platform
Class Diagram of the Configuration Classes
Agent Configuration
The jelly_bean_agent_config class configures the jelly_bean_agent. The class has two
switches; active and has_jb_fc_sub (lines 4 and 5). The active switch controls whether the
agent is in active mode or in passive mode. In the active mode, a sequencer
(jelly_bean_sequencer) and a driver (jelly_bean_driver) will be created. In the passive mode,
no sequencer or driver will be created. Similarly, the has_jb_fc_sub switch controls whether the
agent instantiates a functional coverage subscriber (jelly_bean_fc_subscriber) or not. Based on
the values of the switches, the agent will be structured as one of the four possible configurations
shown in the figure below. The jelly_bean_agent_config class also has a handle to
the jelly_bean_if (line 7).
1
2
3 class jelly_bean_env_config extends uvm_object;
4 `uvm_object_utils( jelly_bean_env_config )
5
6 bit has_jb_agent1 = 1; // switch to instantiate an agent #1 bit has_jb_agent2 =
7 1; // switch to instantiate an agent #2 bit has_jb_sb1 = 1; // switch to
8 instantiate a scoreboard #1 bit has_jb_sb2 = 1; // switch to instantiate a
9 scoreboard #2
10 jelly_bean_agent_config jb_agent_cfg1; jelly_bean_agent_config jb_agent_cfg2;
11 function new( string name = "" );
12 super.new( name );
13 endfunction: new
14 endclass: jelly_bean_env_config
15
Top Module
The top Verilog module instantiates two jelly_bean_ifs (line 6 and 7) and stores them in
configuration database (lines 17 to 20). The cntxt and inst_name provide the scope information of
the virtual interface being stored. Since the top module is not a uvm_component, null is used as
the cntxt. The uvm_test_top is the name of the top-level uvm_component instantiated by
the run_test() task of the uvm_root class.
1
module top;
2
import uvm_pkg::*;
3
4
reg clk;
5
6
jelly_bean_if jb_if1( clk ); jelly_bean_if jb_if2( clk );
7
jelly_bean_subsystem dut( jb_if1, jb_if2 );
8
9
initial begin
10
clk = 0;
11
#5ns ;
12
forever #5ns clk = ! clk;
13
end
14
15
initial begin
16
uvm_config_db#( virtual jelly_bean_if )::set ( .cntxt( null ),
17
.inst_name( "uvm_test_top" ), .field_name( "jb_if1" ), .value( jb_if1 ) );
18
uvm_config_db#( virtual jelly_bean_if )::set ( .cntxt( null ), .inst_name(
19
"uvm_test_top" ), .field_name( "jb_if2" ), .value( jb_if2 ) ); run_test();
20
end
21
endmodule: top
Base Test
The base test builds configuration objects as follows:
1. The base test creates one configuration object (jb_env_cfg) for the verification environment,
and two configuration objects (jb_agent_cfg1 and jb_agent_cfg2) for the jelly-bean agents
(lines 16 to 18).
2. The jelly_bean_ifs, which we’ve created in the top module, are retrieved from the
configuration database. Then each retrieved interface is assigned to the corresponding agent
configuration (lines 20 to 27).
3. The agent configurations are assigned to the jb_env_cfg (lines 29 and 30).
4. The jb_env_cfg is stored in the configuration database so that the verification environment can
get its configuration from the database later (line 32 and 33).
1
2 class jelly_bean_base_test extends uvm_test;
3 `uvm_component_utils( jelly_bean_base_test )
4
5 jelly_bean_env jb_env;
6 jelly_bean_env_config jb_env_cfg;
7 jelly_bean_agent_config jb_agent_cfg1;
8 jelly_bean_agent_config jb_agent_cfg2;
9
10 function new( string name, uvm_component parent );
11 super.new( name, parent );
12 endfunction: new
13
14 function void build_phase( uvm_phase phase );
15 super.build_phase( phase );
16
17 jb_env_cfg = jelly_bean_env_config ::type_id::create( "jb_env_cfg" );
18 jb_agent_cfg1 = jelly_bean_agent_config::type_id::create( "jb_agent_cfg1" );
19 jb_agent_cfg2 = jelly_bean_agent_config::type_id::create( "jb_agent_cfg2" );
20
21 if ( ! uvm_config_db#( virtual jelly_bean_if )::get ( .cntxt(
22 this ), .inst_name( "" ), .field_name( "jb_if1" ), .value( jb_agent_cfg1.jb_if
23 ) ) )
24 begin `uvm_error( "jelly_bean_test", "jb_if1 not found" ) end
25 if ( ! uvm_config_db#( virtual jelly_bean_if )::get ( .cntxt(
26 this ), .inst_name( "" ), .field_name( "jb_if2" ), .value( jb_agent_cfg2.jb_if
27 ) ) )
28 begin `uvm_error( "jelly_bean_test", "jb_if2 not found" ) end
29
30 jb_env_cfg.jb_agent_cfg1 = jb_agent_cfg1;
31 jb_env_cfg.jb_agent_cfg2 = jb_agent_cfg2;
32 uvm_config_db#( jelly_bean_env_config )::set ( .cntxt( this ),
33 .inst_name( "*" ), .field_name( "jb_env_cfg" ), .value( jb_env_cfg ) );
34 jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ), .parent(
35 this ) );
36 endfunction: build_phase
37 endclass: jelly_bean_base_test
Environment
The verification environment builds itself using its configuration object as follows:
1. The environment class gets its configuration object (jb_env_cfg) from the configuration
database (lines 17 to 20).
2. If the configuration object indicates the agent #1 to be created, then the environment creates it
and stores its configuration (jb_agent_cfg1) to the configuration database (lines 23 to 25).
3. A scoreboard is created if the configuration object indicates to do so (lines 27 to 29).
4. The analysis port of the agent and the export of the scoreboard are connected if the both objects
are instantiated (lines 47 and 48).
endclass: jelly_bean_env
Agent
The agent builds itself using its configuration object as follows:
1. The agent class gets its configuration object (jb_agent_cfg) from the configuration database
(lines 19 to 22).
2. If the configuration object indicates the agent is active, then the agent creates a sequencer and
a driver (lines 24 to 27).
3. A functional coverage subscriber is created if the configuration object indicates to do so (lines 29
to 31).
4. The port of the driver and the export of the sequencer are connected if the agent is active. The
virtual interface of the driver is also connected (lines 42 to 45).
5. The analysis port of the agent and the analysis export of the functional-coverage subscriber are
connected if the functional-coverage subscriber exists (lines 47 to 49).
1
class jelly_bean_agent extends uvm_agent;
2
`uvm_component_utils( jelly_bean_agent )
3
4
jelly_bean_agent_config jb_agent_cfg;
5
jelly_bean_sequencer jb_seqr;
6
jelly_bean_driver jb_drvr;
7
jelly_bean_monitor jb_mon;
8
jelly_bean_fc_subscriber jb_fc_sub;
9
10
uvm_analysis_port#( jelly_bean_transaction ) jb_ap;
11
12
function new( string name, uvm_component parent );
13
super.new( name, parent );
14
endfunction: new
15
16
function void build_phase( uvm_phase phase );
17
super.build_phase( phase );
18
19
if ( ! uvm_config_db#( jelly_bean_agent_config )::get (
20
.cntxt( this ), .inst_name( "" ), .field_name( "jb_agent_cfg" ), .value(
21
jb_agent_cfg ) ) ) begin `uvm_error( "jelly_bean_agent",
22
"jb_agent_cfg not found" ) end
23
if ( jb_agent_cfg.active == UVM_ACTIVE ) begin jb_seqr =
24
jelly_bean_sequencer::type_id::create( .name( "jb_seqr" ), .parent( this ) );
25
jb_drvr = jelly_bean_driver ::type_id::create( .name( "jb_drvr" ), .parent(
26
this ) ); end
27
28 if ( jb_agent_cfg.has_jb_fc_sub ) begin jb_fc_sub =
29 jelly_bean_fc_subscriber::type_id::create( .name( "jb_fc_sub" ), .parent(
30 this ) ); end
31 jb_mon = jelly_bean_monitor::type_id::create( .name( "jb_mon" ),
32 .parent( this ) );
33 endfunction: build_phase
34
35 function void connect_phase( uvm_phase phase );
36 super.connect_phase( phase );
37
38 jb_mon.jb_if = jb_agent_cfg.jb_if;
39 jb_ap = jb_mon.jb_ap;
40
41 if ( jb_agent_cfg.active == UVM_ACTIVE ) begin
42 jb_drvr.seq_item_port.connect( jb_seqr.seq_item_export );
43 jb_drvr.jb_if = jb_agent_cfg.jb_if; end
44 if ( jb_agent_cfg.has_jb_fc_sub ) begin jb_ap.connect(
45 jb_fc_sub.analysis_export ); end
46 endfunction: connect_phase
47 endclass: jelly_bean_agent
48
49
50
51
Sequence Diagram
The following sequence diagram summarizes the configuration process described above.
This post will explain how to use the UVM Register Abstraction Layer (RAL) to generate register
transactions. The figure below shows the verification platform used for this post. Among other things,
the jelly_bean_reg_block, the jelly_bean_reg_adapter, and
the jelly_bean_reg_predictor are the classes used for the register abstraction.
Verification Platform
The figure below shows the diagram of the RAL-related classes. The standard UVM classes are
shown in pink, while the jelly-bean classes are shown in light blue. The diagram looks busy, but bear
in mind that I will explain each jelly-bean class one by one.
Diagram of the Jelly-Bean-Register Related Classes
Register Definitions
In the previous posts, the DUT had no accessible registers. We are going to add the registers that
hold jelly-bean recipe information and its taste. We will also add a command input port to the DUT so
that we can write a jelly-bean recipe to the register and read its taste. The figure below shows the
register definition of the DUT.
DUT
Registers
The source code of the DUT (jelly_bean_taster) is shown below. When the command input
is WRITE, the values of flavor, color, sugar_free, and sour input ports are written to the RECIPE
register (line 22 to 25). When the command input is READ, the TASTE register is read out and
the taste output is driven accordingly (line 27).
Register Model
The model of the RECIPE register is defined by extending the uvm_reg class. Each field of the
register is defined as a uvm_reg_field (line 4 to 7). The fields are configured in the build function.
Note that the name, build, is used for convenience. Do not confuse it with the build_phase of
the uvm_component because the uvm_reg is not a uvm_component.
endclass: jelly_bean_reg_block
Register Adapter
The jelly_bean_reg_adapter class provides two functions to convert between
a uvm_reg_bus_op and a jelly_bean_transaction. The reg2bus function converts
a uvm_reg_bus_op into a jelly_bean_transaction, whereas the bus2reg function converts
a jelly_bean_transaction back to a uvm_reg_bus_op.
Register Predictor
The register predictor updates the values of the register model based on observed bus transactions.
As the jelly-bean register predictor is not involved with any kind of extended features,
the uvm_reg_predictor is used as is.
Environment Configuration
The jelly_bean_env_config has a handle to the jelly_bean_reg_block so that
the jelly_bean_env can access the register model.
Environment
The jelly_bean_env instantiates the jelly_bean_agent and
the jelly_bean_reg_predictor (line 28 to 31), then connects register-related objects:
Secondly, the register map and the register adapter are associated with the register predictor (line
49 and 50). The register predictor will use the register map and the register adapter to convert
a jelly_bean_transaction back to a register operation.
Lastly, the register predictor is connected to the agent to subscribe the jelly_bean_transactions
from the agent (line 51).
Base Test
The base test instantiates a jelly_bean_reg_block (line 16 and 17) and stores its handle in
the jelly_bean_env_config (line 19 and 20).
1
2 class jelly_bean_sequence extends uvm_sequence#( jelly_bean_transaction );
3 `uvm_object_utils( 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 = jelly_bean_transaction::type_id::create( .name( "jb_tx" ),
12 .contxt( get_full_name()));
13 start_item( jb_tx );
14 jb_tx.flavor = jelly_bean_types::APPLE;
15 jb_tx.color = jelly_bean_types::GREEN;
16 jb_tx.sugar_free = 0;
17 jb_tx.sour = 1;
18 finish_item(jb_tx);
19 endtask: body
endclass: jelly_bean_sequence
Sequence Using Register Abstraction
The jelly_bean_reg_sequence is another sequence to generate a sour-green-apple jelly bean, but
using the register abstraction. This sequence is extended from the uvm_reg_sequence class so that
we can use the convenience functions such as write_reg() and read_reg(). The body of the
sequence writes a recipe (line 23) to the RECIPE register, then reads back its taste from the TASTE
register (line 24). Note that we do not create a jelly_bean_transaction in the sequence. The
register adapter will convert the register operations into the
corresponding jelly_bean_transactions.
1
2 class jelly_bean_reg_sequence extends uvm_reg_sequence;
3 `uvm_object_utils( jelly_bean_reg_sequence )
4
5 function new( string name = "" );
6 super.new( name );
7 endfunction: new
8
9 virtual task body();
10 jelly_bean_reg_block jb_reg_block;
11 jelly_bean_types::flavor_e flavor;
12 jelly_bean_types::color_e color;
13 bit sugar_free;
14 bit sour;
15 uvm_status_e status;
16 uvm_reg_data_t value;
17
18 $cast( jb_reg_block, model );
19 flavor = jelly_bean_types::APPLE;
20 color = jelly_bean_types::GREEN;
21 sugar_free = 0;
22 sour = 1;
23
24 write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor }
25 status, value ); endtask: body
26
27 endclass: jelly_bean_reg_sequence
Register Test
The jelly_bean_reg_test creates a jelly_bean_reg_sequence we have just created and starts
the sequence (line 12 to 15).
Simulation
Let’s look at a simulation result. The simulation successfully generated a sour-green apple and read
back its taste from the DUT.
1
2
3
4 UVM_INFO jb3.sv(727) @ 30: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
5 ---------------------------------------------------------
Name Type Size Value
6 ---------------------------------------------------------
7 jb_tx jelly_bean_transaction - @7929
8 flavor jelly_bean_types::flavor_e 3 APPLE color jelly_bean_types::color_e 2
9 GREEN sugar_free integral 1 'h0 sour integral 1 'h1
jelly_bean_types::taste_e 2 NO_TASTE
10 ---------------------------------------------------------
11
12 UVM_INFO jb3.sv(727) @ 60: uvm_test_top.jb_env.jb_sb [jelly_bean_scoreboard] You have a good sense of taste.
13 ----------------------------------------------------------
Name Type Size Value
14 ----------------------------------------------------------
15 jb_tx jelly_bean_transaction - @7928
16 flavor jelly_bean_types::flavor_e 3 NO_FLAVOR
17 color jelly_bean_types::color_e 2 NO_COLOR
sugar_free integral 1 'h0
18 sour integral 1 'h0
19 command jelly_bean_types::command_e 2 READ taste jelly_bean_types::taste_e 2
20 YUMMY----------------------------------------------------------
21
22
I hope this tutorial helped you to understand the UVM Register Abstraction.
UVM factory is used to create UVM objects and components. This post will explain the UVM factory
using jelly beans (as you expected) and reveal what happens behind the scenes in the factory.
::type_id
In Transactions and Sequences, we defined the jelly_bean_transaction class. Then
the one_jelly_bean_sequence created a jelly_bean_transaction object as follows:
`uvm_object_utils_begin()
`m_uvm_object_registry_internal()
`m_uvm_object_create_func()
`m_uvm_get_type_name_func()
`m_uvm_field_utils_begin()
`uvm_object_utils_end()
As you see on the line 17, the type_id is nothing but a uvm_object_registry type.
type_id
::create()
Now we know what the type_id is, so let’s look at the ::create() that follows the type_id. If you
look at the UVM source code, src/base/uvm_registry.svh, you will find that the create() is a
static function of the uvm_object_registry class.
The following sequence diagram shows how the one_jelly_bean_sequence creates
a jelly_bean_transaction.
Type Override
In Tasting, the jelly_bean_test class overrode the jelly_bean_transaction with
a sugar_free_jelly_bean_transaction by doing:
jelly_bean_transaction::type_id::set_type_override(sugar_free_jelly_bean_tran
saction::get_type());
As we saw earlier, the jelly_bean_transaction::type_id is a uvm_object_registry type.
The set_type_override() is another static function of the uvm_object_registry. The sequence
diagram below shows how the set_type_override() overrides a type. Overriding a type involves
the following steps:
Firstly, the jelly_bean_test calls
the sugar_free_jelly_bean_transaction::get_type() to get the uvm_object_registry of
the sugar_free_jelly_bean_transaction(steps 1, 2, and 3)
Secondly, the jelly_bean_test calls the set_type_override(), which in turn calls
the set_type_override_by_type() of the uvm_factory (steps 4 and 5)
Lastly, the uvm_factory creates a uvm_factory_override object and put it in an override
queue (steps 6 and 7)
A UVM driver and a UVM sequencer are connected using a UVM sequence item port and an export.
This post will explain how the sequence item port works. In Agent, we connected the sequence item
port (seq_item_port) of the jelly-bean driver (jb_drvr) to the sequence item export
(seq_item_export) of the jelly-bean sequencer (jb_seqr) like this:
jb_drvr.seq_item_port.connect( jb_seqr.seq_item_export );
The driver called get_next_item() to get a jelly-bean transaction (jb_tx),
seq_item_port.get_next_item( jb_tx );
and then called item_done() at the end.
seq_item_port.item_done();
We will look at how the above code works in detail in this post. The class diagram related to the
sequence item port is shown below. UVM standard library classes are shown in pink, and the UVM
classes specialized with the jelly_bean_transaction type are shown in yellow.
The following pseudo code shows how these macros are expanded.
As you have seen above, both the get_next_item() and the item_done() simply delegates their
job to this.m_if (lines 39 and 47). But what is this.m_if? Before answering this question, let’s
look at the other side of the connection.
`UVM_IMP_COMMON
`UVM_SEQ_ITEM_PULL_IMP
The following pseudo code shows how these macros are expanded.
1 class uvm_seq_item_pull_imp
2 #( type REQ = jelly_bean_transaction,
3 type RSP = REQ,
4 type IMP = uvm_sequencer #( jelly_bean_transaction,
5 jelly_bean_transaction ) )
6 extends uvm_port_base #( uvm_sqr_if_base #( REQ, RSP ) );
7
8 // `UVM_IMP_COMMON( `UVM_SEQ_ITEM_PULL_MASK, "uvm_seq_item_pull_imp",
9 // | uvm_sequencer #( jelly_bean_transaction,
10 // | jelly_bean_transaction ) )
11 // V
12
13 local uvm_sequencer #( jelly_bean_transaction,
14 jelly_bean_transaction ) m_imp;
15
16 function new( string name,
17 uvm_sequencer #( jelly_bean_transaction,
18 jelly_bean_transaction ) imp );
19 super.new( name, imp, UVM_IMPLEMENTATION, 1, 1 );
20 m_imp = imp;
21 m_if_mask = 9'h1FF;
22 endfunction // new
23
24 // |
25 // +--> `UVM_TLM_GET_TYPE_NAME( "uvm_seq_item_pull_imp" )
26
27 virtual function string get_type_name();
28 return "uvm_seq_item_pull_imp";
29 endfunction // get_type_name
30
31 // `UVM_SEQ_ITEM_PULL_IMP( m_imp,
32 // | jelly_bean_transaction, jelly_bean_transaction,
33 // | t, t )
34 // V
35
36 task get_next_item( output jelly_bean_transaction t );
37 m_imp.get_next_item( t );
38 endtask // get_next_item
39 task try_next_item( output jelly_bean_transaction t );
40 m_imp.try_next_item( t );
41 endtask // try_next_item
42
43 function void item_done( input jelly_bean_transaction t = null );
44 m_imp.item_done( t ); endfunction // item_done
45 task wait_for_sequences();
46 m_imp.wait_for_sequences();
47 endtask // wait_for_sequences
48
49 function bit has_do_available();
50 return m_imp.has_do_available();
51 endfunction // has_do_available
52
53 function void put_response( input jelly_bean_transaction t );
54 m_imp.put_response( t );
55 endfunction // put_response
56
57 task get( output jelly_bean_transaction t );
58 m_imp.get( t );
59 endtask // get
60
61 task peek( output jelly_bean_transaction t );
62 m_imp.peek( t );
63 endtask // peek
64
65 task put( input jelly_bean_transaction t );
66 m_imp.put( t );
67 endtask // put
68
69 endclass // uvm_seq_item_pull_imp
70
71
72
Connection
Now let’s connect the sequence item port and the sequence item export. The following sequence
diagram shows the steps involved in the connection.
Sequence
Diagram
The UVM driver instantiates the seq_item_port (step 1), and the UVM sequencer instantiates
the seq_item_export (step 2).
When seq_item_port.connect(jb_seqr.seq_item_export) is called,
the seq_item_export is stored in the array called m_provided_by (step 3)
Just before entering end_of_elaboration_phase, resolve_bindings() function is called.
This function traverses the port connection and stores “implementation” to the array
called m_imp_list (step 4). The function checks the number of implementations. If it is 1, the
function stores the implementation as m_if (step 5). This is the m_if the sequence item port
delegated its jobs to. If the number of implementations is 0, the seq_item_port is left
unconnected. If the number of implementations is more than 1, it is an error.
When the seq_item_port.get_next_item() is called, the task calls the get_next_item() of
the seq_item_export, which in turn calls the get_next_item() of the uvm_sequencer (steps 6
and 7).
Similarly, when the seq_item_port.item_done() is called, the function calls
the item_done() of the seq_item_export, which in turn calls the item_done() of
the uvm_sequencer (steps 8 and 9).
I hope this post helped you to understand how the sequence item port works.
This post will explain how analysis port and analysis export work.
In Agent, we connected the analysis port (jb_ap) of the jelly-bean monitor (jb_mon) to the analysis
port (jb_ap) of the jelly-bean agent (jb_agent) as follows:
Analysis Port
The jb_ap of the jelly-bean monitor is an object of the uvm_analysis_port class specialized with
the jelly_bean_transaction type. The following pseudo code shows how the class is specialized.
1
2 class uvm_analysis_port #( type T = jelly_bean_transaction )
3 extends uvm_port_base #( uvm_tlm_if_base #(T,T) );
4
5 function new( string name, uvm_component parent );
6 super.new( name, parent, UVM_PORT, 0, UVM_UNBOUNDED_CONNECTIONS );
7 m_if_mask = `UVM_TLM_ANALYSIS_MASK; // = 1 < < 8
8 endfunction // new
9
10 virtual function string get_type_name();
11 return "uvm_analysis_port";
12 endfunction // get_type_name
13
14 function void write( input jelly_bean_transaction t );
15 uvm_tlm_if_base #( jelly_bean_transaction, jelly_bean_transaction ) tif;
16 for ( int i = 0; i < this.size(); i++ ) begin
17 tif = this.get_if( i );
18 if ( tif == null )
19 uvm_report_fatal( "NTCONN",
20 { "No uvm_tlm interface is connected to ",
21 get_full_name(), " for executing write()" },
22 UVM_NONE );
23 tif.write( t ); end
24 endfunction // write
25
26 endclass // uvm_analysis_port
As you have seen above, the write() function of the uvm_analysis_port delegates its job to tif,
which is an object of uvm_tlm_if_base class. The uvm_tlm_if_base class is the base class
of uvm_port_base class, which in turn is the base class of uvm_analysis_imp class (line 22).
Analysis Export
The analysis_export of the jelly-bean-functional-coverage subscriber (jb_fc_sub) is an object of
the uvm_analysis_imp class specialized with the jelly_bean_transactiontype. The following
pseudo code shows how the class is specialized.
The write() function of the uvm_analysis_imp class delegates its job to m_imp. The m_imp is the
jelly-bean-functional-coverage subscriber (jb_fc_sub) we passed as a parameter to
the uvm_analysis_imp class. This means the write() function of the uvm_analysis_imp simply
calls the write() function of the jb_fc_sub.
Connection
Now let’s put the things together. The following sequence diagram shows the steps involved in the
connection.
Sequence Diagram
That’s about the analysis port. I hope this post helped you to understand how the analysis port
works.
This post will explain how configuration database (uvm_config_db) works. In Configurations, we
used the uvm_config_db to store a jelly_bean_if, a jelly_bean_env_config, and
two jelly_bean_agent_configs. This post will analyze how a configuration data is stored and
retrieved. The class diagram related to the configuration database is shown below. Note that we only
showed the classes related to the jelly_bean_if and jelly_bean_agent_config to avoid clutter.
UVM standard library classes are shown in pink, while the UVM classes specialized with
the jelly_bean_transaction type are shown in yellow.
Storing the Virtual InterfacesThese are the steps involved in this course of action.
1. Since the top is a module, not a uvm_component, null is passed as the cntxt when set is
called. In this case, uvm_top, which is the singleton object of uvm_root class, will be used as
the cntxt.
2. An entry of an associative array (m_rsc) is created using the cntxt object as the key, if this
entry does not exist.
3. The entry of the associative array points to a uvm_pool, which has another associative array
(pool).
4. An entry of the associative array is created using the inst_name and the field_name as the key
(lookup). Note that the inst_name and the field_name are concatenated with a string
(“__M_UVM__“) when the lookup is created.
5. If the lookup does not exist, a uvm_resource object is created. If the inst_name is "", the full
name of the cntxt, which is "uvm_test_top", is stored as the scope. The value passed to
the set() function is also stored.
6. The handle to the uvm_resource object is stored in two kinds of uvm_queues. The first kind of
queues store the handles to the uvm_resource objects that have the common field_name.
A uvm_queue is created for every unique field_name. In our case, two uvm_queues are created;
one for the "jb_if1" and the other for the "jb_if2".
7. The second kind of queues store the handles to the uvm_resource objects that have the
common type. A uvm_queue is created for every unique type. In our case, one uvm_queue is
created for the virtual jelly_bean_if type.
8. The queues are registered in the singleton uvm_resource_pool. The uvm_resouce_pool has
two associative arrays. One array (rtab) uses the field_name as the key.
9. The other array (ttab) uses the type of the uvm_resource as the key.
1. Look up the name table of the uvm_resource_pool with the field_name as the key.
2. If the name matched, get the handle of the uvm_queue.
3. Traverse the entries of the queue.
4. If the scope and the type of the object matches, that uvm_resource is stored in a newly
created uvm_queue.
5. The uvm_resource with the highest precedence is retrieved.
6. The value of the resource is returned.
1. The jelly_bean_env calls the set() function with this (jb_env) as the cntxt.
2. An entry of an associative array (m_rsc) is created using the cntxt object as the key, if this
entry does not exist.
3. The entry of the associative array points to a uvm_pool, which has another associative array
(pool).
4. An entry of the associative array is created using the inst_name and the field_name as the key
(lookup). Note that the inst_name and the field_name are concatenated with a string
(“__M_UVM__“) when the lookup is created.
5. If the lookup does not exist, a uvm_resource object is created. Unlike the virtual-interface case,
the inst_name is not "" this time. In this case, the full name of the cntxtobject concatenated
with the inst_name is stored as the scope. The value passed to the set() function is also
stored.
6. The handle to the uvm_resource object is stored in two kinds of uvm_queues. The first kind of
queues store the handles to the uvm_resource objects that have the common field_name.
A uvm_queue is created for every unique field_name. In our case, one uvm_queue is created for
the "jb_agent_cfg".
7. The second kind of queues store the handles to the uvm_resource objects that have the
common type. A uvm_queue is created for every unique type. In our case, one uvm_queue is
created for the jelly_bean_agent_config type.
8. The queues are registered in the singleton uvm_resource_pool. The uvm_resouce_pool has
two associative arrays. One array (rtab) uses the field_name as the key.
9. The other array (ttab) uses the type of the uvm_resource as the key.
One difference from the previous example is that the rtab has only one entry because both
the uvm_agent_cfg1 and the uvm_agent_cfg2 use the same field_name("uvm_agent_cfg").
1. Look up the name table of the uvm_resource_pool with the field_name as the key.
2. If the name matched, get the handle of the uvm_queue.
3. Traverse the entries of the queue.
4. If the scope and the type of the object matches, that uvm_resource is stored in a newly
created uvm_queue. Note that there are two uvm_resource objects, but their scopes are
different. The full name of the cntxt object passed to the get() function distinguishes between
the two.
5. The uvm_resource with the highest precedence is retrieved.
6. The value of the resource is returned.
Summary
That’s about the configuration database. This diagram shows the main objects involved in this
article. I hope this post helped you to understand how the configuration database works.
This post will explain how UVM field macros (`uvm_field_*) work. In Transactions and Sequences,
we used the UVM field macros to automatically implement the standard data methods, such
as copy(), compare(), and pack() for the jelly_bean_transaction.
1
`uvm_object_utils_begin(jelly_bean_transaction)
2
`uvm_field_enum(flavor_e, flavor, UVM_ALL_ON)
3
`uvm_field_enum(color_e, color, UVM_ALL_ON)
4
`uvm_field_int (sugar_free, UVM_ALL_ON)
5
`uvm_field_int (sour, UVM_ALL_ON)
6
`uvm_field_enum(taste_e, taste, UVM_ALL_ON)
7
`uvm_object_utils_end
The following pseudo code shows how these field macros are expanded.
The __m_uvm_field_automation() is then used in uvm_object class. As you see the following
diagram, the uvm_object::copy() calls the __m_uvm_field_automation() with UVM_COPY as the
value of the what__. Similarly uvm_object::compare() calls
the __m_uvm_field_automation() with UVM_COMPARE.
Some Standard Data Methods of the uvm_object ClassBy now you might think that these field
macros are convenient but not efficient. For more efficient and more flexible implementation, we can
use user definable do_*() hooks. As shown above the uvm_object calls the do_*() hook after
calling the __m_uvm_field_automation(). For example, the uvm_object::copy() calls
the do_copy() after calling the __m_uvm_field_automation(), and
the uvm_object::compare() calls the do_compare() after calling
the __m_uvm_field_automation(). By default these do_*() methods are empty. I will explain
more detail about the do_*() methods in the next post. If no field macros are used,
the __m_uvm_field_automation() does almost nothing (only the code between the two highlighted
blocks will remain).
I hope this post helped you to understand the field macros.
This post will explain user-definable do_* hook functions. In Field Macros, we saw that the standard
data methods, such as copy() and compare(), called the user-definable hook functions, such
as do_copy() and do_compare(). I will define these hook functions in this post.
do_copy
The do_copy() method is called by the copy() method. The do_copy() method is used to copy all
the properties of a jelly_bean_transaction object (lines 10 to 14). Note that we have to cast
a uvm_object (rhs) to a jelly_bean_transaction (that) in order to access the properties of
the jelly_bean_transaction object (line 4). We must call the super.do_copy() to copy the
properties defined in the super class (line 9).
do_compare
The do_compare() method is called by the compare() method. The do_compare() is used to
compare each property of the jelly_bean_transaction object. The do_compare()returns 1 if the
comparison succeeds, and returns 0 if the comparison fails (lines 6 to 11). Note that we have to cast
a uvm_object (rhs) to a jelly_bean_transaction (that) again in order to access the properties
of the jelly_bean_transaction object (line 4). We must call the super.do_compare() to compare
the properties of the super class (line 6). The uvm_comparer argument provides a policy object for
doing comparisons, but we do not use it.
1
2
3
4 virtual function bit do_compare( uvm_object rhs, uvm_comparer comparer );
5 jelly_bean_transaction that;
6
7 if ( ! $cast( that, rhs ) ) return 0;
8 return ( super.do_compare( rhs, comparer ) && this.flavor
9 == that.flavor && this.color == that.color &&
10 this.sugar_free == that.sugar_free && this.sour == that.sour
11 && this.taste == that.taste ); endfunction: do_compare
12
do_pack
The do_pack() method is called by the pack(), pack_bytes(), and pack_ints() methods.
The do_pack() is used to pack each propery of the jelly_bean_transaction object using
a uvm_packer policy object. Please see Register Abstraction for how each property is packed.
The packer determines how the packing should be done. We must call the super.do_pack() to
pack the properties of the super class (line 5).
1
virtual function void do_pack( uvm_packer packer );
2
bit R1; // reserved bit
3
bit [5:0] R6; // reserved bits
4
5
super.do_pack( packer ); packer.pack_field_int( .value( flavor ),
6
.size( 3 ) ); packer.pack_field_int( .value( color ), .size( 2 ) );
7
packer.pack_field_int( .value( sugar_free ), .size( 1 ) );
8
packer.pack_field_int( .value( sour ), .size( 1 ) );
9
packer.pack_field_int( .value( R1 ), .size( 1 ) );
10
packer.pack_field_int( .value( taste ), .size( 2 ) );
11
packer.pack_field_int( .value( R6 ), .size( 6 ) );
12
endfunction: do_pack
13
do_unpack
The do_unpack() method is called by the unpack(), unpack_bytes(),
and unpack_ints() methods. The do_unpack() is used to unpack each property of
the jelly_bean_transaction object using a uvm_packer policy object. The packer determines
how the unpacking should be done. We must call the super.do_unpack() to unpack the properties
of the super class (line 5).
1
2 virtual function string convert2string();
3 string s = super.convert2string();
4 s = { s, $psprintf( "\nname : %s", get_name() ) };
5 s = { s, $psprintf( "\nflavor : %s", flavor.name() ) };
6 s = { s, $psprintf( "\ncolor : %s", color.name() ) };
7 s = { s, $psprintf( "\nsugar_free: %b", sugar_free ) };
8 s = { s, $psprintf( "\nsour : %b", sour ) };
9 s = { s, $psprintf( "\ntaste : %s", taste.name() ) };
10 return s;
endfunction: convert2string
Simulation
Here is a simulation result. As you see, all three jelly_bean_transactions have same property
values.
The register abstraction layer (RAL) of UVM provides several methods to access registers. This post
will explain how the register-access methods work. In Register Abstraction, we introduced the
overview of RAL and explained how to define registers. In this post, we will cover how to access the
registers.
Properties of uvm_reg_field
Before diving into the register-access methods, let’s look at how a register value is stored. As seen
in Register Abstraction, uvm_reg_field is the lowest register-abstraction layer which represents the
bits of a register. The uvm_reg_field uses several properties to store a variety of register-field
values:
m_reset["HARD"] stores a hard reset value. Note that the m_reset is an associative array with
a kind of reset as the key.
m_mirrored stores the value of what we think in our design under test (DUT).
m_desired stores the value of what we want to set to the DUT.
value stores the value to be sampled in a functional coverage, or the value to be constrained
when the field is randomized.
Note that among these properties, only the value property is public. The other properties are local,
thus we cannot access them directly from the out side of the class. We will show you how to access
these local properties using register-access methods later.
Properties of uvm_reg_field
configure()
The first thing we do after creating a uvm_reg_field is configuring it. In Register Abstraction, we
configured the flavor field as follows. Note that in Register Abstraction, we defined the flavor field
as "WO" (write-only), but we defined it as "RW" (read/write) here to make the field more generic.
1
2 flavor = uvm_reg_field::type_id::create( "flavor" );
3 flavor.configure( .parent ( this ),
4 .size ( 3 ),
5 .lsb_pos ( 0 ),
6 .access ( "RW" ),
7 .volatile ( 0 ),
8 .reset ( 0 ), .has_reset
9 ( 1 ),
10 .individually_accessible( 0 ) );
If the has_reset argument is 1, the value of reset argument is taken as the "HARD" reset value. If
the has_reset value is 0, the value of reset is ignored. The value of resetshould match the reset
state of the DUT. If you want to modify the reset value after the configuration, you can
use set_reset() method.
set()
The set() method sets the desired value of a register field. The set() method does not set the
value to a register in the DUT. It only sets the value to the m_desired and the value properties of a
register-field object. To actually set the value to the register in the DUT,
use write() or update() method. These methods will be explained later.
flavor.set( .value( 1 ) );
How the set() method works
get()
The get() method gets the desired value of a register field. The get() method does not get the
value from a register in the DUT. It only gets the value of the m_desiredproperty. To actually get the
value from the DUT, use read() or mirror() methods. These methods will be explained later.
Similarly to the get() method, there are two more getters to access the local properties.
The get_reset() retrieves the value of the m_reset[kind] property, while
the get_mirrored_value() method retrieves the value of the m_mirrored property.
randomize()
The randomize() method is a SystemVerilog method. It randomizes the value property of a
register-field object. After the randomization, the post_randomize() method copies the value of
the value property to the m_desired property. Note that the pre_randomize() method copies the
value of the m_desired to the value property if the rand_mode of the value property is OFF.
assert( flavor.randomize() );
How the randomize() method works
write()
The write() method actually writes a value to the DUT.
uvm_status_e status;
Note that if the individually_accessible argument was 0 when the register field was configured,
the entire register containing the field is written, because the field is not individually accessible. In
this case, the m_mirrored values are used as the write values for the other fields.
How the write() method works
read()
The read() method actually reads a register value from the DUT.
uvm_status_e status;
uvm_reg_data_t value;
Note that if the individually_accessible argument was 0 when the register field was configured,
the entire register containing the field is read. In this case, the m_mirroredvalues are updated for
the other fields as well.
How the read() method works
update()
The update() method actually writes a register value to the DUT. The update() method belongs to
the uvm_reg class. The uvm_reg_field class does not have the update()method.
uvm_status_e status;
The write() method takes a value as its argument, while the update() method uses the value
of the m_desired property as the value to write.
The update() method writes the value only if the m_mirrored and the m_desired are not
equal.
Before the update() is executed
The update() method internally calls the write( .value( m_desired ) ). Because of this, the
value of the m_mirrored will be updated as well, after the update.
After the
update() is executed
mirror()
The mirror() method actually reads a register from the DUT.
uvm_status_e status;
The read() method returns the register value to the caller, while the mirror() method does not
return the register value. The mirror() method only updates the value of
the m_mirrored property.
The mirror() method compares the read value against the m_desired if the value of
the check argument is UVM_CHECK. Note that the UVM Class Library document states that it
compares the read value against the mirrored value, but if you look at the line 2,944
of uvm_reg.svh of uvm-1.1c code base, it actually compares against the desired value, not
against the mirrored value.
April 11, 2014: uvm-1.1d code base has corrected this issue. The mirror() compares the read
value against the mirrored value now. Please see the line 2,951 of uvm_reg.svh if you are
curious about this fix.)
Another caveat about the check is that if you set the volatile argument to be 1 when you
configured the register field, the register field won’t be checked even though you set
the check argument to be UVM_CHECK. This is because we cannot predict the value of the
register field deterministically as it might have been changed (volatile) in the DUT.
The mirror() method internally calls do_read() method. This is the same method
the read() method internally calls. Because of this, the mirror() method will update the value and
the m_desired properties, in addition to the m_mirrored property.
predict()
The predict() method updates the mirrored value.
flavor.predict( .value( 1 ) );
The predict() method also updates the value and the m_desired properties.
Summary
The table below summarizes how each method updates the properties of the register-field object.
m_reset
Method ["HARD"] value m_desired
get_mirrored_value()
In the last post, Register Access Methods, we looked at the primary methods of RAL and showed
how they worked. This post will further focus on the read() method and show how the method
actually reads and returns the value of a register. In particular, we will explain the steps #1 to #5 of
the read() method we have seen in the last post in more detail.
The following sequence diagram shows the steps involved in the read() method. We assumed that
the user called the method like this:
uvm_status_e status;
uvm_reg_data_t value;
jb_reg_block.jb_taste_reg.read( status, value );
How the read() method reads and returns the value of a registerThe number in the angle bracket
below corresponds to the number in the figure.
I hope this post helped to understand how the read() method works under the hood.
In the post, Configurations, we looked at the configuration flow of the jelly bean verification. We also
looked at the behind the scenes of the configuration flow in the post, Configuration Database. This
post will focus on the syntax and semantics of accessing the configuration
database. In Configurations, we stored and retrieved
the jelly_bean_if, jelly_bean_env_config, and jelly_bean_agent_config using the
configuration database. The following figure depicts the component hierarchy used
in Configurations on the left, and the configuration database (shown as clouds) on the right. The
code snippets to access the database are shown in the middle.
Accessing Configuration Database
jelly_bean_base_test
(1) (2)
(uvm_test_top)
uvm_test_top.jb_env
uvm_test_top.jb_env.jb_agent1
uvm_test_top.jb_env.jb_agent2
I hope this post helped to understand how to access the configuration database.
This post will explain how to use analysis FIFOs. Let’s assume I wanted a scoreboard that compares
two streams of jelly beans; one stream is for “expected” jelly beans, the other is for “actual” jelly
beans. Also assume the jelly beans are fed to the scoreboard asynchronously. To synchronize the
jelly beans, I used two analysis FIFOs:
Asynchronous Jelly Bean Scoreboard
This is a sample code to implement the above scoreboard. The code should be self-explanatory.
One note is that the get is a blocking task to get the next item from the FIFO (lines 43 and
44). Somehow this task is not documented in the UVM Class Reference, though.
February 14, 2015: Changed to explicitly use the get_peek_export instead of using
undocumented get task.
When both the expected jelly bean and the actual jelly bean become available, the scoreboard
compares them (line 45). Before finishing a simulation (extract_phase), the scoreboard checks
whether there are leftover jelly beans in either FIFO. Isn’t it easy?
Ports
Ports define which access methods to use. There are twenty-three port classes in TLM 1. Each port
is a subclass of the uvm_port_base class, which in turn is a subclass of the uvm_tlm_if_base class
(or a subclass of the uvm_sqr_if_base class in case of the uvm_seq_item_pull_port). See the
class diagram below.
TLM 1 Port ClassesThe uvm_tlm_if_base class and the uvm_sqr_if_base class define the access
Class \ Method put try_ put can_ put get try_ get can_ get peek try_ peek
uvm_blocking_ X
put_port
uvm_nonblocking_ X X
put_port
uvm_put_port X X X
uvm_blocking_ X
get_port
uvm_nonblocking_ X X
get_port
uvm_get_port X X X
uvm_blocking_ X
peek_port
uvm_nonblocking_ X
peek_port
uvm_peek_port X X
uvm_blocking_ X X
get_peek_port
uvm_nonblocking_ X X X
get_peek_port
uvm_get_peek_port X X X X X
uvm_blocking_ X X X
master_port
uvm_nonblocking_ X X X X X
master_port
uvm_master_port X X X X X X X X
uvm_blocking_ X X X
slave_port
uvm_nonblocking_ X X X X X
slave_port
uvm_slave_port X X X X X X X X
uvm_blocking_
transport_port
uvm_nonblocking_
transport_port
uvm_transport_port
uvm_analysis_port
methods of a port. By default, each method issues an error message if it is called. Each port
overrides a subset of the access methods as listed below. For example,
the uvm_blocking_put_port overrides the put method (only). This means if you call a method
other than the put, you will get an error message defined in the uvm_tlm_if_base class.
Exports
Exports are used when you promote imps (see the next section) to a parent component. Similar to
the ports, each export is a subclass of the uvm_port_base class, which in turn is a subclass of
the uvm_tlm_if_base class (or a subclass of the uvm_sqr_if_base class in case of
the uvm_seq_item_pull_export). See the class diagram below.
TLM 1 Export ClassesSimilar to a port class, each export class supports a subset of the access
methods. See the corresponding port in the table above to figure out which methods are supported.
Imps
Imps provide the implementation of access methods. To be precise, the imps delegate the
implementation to the component that actually implements the access methods. The type of the
component is passed as a parameter when an imp is instantiated. Similar to the ports and exports,
each imp is a subclass of the uvm_port_base class, which in turn is a subclass
of uvm_tlm_if_base class (or a subclass of the uvm_sqr_if_base class in case of
the uvm_seq_item_pull_imp). See the class diagram below.
TLM 1 Imp ClassesSimilar to a port class, each imp class supports a subset of the access methods.
See the corresponding port in the table above to figure out which methods are supported.
FIFOs
TLM 1 defines two FIFOs; the uvm_tlm_fifo and the uvm_tlm_analysis_fifo. See the
component figure and the class diagram below.
Channels
TLM 1 defines two channels; the uvm_tlm_req_rsp_channel and
the uvm_tlm_transport_channel. See the component figure and the class diagram below.
TLM 1 Channel Components
TLM 1 Channel Class DiagramThe next post will give you an example of how to use the TLM 1
classes.
Components
We created the following components to demonstrate different kinds of TLM 1 interface.
The jelly_bean_sequencer (the leftmost component) creates an object of
the jelly_bean_transaction, called jb_req. The jb_req is transferred all the way to the right
(jelly_bean_transporter) via several TLM 1 interfaces.
The jelly_bean_transporter evaluates the flavor of the jb_req and returns
another jelly_bean_transaction called jb_rsp (jelly-bean response) with the
updated tasteproperty. The jb_rsp is transferred back to the jelly_bean_subscriber at the end.
Though this example is artificial, it will show you a variety of TLM 1 interfaces.
jelly_bean_sequencer
The jelly_bean_sequencer is a uvm_sequencer specialized with the jelly_bean_transaction.
jelly_bean_master
The jelly_bean_master has one uvm_get_port (get_port, line 4),
one uvm_master_port (master_port, lines 5 and 6), and one uvm_analysis_port (rsp_ap, line
7). The run_phase gets a jb_req from the get_port (line 25), then puts it to the master_port (line
27). The jelly_bean_master waits for a jelly-bean response (jb_rsp, line 29), then writes it to
the rsp_ap (line 31).
jelly_bean_slave
The jelly_bean_slave has one uvm_master_imp (master_export, lines 4 to 6), and
one uvm_transport_port (trans_port, lines 7 and 8). The jelly_bean_slave also has two
queues; the req_q stores the jb_reqs and the rsp_q stores the jb_rsps.
The jelly_bean_slave implements the access methods of a uvm_master_imp;
namely put, try_put, can_put, get, try_get, can_get, peek, try_peek, and can_peek (lines 34
to 81). The run_phase gets a jb_req from the req_q if available, then calls transport of
the trans_port (line 28).
1
2
3 class jelly_bean_transporter extends uvm_component;
4 `uvm_component_utils( jelly_bean_transporter )
5
6 uvm_transport_imp#( .REQ( jelly_bean_transaction ),
7 .RSP( jelly_bean_transaction ),
8 .IMP( jelly_bean_transporter ) ) trans_export;
9 function new( string name, uvm_component parent );
10 super.new( name, parent );
11 endfunction: new
12
13 function void build_phase( uvm_phase phase );
14 super.build_phase( phase );
15 trans_export = new( "trans_export", this );
16 endfunction: build_phase
17
18 virtual task transport( input jelly_bean_transaction jb_req,
19 output jelly_bean_transaction jb_rsp );
20 assert( nb_transport( jb_req, jb_rsp ) );
21 endtask: transport
22
23 virtual function bit nb_transport( input jelly_bean_transaction jb_req,
24 output jelly_bean_transaction jb_rsp );
25 jb_rsp = jelly_bean_transaction::type_id::create( "jb_rsp" );
26 jb_rsp.copy( jb_req );
27 if ( jb_req.flavor == jelly_bean_transaction::CHOCOLATE && jb_req.sour )
28 jb_rsp.taste = jelly_bean_transaction::YUCKY;
29 else jb_rsp.taste = jelly_bean_transaction::YUMMY;
30 `uvm_info( get_type_name(), { "Returning:\n", jb_rsp.sprint() }, UVM_NONE )
31 return 1;
32 endfunction: nb_transport
33 endclass: jelly_bean_transporter
jelly_bean_subscriber
The jelly_bean_subscriber is a uvm_subscriber, which means it has an analysis_export.
The jelly_bean_subscriber implements the write method of the analysis_export (lines 8 to
10).
jelly_bean_agent
The jelly_bean_agent instantiates all the components and connect them together.
1
2
3
4 class jelly_bean_agent extends uvm_agent;
5 `uvm_component_utils( jelly_bean_agent )
6
7 jelly_bean_sequencer jb_seqr;
8 jelly_bean_put_driver jb_put_drvr;
9 uvm_tlm_fifo#( jelly_bean_transaction ) jb_fifo;
jelly_bean_master jb_master;
10
jelly_bean_slave jb_slave;
11 jelly_bean_transporter jb_trans;
12 jelly_bean_subscriber jb_sub;
13
14 function new( string name, uvm_component parent );
15 super.new( name, parent );
16 endfunction: new
17
function void build_phase( uvm_phase phase );
18 super.build_phase( phase );
19 jb_seqr = jelly_bean_sequencer ::type_id::create( .name( "jb_seqr" ), .parent( this ) );
20 jb_put_drvr = jelly_bean_put_driver ::type_id::create( .name( "jb_put_drvr" ), .parent( this ) );
21 jb_fifo = new( .name( "jb_fifo" ), .parent( this ) );
jb_master = jelly_bean_master ::type_id::create( .name( "jb_master" ), .parent( this ) );
22 jb_slave = jelly_bean_slave ::type_id::create( .name( "jb_slave" ), .parent( this ) );
23 jb_trans = jelly_bean_transporter::type_id::create( .name( "jb_trans" ), .parent( this ) );
24 jb_sub = jelly_bean_subscriber ::type_id::create( .name( "jb_sub" ), .parent( this ) );
25 endfunction: build_phase
26
function void connect_phase( uvm_phase phase );
27 super.connect_phase( phase );
28 jb_put_drvr.seq_item_port.connect( jb_seqr.seq_item_export );
29 jb_put_drvr. put_port.connect( jb_fifo.put_export );
30 jb_master. get_port.connect( jb_fifo.get_peek_export );
31 jb_master. master_port.connect( jb_slave.master_export );
32 jb_slave. trans_port.connect( jb_trans.trans_export );
33 jb_master. rsp_ap.connect( jb_sub.analysis_export );
34 endfunction: connect_phase
35 endclass: jelly_bean_agent
36
Simulation
Here is an excerpt of a simulation output.
# UVM_INFO ../src/tutorial_20.sv(152) @ 0: uvm_test_top.jb_env.jb_agent.jb_put_drvr [jelly_bean_put_driver] [seq_item_port]-->{jb_req}-->[put_port]
# UVM_INFO ../src/tutorial_20.sv(188) @ 0: uvm_test_top.jb_env.jb_agent.jb_master [jelly_bean_master] [get_port]-->{jb_req}-->[master_port]
# UVM_INFO ../src/tutorial_20.sv(223) @ 0: uvm_test_top.jb_env.jb_agent.jb_slave [jelly_bean_slave] (master_export)---{jb_req}-->[trans_port]
# UVM_INFO ../src/tutorial_20.sv(282) @ 0: uvm_test_top.jb_env.jb_agent.jb_trans [jelly_bean_transporter] Returning:
# UVM_INFO ../src/tutorial_20.sv(225) @ 0: uvm_test_top.jb_env. # UVM_INFO ../src/tutorial_20.sv(118)
@ 0: uvm_test_top.jb_env.jb_agent.jb_seqr@@jb_seq [one_jelly_bean_sequence] Generated:
jb_agent.jb_slave [jelly_bean_slave] {jb_rsp}< --[trans_port]
# UVM_INFO ../src/tutorial_20.sv(190) @ 0: uvm_test_top.jb_env.jb_agent.jb_master [jelly_bean_master] {jb_rsp}<--[master_port]
# UVM_INFO ../src/tutorial_20.sv(192) @ 0: uvm_test_top.jb_env.jb_agent.jb_master [jelly_bean_master] {jb_rsp}-->[rsp_ap]
# UVM_INFO ../src/tutorial_20.sv(298) @ 0: uvm_test_top.jb_env.jb_agent.jb_sub [jelly_bean_subscriber] Received:
UVM Phases
UVM has nine common phase classes (shown in yellow) and twelve run-time phase classes (shown
in pink). These phase classes are derived from the uvm_topdown_phase, uvm_bottomup_phase,
or uvm_task_phase classes, which in turn are derived from the uvm_phase class.
The uvm_phase class has a virtual function called exec_func and a virtual task called exec_task.
The phase classes derived from the uvm_topdown_phase and the uvm_bottomup_phase implement
the exec_func, while the phase classes derived from the uvm_task_phase implement
the exec_task. As shown in the diagram, each phase class calls the corresponding phase function
or task of the uvm_component. For example, the exec_func of the uvm_build_phase class calls
the build_phase function of the uvm_component, and the exec_task of the uvm_run_phase class
calls the run_phase task of the uvm_component, etc.
UVM Phase Classes
Simplified Flow
The way UVM phases are implemented is rather complicated, but here is a simplified flow.
Simplified Flow
1. We call run_test (if you don’t remember, see the line 17 of the top module in Tasting), which in
turn calls the run_test task of the uvm_root class.
2. The uvm_root calls the m_run_phases task of the uvm_phase class.
3. For each phase, the execute_phase task is called.
4. If the phase is a top-down or bottom-up phase, exec_func is called for each component.
5. For example, the exec_func calls the build_phase function of each component.
6. If the phase is a task phase, exec_task is called for each component.
7. For example, the exec_task calls the main_phase task of each component.
8. The uvm_phase checks if any objections are raised by the components. The phase_done is
the uvm_objection object that the uvm_phase keeps track of the number of objections with.
When we called phase.raise_objection() from inside the run_phase of
the jelly_bean_test class (see the line 27 of the jelly_bean_test class
in Tasting), phase_done.raise_objection() is called in the uvm_phase under the hood.
9. If no objection is raised, all the processes started by the exec_task are killed. In other words,
unless an objection is raised, the phase is immediately killed!
10. The steps 3 to 9 repeat until all phases are executed.
I hope this article helped in your understanding of the UVM phasing.
New Scoreboard
This is our new scoreboard that respects any tastes. For those of you who want to see the original
scoreboard, please see this.
Type Override
There are several ways to override a component using its type.
uvm_component_registry#(T,Tname)::set_type_override(override
_type)
The first method is using the uvm_component_registry. I think this is the easiest way to override a
component type. The line 8 instructs that the factory to override
the jelly_bean_sb_subscriber with the jelly_bean_liberal_scoreboard.
uvm_component::set_type_override_by_type(original_type,
override_type)
The second method is using the set_type_override_by_type function of the uvm_component.
Although the syntax is different, what it does is basically the same as the first method.
uvm_factory.set_type_override_by_type(original_type,
override_type)
The third method is using the set_type_override_by_type function of the uvm_factory. Actually
the above two methods we have seen are merely convenience functions for this third method. The
line 7 gets the current factory, and the line 10 calls the function of the factory.
1
2 class jelly_bean_test extends uvm_test;
3 `uvm_component_utils( jelly_bean_test )
4
5 jelly_bean_env jb_env;
6
7 function void build_phase( uvm_phase phase );
8 uvm_factory factory = uvm_coreservice_t::get().get_factory();
9 super.build_phase( phase );
10 factory.set_type_override_by_type( jelly_bean_sb_subscriber::get_type(),
11 jelly_bean_liberal_scoreboard::get_type() );
12 jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ), .parent( this ) );
13 endfunction: build_phase
14 // ...
endclass: jelly_bean_test
uvm_component::set_type_override(original_type_name,
override_type_name)
The fourth method is using the set_type_override function of the uvm_component. Unlike the
above three methods, this function takes two strings.
uvm_factory.set_type_override_by_name(original_type_name,
override_type_name)
The last method is using the set_type_override_by_name function of the uvm_factory. Actually
the fourth method above is a convenience function of this method.
1
2 class jelly_bean_test extends uvm_test;
3 `uvm_component_utils( jelly_bean_test )
4
5 jelly_bean_env jb_env;
6
7 function void build_phase( uvm_phase phase );
8 uvm_factory factory = uvm_coreservice_t::get().get_factory();
9 super.build_phase( phase );
10 factory.set_type_override_by_name( jelly_bean_sb_subscriber::type_name,
11 jelly_bean_liberal_scoreboard::type_name );
12 jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ), .parent( this ) );
13 endfunction: build_phase
14 // ...
endclass: jelly_bean_test
Instance Override
You can override a specific component instance, instead of replacing all the components of the
same type. Similar to the type override, there are five different methods.
uvm_component_registry#(T,Tname)::set_inst_override(override
_type, inst_path, parent)
The first method is using the uvm_component_registry. I think this is the easiest way to override a
component instance. The line 8 instructs that the factory to override
the jelly_bean_sb_subscriber with the jelly_bean_liberal_scoreboard. We specified the
instance path ("jb_env.jb_sb") relative to the jelly_bean_test‘s hierarchical instance path.
uvm_component::set_inst_override_by_type(relative_inst_path,
original_type, override_type)
The second method is using the set_inst_override_by_type function of the uvm_component.
Although the syntax is different, what it does is basically the same as the first method.
1
2 class jelly_bean_test extends uvm_test;
3 `uvm_component_utils( jelly_bean_test )
4
5 jelly_bean_env jb_env;
6
7 function void build_phase( uvm_phase phase );
8 uvm_factory factory = uvm_coreservice_t::get().get_factory();
9 super.build_phase( phase );
10 factory.set_inst_override_by_type( jelly_bean_sb_subscriber::get_type(),
11 jelly_bean_liberal_scoreboard::get_type(), { get_full_name(), ".jb_env.jb_sb" } );
12 jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ), .parent( this ) );
13 endfunction: build_phase
14 // ...
endclass: jelly_bean_test
uvm_component::set_inst_override(relative_inst_path,
original_type_name, override_type_name)
The fourth method is using the set_inst_override function of the uvm_component. Unlike the
above three methods, this function takes three strings.
The first three methods above take uvm_object_wrapper as their type argument(s).
uvm_factory.set_inst_override_by_name(original_type_name,
override_type_name, full_inst_path)
The last method is using the set_inst_override_by_name function of the uvm_factory. Actually
the fourth method above is a convenience function of this method. Similar to the third method above,
we created the full path by concatenating the two paths.
1
2 class jelly_bean_test extends uvm_test;
3 `uvm_component_utils( jelly_bean_test )
4
5 jelly_bean_env jb_env;
6
7 function void build_phase( uvm_phase phase );
8 uvm_factory factory = uvm_coreservice_t::get().get_factory();
9 super.build_phase( phase );
10 factory.set_inst_override_by_name( jelly_bean_sb_subscriber::type_name,
11 jelly_bean_liberal_scoreboard::type_name, { get_full_name(), ".jb_env.jb_sb" } );
12 jb_env = jelly_bean_env::type_id::create( .name( "jb_env" ), .parent( this ) );
13 endfunction: build_phase
14 // ...
endclass: jelly_bean_test
I covered five different methods to override a type, and another five methods to override an instance.
Personally, I use the first methods because they are the simplest. I hope this article clarified the
difference of each method.