c4brian and I have messaged a bunch about ways to share data and access between elements in SV/UVM code.
I put common functions/tasks/constants into a package, which I think is standard.
I realize that absolute dot notation of accessing elements (i.e. variable12 = smthgA.smthgB.smthgC.variableXYZ;) is frowned upon as it is not good for reuse.
So, the UVM presents the uvm_config_db to facilitate reuse and simplify connectivity across a hierarchy.**1
Many folks have (including c4brian, I believe) commented that they don't like how verbose it is to ::get from the config db and to check the return result.
if (! uvm_config_db#(integer)::get(this,"hiccup","xyz_important_var", important_var) ) begin
`uvm_fatal("ERROR,"important_var not found in config_db")
end
In a recent testbench, I've done the following. I probably need to review how the config component is supposed to be used.
I am curious what others think.
My setup:
1) Create an object to store common variables and handles.
//This should be a singleton and serve as a central point for others to access global connections. Likely not a good style. Discuss.
class xyz_config extends uvm_component;
`uvm_component_utils(xyz_config)
static xyz_env m_env;
static id_state m_id_state;
static delay_ctl m_delayctl;
function new(string name="xyz_config", uvm_component parent=null);
super.new(name, parent);
endfunction
set_env_handle(input xyz_env handle);
m_env=handle;
endfunction : set_env_handle
...
2) When something is created that other code might want to access, I set a handle to it in xyz_config.
ex1: In the test class constructor (which is declared inside top), create the env and set a handle to it in the xyz_config. (Probably these actions should be done in the build_phase rather than the constructor.)
class test_base extends uvm_test;
`uvm_component_utils(test_base)
xyz_env env;
function new(string name = "test_base", uvm_component parent = null);
super.new(name, parent);
env = xyz_env::type_id::create("env",this);
rrc_config::set_env_handle(env);
ex2: In the env build_phase, I set handles to some objects which track data which is useful in many sequences and other code
function void xyz_env::build_phase(uvm_phase phase);
super.build_phase(phase);
...
// Create Helper Structures
m_id_state = id_state::type_id::create("m_id_state",this);
xyz_config::set_id_state_handle(m_id_state);
m_delayctl = delay_ctl::type_id::create("m_delayctl",this);
xyz_config::set_delayctl_handle(m_delayctl);
3) Now, in various parts of the tb (such as from sequences), I can access xyz_config as a singleton, and access its handles (using . "dot" notation) to whatever data structures it was given access to. (I'm thinking now that those data structures should be in the scoreboard.)
The dot notation is much more concise than declaring a variable and then performing a uvm_config_db ::get and checking the return value.
//in seq, id_state which tracks system-state used to set transaction variable
jhg_input_trans.state = xyz_config::m_id_state.get_jhg_state(.loopback(loopback), .fce_1(fce_1));
or
//in virtual seq, a call is made that turns off any internal stallers (special stallers to alter congestion in the dut)
xyz_config::m_env.turn_off_internal_stallers();
or
//in scoreboard, as monitors send in transactions, it adjusts state info which is used by sequences to make legal input
xyz_config::m_id_state.move_some_id(.note("fuf->xyz"), .syd(t.fuf_xyz_read_syd), .from_q(Q001), .to_q(Q111));
A benefit of this is that the user can more easily (from this line of code), see what is being accessed, rather than needing to rerun a test and dump config_db or grep thru a bunch of files to see who did the ::set.
With regards to reuse, it seems to me that as long as the new tb (that wants to benefit from reuse), sets the handles in the _config properly, it is just as reuse-able.
Probably, I am missing something.
I've have a vague notion and have heard soft feedback that this style (which I feel is unique to me) is not good. Maybe I'm imagining this or exaggerating it in my mind. I bring it up here, in the event anyone has feedback or a good scolding to give.
**1
Conceptually I've been taught to think of uvm_config_db as a "string"-accessible associative array, or a group of associative arrays; something like 'one for each datatype'.
I'm not poking into the uvm base class here, but just voicing my understanding out-loud for comments. Conceptually, I think of uvm_config_db as operating as follows.
A user specifies the data type that they want to store or get - which 'conceptually' selects which associative array to access.
A user specifies a "string-name" which is the key into that associate array.
A user reads or writes that associative array as follows.
To write, they use ::set, specify a "string-name", and provide a value.
To read, they use ::get, specify a "string-name", and provide a variable, into which the value stored at "string-name" will be copied.
(Note: I've modified the code examples to shorten them, so may have introduced typos.)