Uvm Random Stability
Uvm Random Stability
Avidan Efody
Mentor Graphics, Corp.
10 Aba Eban Blvd.
Herzilya 46120, Israel
avidan_efody@mentor.com
package my_pkg;
Earlier calls to class some_class;
randomize() endclass
class my_class;
rand int x;
endclass
Object
endpackage
instantiated
module my_top();
import my_pkg::*;
Earlier calls to $urandom()
my_class cls = new();2 //->both
Earlier object instantiations
Earlier forked threads int j = $urandom();3 //->both
initial;
initial begin
automatic int i;
Thread started some_class sc;
fork
some_thread(); //->both
join_none
Earlier calls to $urandom() from static
functions/tasks sc = new();4 //->both
i = $urandom(); //->both
Earlier static object instantiations
Earlier static threads fork
my_thread(); //->both
join_none
end
Top module, program or
interface instantiated task some_thread();
#1;
endtask
p.set_randstate(rand_state);
B. Relative Path Dependency
$display(“and a second identical one %d”,
Dependency on absolute execution path will make random $urandom());
results extremely sensitive to code changes even in a small size endtask
project. By manually setting an RNG to a specific known state, endclass
the execution path up to a certain point becomes a don’t care.
This is referred to as “manual seeding” and makes any
subsequent random results depend only on the relative
execution path from the manual seeding point onwards. The III. UNDERSTANDING UVM RANDOM STABILITY
code below shows how this is done for a thread or an object. In a typical UVM testbench, the following areas are random
class my_class; to some extent:
1. Random Bus Functional Model (BFM) parameters
rand int y; such as delays (when not part of the transaction)
task not_random(); 2. Random configuration parameters (for example
process p = process::self(); register values)
p.srandom(1); // thread reseeding
$display("constant result %d", 3. Sequences and transactions
$urandom()); For 1) and 2) randomization is usually done inside a static
this.srandom(2); // object reseeding
uvm_component. For 3) it is usually done inside a dynamic
randomize(); uvm_sequence or uvm_sequence_item. We will now look at the
$display("and another one %d", y); random stability mechanisms available in each, understand
endtask their limitations, and suggest the best ways to cope with those.
class class_a;
endclass
random values generated by a component to its location in the
Top_env UVM hierarchy, which makes sense, since users don’t expect
two instances in different testbench parts to produce the same
results. It also means that if a testbench becomes a part of a
bigger testbench (i.e. vertically reused), it can no longer be
expected to produce the same random results.
Main_sequence
This would make the results depend on the component
RNG, and since this one is manually seeded in UVM-1.1 (and
OVM), it means that the random value returned would be
affected only by other calls to randomize() of the same object.
The following code shows a similar case: TR_A TR_NEW TR_B TR_C
//…
endclass Sequencer
Although the green line (with the comment “1”) and the red
line (with the comment “2”) might appear equivalent, from a
random stability point of view they are not: while the first Main Parallel
randomize() call would select the object values based on the sequence sequence
parent component RNG, the second randomize() call would
select them based on the object’s own RNG. Since objects
don’t have unique names and are not manually seeded during Sub New sub
their creation, their RNG initialization depends on the RNG Sequence Sequence
state of the thread that instantiated them. In a UVM-1.1a
testbench, however, both methods would be stable enough,
because the instantiating function phase is manually seeded TR_A TR_A TR_A
prior to its execution. Unfortunately, with UVM-1.1 and earlier
this is not the case, and when the second option is used, the TR_N TR_B TR_B
values of the object will once again end up being chosen based
on the absolute execution path.
TR_B
B. uvm_sequence/uvm_sequence_item random stability
1) The requirement Figure 4. Sequence hierarchy modifications that are not expected to alter
original results
Assume a user runs the blue sequence shown in the Fig. 3
below, then for some reason modifies it by adding the red 2) The Solution
sequence item.
Although a component hierarchy stays the same throughout
a simulation, while an entire sequence hierarchy could be
created and destroyed multiple times, the random stability
requirements in both cases are very similar: A change in
hierarchy should not affect existing elements. Therefore, UVM or task more random stable, since their RNG initialization
tries to address this problem with a similar approach to the one depends only on code inside that same function or task.
taken with components. Sequences and sequence items are
isolated from each other and from the rest of the testbench by Unfortunately with UVM-1.1a neither the sequence tasks,
manual seeding which is based on their full name. Since users nor its main thread which calls all of them are reseeded. Users
do not expect identical sequences running on different wishing to avoid random stability issues can either make sure
sequencers to produce identical results (that would be too their code complies with the guidelines given in the
stable), UVM prefixes a sequence or sequence item full name uvm_components section for UVM-1.1 users. Or, they can
with the full component path of the sequencer it is executed on. derive their own base class from uvm_sequence, and
For example, TR_A in Fig. 3 about would be manually seeded implement manual seeding during the pre_start() phase. The
based on the following name: code below, which could be placed in a sequence base class,
shows how to do this.
[Name of uvm_sequencer on which Main_sequence is
running].Main_sequence.TR_A
class reseed_seq#(type REQ =
uvm_sequence_item,type RSP = REQ) extends
3) Limitations uvm_sequence#(REQ, RSP);
a) Unique Naming not Enforced
function new(string name = "");
super.new(name);
Although the whole concept of sequence random stability endfunction: new
rests on the assumption that every sequence and sequence item
are uniquely identifiable by their full name, UVM doesn’t force virtual task pre_start();
sequences and sequence items to have a unique name, or even process proc = process::self();
have a name at all. In the absence of unique names, manual
proc.srandom(uvm_create_random_seed("bo
seeding becomes meaningless, since al items sharing a name
dy", get_full_name()));
would be reseeded based on some sort of a counter to endtask
differentiate them from each other. Random stability as endclass
described in Fig. 3 or Fig 4. simply can’t be achieved in this
case, all the more so because users themselves can’t identify
transactions by any other means except for counting. It is very c) Reseeding timing doesn’t match use model
likely that in such situation, all random results within a specific
sequence hierarchy would change due to an additional
instantiation or an additional call to $urandom() in the thread UVM lets users choose between two ways for creating,
that started the top most sequence. randomizing and running sequences – using `uvm_do macros,
or using create, randomize and start. These ways are shown
We strongly advice users to assign sequences and sequence below, in red and blue corresponding:
items with unique names. This is not only a pre-condition for
random stability as shown in Fig. 3 and Fig. 4 above, but also
makes debugging of long sequences of transactions much class my_sub_sequence extends
easier. As mentioned above, if transactions and sequences are //…
not uniquely named, the only way to match them with, for rand int unsigned num_of_items;
//…
example, whatever shows in the waveform viewer, is by
uvm_sequence#(my_item);
counting. This is time consuming and error prone. //…
To enforce unique naming it is possible to extend the endclass
uvm_sequence/uvm_sequence_item and uvm_sequencer
classes, so that they check if a name of a top level sequence, class my_sequence extends
uvm_sequence#(my_item);
child sequence, or item has already been used prior to
my_sub_sequence sub_sequence;
execution. A simple implementation of these extensions for
UVM 1.1a is shown in appendix A. task body();
b) Sequence thread is not manually seeded
// using create/randomize/start
sub_sequence =
my_sub_sequence::type_id::create(“sub_sequence
In the section dedicated to components we have mentioned
”);
that UVM manually seeds every function and task phase before sub_sequence.randomize();
they’re executed. This makes calls to randomization methods sub_sequence.start(get_sequencer(),this);
that are dependent on the thread RNG, insensitive to anything
that occurs outside the specific function or task in which they //using `uvm_do
are used. It also makes any objects created within the function `uvm_do(sub_sequence)
endtask
endclass Decoupling random parts from each other allows them to
be individually seeded. If a specific part is individually seeded,
only the results within it would change, but everything else
Both options are equivalent to a large extent, but not from a would be kept constant. As mentioned above, this can be
random stability point of view. While with the `uvm_do macro useful, for example, for testing a bug fix. If the bug is related to
the sequence will be manually seeded before it is randomized, several independent parameters and events that happen
with the create/randomize/start method it will be manually together by chance, keeping some of those constant while
seeded only after randomization. This means, for example, that modifying others might reveal similar bugs, or find the
when using create/randomize/start as shown above, an weaknesses of the bug fix.
additional instantiation in the body() task of the parent
sequence will modify the rand fields of the sub-sequence (i.e. As part of the work on this paper we have created a UVM
num_of_items in the example above). Note that this is true only package that can be used to individually seed specific testbench
for uvm_sequences that are executed using start(), and not for elements from the command line. The code of this package is
uvm_sequence_items that are executed using fully available in appendix B of this paper. It allows users to
start_item()/finish_item(). configure a few selected components as random domain roots
(sequences as random domain roots are not supported, although
Since using `uvm_do macros should in general be avoided this could be implemented easily). A random domain root can
[3], we recommend that users simply reseed sequences by be seeded from the command line through a SystemVerilog
themselves before they are randomized. Also, it should be plusarg, or generate its own random seed, which is based on
noted that `uvm_do derivatives can’t create the a top level the main simulation seed, but different from it. It then makes its
sequences, which must always be executed using start(). seed available to all components lower in the hierarchy as a
Therefore using them would solve the problem only partially. configuration parameter. The components lower in the
The code below shows how to use the preferred hierarchy pick up the specific domain seed from the
create/randomize/start method so that manual seeding takes configuration table and reseed themselves.
place before randomization. The solution is to insert a call to
set_item_context() before randomization. This function Note that users of the package are required to always call
initializes the sequencer and parent sequence fields for the sub the super class phase functions and tasks, on the first line of
sequence and allows full name calculation and reseeding. their own implementation. This is needed in order to reseed
execution thread before the task or function are executed.
UVM users normally do this anyhow for build_phase() and
class my_sequence extends sometimes for other phases, so that should not add too much
uvm_sequence#(my_item); overhead.
my_sub_sequence sub_sequence;
The abridged example below shows how the package is
task body(); used. An environment (env) made out of two component
hierarchies that the user defines to be seeded individually:
// using create/randomize/start root_comp1 and root_comp2. Each of these contains random
sub_sequence = fields and some sub components that call $urandom(). The user
my_sub_sequence::type_id::create(“sub_sequence could keep each of these hierarchies constant, while changing
”); the seed and random values of the other. For example
sub_sequence.set_item_context(this, (assuming the user runs the simulation with Mentor’s Questa),
get_sequencer()); running the following two commands:
sub_sequence.randomize();
sub_sequence.start(get_sequencer(),this);
vsim top –sv_seed random +SEED0=1 +SEED1=1
endtask vsim top –sv_seed random +SEED0=1 +SEED1=2
endclass
V. ACKNOWLEDGEMENTS
class root_component extends
multi_seed_component; VI. REFERENCES
[1] IEEE Standard for System Verilog- Unified Hardware, Design,
rand int unsigned num_of_sub_components; Specification and Verificaction Language”, IEEE std 1800-2009, 2009.
constraint max_sub_comps_c {
[2] UVM 1.1a Reference, www.uvmworld.org
num_of_sub_components < 5; };
[3] “Are OVM & UVM Macros Evil? A Cost-Benefit Analysis”, Erickson,
Adam, 2011
sub_component sub_comps[];
//…
sub_comps = new[num_of_sub_components];
//…create sub components
end
endfunction
endclass
super.build_phase(phase);
//…create components
domain2 = new();
domain2.set_id(1);
domain2.name = "root_comp2";
// *** This code is provided as an example only and without guarantee or any commitment to
enhancements/support ***
package unique_seq_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`uvm_component_param_utils(this_type)
string used_names[$];
set_item_context(parent_sequence, sequencer);
// Ensure that the sequence_id is intialized in case this sequence has been stopped
previously
set_sequence_id(-1);
// Remove all sqr_seq_ids
m_sqr_seq_ids.delete();
fork
begin
m_sequence_process = process::self();
m_sequence_state = PRE_START;
#0;
pre_start();
if (call_pre_post == 1) begin
m_sequence_state = PRE_BODY;
#0;
pre_body();
end
m_sequence_state = BODY;
#0;
body();
m_sequence_state = ENDED;
#0;
if (call_pre_post == 1) begin
m_sequence_state = POST_BODY;
#0;
post_body();
end
m_sequence_state = POST_START;
#0;
post_start();
m_sequence_state = FINISHED;
#0;
end
join
endtask
if (sequencer == null)
sequencer = item.get_sequencer();
if(sequencer == null)
sequencer = get_sequencer();
if (sequencer == null)
sequencer = item.get_sequencer();
if (sequencer == null) begin
uvm_report_fatal("STRITM", "sequence_item has null sequencer", UVM_NONE);
end
item.set_item_context(this, sequencer);
if (set_priority < 0)
set_priority = get_priority();
sequencer.wait_for_grant(this, set_priority);
`ifndef UVM_DISABLE_AUTO_ITEM_RECORDING
void'(sequencer.begin_child_tr(item, m_tr_handle, item.get_root_sequence_name()));
`endif
pre_do(1);
endtask
if (seq_item.m_parent_sequence == null)
begin
unique_sequencer#(REQ, RSP) unique_sqr;
string result[$];
if (!$cast(unique_sqr, m_sequencer))
uvm_report_error("UNIQUE_SEQUENCR_NOT_USED", {"Trying to start the sequence ", name,
" , which is derived from unique_sequence, on sequencer ", m_sequencer.get_full_name() ," which
is not derived from unique_sequencer"}, UVM_LOW);
if (result.size() > 0)
uvm_report_error("TOP_SEQ_NOT_UNIQUE", {"Top level sequence by name of ", name, "
already started on the sequencer ", unique_sqr.get_full_name()}, UVM_LOW);
else
unique_sqr.used_names[$+1] = name;
end
else
begin
unique_sequence#(REQ, RSP) parent_unique_sequence;
string result[$];
if (!$cast(parent_unique_sequence, seq_item.m_parent_sequence))
uvm_report_error("UNIQUE_SEQUENCS_NOT_USED", {"Trying to start a sequence derived
from unique_sequence from the sequence ", m_parent_sequence.get_full_name() , " which is not
derived from unique_sequence"}, UVM_LOW);
if (result.size() > 0)
uvm_report_error("SEQ_NOT_UNIQUE", {"Sequence by name of ", name, " already started
by the sequence ", parent_unique_sequence.get_full_name()}, UVM_LOW);
else
parent_unique_sequence.used_names[$+1] = name;
end
endfunction
endclass
endpackage
VIII. APPENDIX B – UVM EXTENSIONS TO ALLOW INDIVIDUAL SEEDING OF VARIOUS TESTBENCH PARTS
// *** This code is provided as an example only and without guarantee or any commitment to
enhancements/support ***
package multi_seed_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`define SEED_NUM 10
string name;
local int unsigned id;
static int unsigned used_ids[$];
if (results.size() > 0)
set_id = 0;
else
begin
this.id = id;
used_ids[$+1] = id;
set_id = 1;
end
end
endfunction
// This component:
// 1. Checks to see if it is set to be a domain root by looking for a domain_root_config
object placed for it in config table
`define function_phase_rereseed(PHASE_NAME) \
function void PHASE_NAME(uvm_phase phase); \
super.PHASE_NAME(phase); \
rereseed(phase); \
endfunction
`define task_phase_rereseed(PHASE_NAME) \
task PHASE_NAME(uvm_phase phase); \
super.PHASE_NAME(phase); \
rereseed(phase); \
endtask
`define component_extension \
\
domain_root_config m_domain_root_config;\
bit root_found;\
bit seed_found;\
int unsigned m_seed;\
\
function new(string name, uvm_component parent = null);\
super.new(name, parent);\
endfunction\
\
function void build_phase(uvm_phase phase);\
super.build_phase(phase);\
\
root_found = uvm_config_db#(domain_root_config)::get(this, "", "domain_root_config",
m_domain_root_config);\
\
if (m_domain_root_config != null)\
// component is root\
begin\
string plusarg_name, s;\
int i;\
$sformat(plusarg_name, "SEED%0d", m_domain_root_config.get_id());\
\
void'($value$plusargs({plusarg_name, "=%s"}, s));\
\
if (s == "random")\
m_seed = seeds[m_domain_root_config.get_id()];\
else\
if ($value$plusargs({plusarg_name, "=%d"}, i))\
m_seed = i;\
else\
uvm_report_error("CMD_LINE_ARG_WRONG", {"Got ", s, " as one of the seed values
specified on the command line. This value is wrong. Allowed values are 'random' or an integer"},
UVM_LOW);\
\
\
uvm_config_db#(int unsigned)::set(this, "*", "seed", m_seed);\
end\
\
seed_found = uvm_config_db#(int unsigned)::get(this, "", "seed", m_seed);\
\
if ((root_found) || (seed_found))\
begin\
int unsigned global_seed = uvm_global_random_seed;\
uvm_global_random_seed = m_seed;\
reseed(); // reseed component RNG\
uvm_global_random_seed = global_seed;\
end\
\
rereseed(phase); // reseed thread RNG for $urandom at build\
endfunction\
\
// phase function and tasks implementations preform reseeding\
// users are required to call these via super, prior to their own implementation\
function void rereseed(uvm_phase phase);\
int unsigned global_seed;\
process p;\
\
if (root_found || seed_found) begin\
global_seed = uvm_global_random_seed;\
uvm_global_random_seed = m_seed;\
p = process::self();\
p.srandom(uvm_create_random_seed(phase.get_name(), get_full_name()));\
uvm_global_random_seed = global_seed;\
end\
endfunction\
\
`function_phase_rereseed(connect_phase)\
`function_phase_rereseed(end_of_elaboration_phase)\
`function_phase_rereseed(start_of_simulation_phase)\
`task_phase_rereseed(run_phase)\
`function_phase_rereseed(extract_phase)\
`function_phase_rereseed(check_phase)\
`function_phase_rereseed(report_phase)\
`function_phase_rereseed(final_phase)\
endpackage