This article is to list down the most used constructions of UVM to my personal understanding.
UVM simulator steps
- VCS(Synopsys)
- IUS(Cadence)
- Questa(Mentor)
1. set $UVMHOME to instal dir of required UVM library
2. Use irun option -uvmhome to reference $UVMHOME
3. Use incdir to reference any included file directories
%irun -f run.f
run.f:
-uvmhome $UVMHOME
-incdir .../sv
.../sv/tb_pkg.sv
top.sv
top.sv:
module top();
import uvm_pkg::*;
import tb_pkg::*;
initial begin
//run test
...
end
endmodule: top
Debugging case:
when using 3 step compile on vcs, vlogan and elab must both add uvm_dpi.cc to actually compile the c. add the file in file list does not work.
if using 1 step compile, then just need to include the file in filelist.
UVM Directory Structure
contains two kinds of code:
UVM_ROOT UVM_TOP
Tips: (1) debug features
uvm_top.print_topology(); //advised to be called from end_of_elaboration_phase
=======================================
If you look into the uvm source code, run_test() task is actually a task defined in the class uvm_root. it's the implicit top-level and phase controller for all UVM components. the UVM automatically creates a single instance of <uvm_root> that users can access via the global (uvm_pkg-scope) variable uvm_top.
- long time confusion solved: the run_test() called in top tb module is defined in uvm_globals.svh which actually calls the run_test() in uvm_root.
- uvm_test_top is not a variable in uvm_root, how can you access that with uvm_root?
- class uvm_root extends uvm_component
- const uvm_root uvm_top = uvm_root::get();
- uvm_top is the top-level component, and any component whose parent is specified as NULL becomes a child of uvm_top.
- uvm top manages the phasing for all components.
- set globally the report verbosity, log files, and actions(?).
- Because uvm_top is globally accessible (in uvm_pkg scope(?)), UVM's reporting mechanism is accessible from anywhere outside uvm_component, such as in modules and sequences.
UVM Configuration
1) uvm_config_db
- uvm_config_db#(int)::set(this, "env.agent", "is_active", UVM_PASSIVE);
- uvm_config_db#(int)::set(null, "uvm_test_top.env.agent", "is_active", UVM_PASSIVE);
- uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.agent", "is_active", UVM_PASSIVE);//equivalent to using null
- uvm_config_db#(int)::set(null, "*.env.agent", "is_active", UVM_PASSIVE);
- uvm_config_db#(int)::set(null, "uvm_test_top.env*", "is_active", UVM_PASSIVE);
- Database must be type parameterized. this allows config db to be created for any standard or user-defined type; and allows better compile time checking.
- Methods are static
- Methods use a specific contxt argument, whi is usually this; unless the set is called from the top module, in which case it must be assigned to null
- syntax: static function void set(uvm_component cntxt, string inst_name, string field_name, ref T value)
- syntax: static function void get(uvm_component cntxt, string inst_name, string field_name, ref T value)
- get is only required when set is called from the top level module or outside the build phase
- syntax: static function bit exists(uvm_component cntxt, string inst_name, string field_name, bit spell_chk = 0)
- syntax: static task wait_modified(uvm_component cntxt, string inst_name, string field_name)
- inst_name may contain wildcards or regular expression syntax
- for object, interfaces or user-defined types, use uvc_config_db
- for run-time configuration, use uvc_config_db
2) a specialized cfg task is set_config_* for uvm_component class, where * can be int, string or object, depending on type of config property:
- config settings are automatically resolved in UVM build phase; that is because apply_config_settings() is executed in the build phase (when super.build_phase() is called in any uvm_component class). settings are applied only when match is found, if not found, field names will be unset, and mismatched configuration set's should be listed at end of simulation.
- syntax: virtual function void set_config_in (string inst_name, string field, bitstream_t value)
- inst_name is relative pathname to a specific component instance from the component where the method is called
- field is a string containing a config property name of the instance class
- set build options before calling super.build_phase; this is also why the parameters are strings, because the components and fields does not exist yet.
- set_config_int("env.agent", "is_active", UVM_PASSIVE);
- set_config_int("*", "recording_detail", 1);//default is 0, by enabling the recording details for every component, transactions can be viewed in the waveform window(?)
- the creation of the agent instance in the parent build_phase() triggers the execution of the build_phase() of the agent instance.
- config property must be automated in the component where declared (field registered)
- Config settings in higher scope take precedence over lower scopes.
- Config settings in the same scope conform to "last one in wins"
UVM Field Automation
#Non-array
1) `uvm_field_int (<field_name>, <flags>)
2) `uvm_field_object (<field_name>, <flags>)
3) `uvm_field_string (<field_name>, <flags>)
4) `uvm_field_event (<field_name>, <flags>)
#static Array:
1) `uvm_field_sarray_enum (<enum_type>, <field_name>, <flags>)
2) `uvm_field_sarray_int (<field_name>, <flags>)
3) `uvm_field_sarray_object (<field_name>, <flags>)
4) `uvm_field_sarray_string (<field_name>, <flags>)
#dynamic Array:
1) `uvm_field_array_enum (<enum_type>, <field_name>, <flags>)
2) `uvm_field_array_int (<field_name>, <flags>)
3) `uvm_field_array_object (<field_name>, <flags>)
4) `uvm_field_array_string (<field_name>, <flags>)
#dynamic Array:
1) `uvm_field_queue_enum (<enum_type>, <field_name>, <flags>)
2) `uvm_field_queue_int (<field_name>, <flags>)
3) `uvm_field_queue_object (<field_name>, <flags>)
4) `uvm_field_queue_string (<field_name>, <flags>)
#associative Array:
1) `uvm_field_aa_<d_type>_<ix_type>
#flags
UVM_NOVOMPARE
#the do_* functions are worth more discussion later.
uvm_factory
b extends a, c extends a, override(a,c)
will b be affected?
Tips: (1) how to use factory debug features.
uvm_factory::get().print(); //prints the uvm_factory details like registration and override. can be called from build phase, connect phase or mostly likely end_of_elaboration_phase.
UVM Phasing
Tips:(1) phasing debug features (not very useful)
sim option +UVM_PHASE_TRACE
(2) objection debugging
sim option +UVM_OBJECTION_TRACE
UVM_sequence
start_item()
finish_item()
get_response()
driver:
seq_item_port.get_next_item(),
seq_item_port.item_done(),
seq_item_port. put(resp)
UVM_POOL
uvm_object_string_pool #(T)
pool.get(string) #get object by a name
Scoreboard
A scoreboard normally consists of 3 components of functions:
1) reference model/transfer function
c++, systemC or systemverilog: a) existing C model; b) create model, not in collaboration with design team( duplication of errors)
2) Data Storage
model output instant, DUT takes time to output. Need to store data (and synchronization)
Queue: output data in same order as input order
Associative Array: out of order output. Key unique and knonw for input and output. herefore, key is either: a) untransformed port of data, or b) can be generated from data
3) comparison/check logic
Scoreboard internals:
update counter, tracking received, dropped, matched, and mismatched
report_phase, print summary of statistics
end of simulation: check scoreboard queues are empty
Scoreboard must create a new copy of received data item by cloning, before writing the cloned packet to the queue.
UVM TLM Communication bwtween Components
TLM concepts: Port and Imp
Data Flow: producer create data, consumer consume data
Producer ---data---> Cosumer
Control Flow: Initiator sends request to Target
Initiator ---request---> Target
producer is initiator: write operation, also called push/put: e.g. analysis connections
producer is target: read operation, also called pull/get
Port: TLM connection object for Initiator
Imp (implementation): TLM connection object for target.
Export:
symbols: square(port), circle(imp), triangle(export)
port.connect(Imp)
port.connect(Export)
TLM Analysis Interface
uvm_analysis_port #(data type) ap_out
ap_out = new("ap_out", this);
`uvm_analysis_imp_decl(_foo)
uvm_analysis_imp_foo #(data type) foo_in
`uvm_analysis_imp_decl(_bar)
uvm_analysis_imp_foo #(data type) bar_in
function void write_foo(input ---);
endfunction
function void write_bar(input---);
endfunction
...ap_out.connect(...ap_in)
Complex Module UVC connection(external to intermal)
two ways:
1. Module monitor: all external connections are made to this monitor and monitor is responsible for routing connections to other component in the UVC.
the good: Single, central location for connecting external TLM interfaces.
the bad: at the expense of additional internal interface connections to other components.
2. Module connections: external TLM interfaces are placed on UVC itself, then routed using hierarchical connections and not separate TLM interface. use of TLM export object
the good: fewer TLM connections
the bad: losing some readability
Port initiators can be connected to port, export, or imp targets.
Export initiators can be connected to export or imp targets.
Imp cannot be a connection initiator. Imp is a target only, and is always the last connections object on a route.
TLM FIFO
Analysis FIFO
uvm_tlm_analysis_fifo is a specialization of uvm_tlm_fifo:
unbounded (size=0)
analysis_export replaces put export, support analysis write method.
uvm_analysis_port ---> analysis_export---analysis_fifo---get_peek_export <---scoreboard_get_port
uvm_tlm_analysis_fifo #(data type) tb_fifo = new("...", this);
uvm_get_port $(data type) sb_in = new("...", this);
function void connect_phase();
sb_in.connect(tb_fifo.get_peek_export)
endfunction
by the way, analysis fifo's blocking_get_export is just an alias to get_peek_export