Targeted config_db::get in sequence pre_randomize

Hi Academy,

I’m looking for feedback on a pattern I’ve been following with UVM sequences. I’ve found this pattern useful. It’s generally first met with much skepticism, which I think is because it asks the coder for a little more up front. However, people usually come around to liking it. My own biggest concern with this pattern, is it could potentially add enough config_db::get calls to slow performance. However, whether that’s really an issue comes down to how frequently you randomize your sequences. Am I missing any other possible negatives? Let’s dig in.

Targetting a config_db::set to a specific nested sequence, running on a specific sequencer, requires the ::get in the sequence to have context to provide the requisite hierarchy for targeting from your ::set. That hierarchy is only available to the sequence after it’s context is set by it’s parent ( done by seq.start(…) or seq.set_item_context(…) ).

In the sequence, calling config_db::get in new() has the problem that the context has not ( and cannot ) be set yet, so it must occur later. Putting the ::get in the body() has the problem that it occurs after sequence randomization, so it cannot be used to influence randomization. This necessitates that the ::get() happens in the sequence’s pre_randomize(), but not without adding some burden where the sequence is used.

In the test, seq.start() does set the context, but has the problem that it occurs after sequence randomization. This necessitates that an additional step be taken to set the context before randomization, a call to seq.set_item_context(). With that added, the context is available during the sequence’s pre_randomize().

Facilitating this is code in the sequence’s pre_randomize() and some wherever the sequence is randomized.

Example sequence:

class LeafSequence extends uvm_sequence#();
  rand bit [7:0] foo;
  
  function void pre_randomize();
    if ( uvm_config_db#(uvm_bitstream_t)::get( null, get_full_name(), "foo", foo ) )
      foo.rand_mode(0);
    `uvm_info( "TRACE", $sformatf( "%s.%s.rand_mode() == %0d", get_full_name(), "foo", foo.rand_mode() ), UVM_HIGH )
  endfunction
  
  `uvm_object_utils(LeafSequence)

  function new( string name = "LeafSequence" );
    super.new(name);
  endfunction
  
  task body();
    `uvm_info( "TRACE", $sformatf( "%s.%s == %d", get_full_name(), "foo", foo ), UVM_LOW )
  endtask
  
endclass

Example sequence usage:

      LeafSequence lseq = LeafSequence::type_id::create( "lseq", this );
      lseq.set_item_context(null,agt_sqr[s]);  // required for this example to work
      if ( ! lseq.randomize() ) `uvm_fatal( "RANDOMIZE", "Randomize failed" )
      lseq.start( agt_sqr[s] );

Full example available here: targeted config_db::get in sequence pre_randomize - EDA Playground

In reply to warnerrs:

At first glance, this seems extremely cumbersome and not very efficient.

Why would you want to disable randomization on specific variables within the sequence? If this is the case, then wouldn’t the parent sequence have to assign a specific value to those variables after the call to randomization?

It seems to me that it would be more efficient for the parent sequence to call randomize with the associated variable constrained appropriately:


if (!lseq.randomize() with {foo == required_value;})
  `uvm_error("RND_ERROR", "Randomization error");

Also, you should never wrap a randomization call with an assert statement. You should check the return value as above.

In reply to cgales:

In reply to warnerrs:
At first glance, this seems extremely cumbersome and not very efficient.

It seems I’m missing the compelling problem statement. Imagine you run your test and see:

UVM_INFO LeafSequence.sv(15) @ 0: uvm_test_top.agt_sqr[0]@@lseq [TRACE] uvm_test_top.agt_sqr[0].lseq.foo ==  15
UVM_INFO NeedleSequence.sv(14) @ 0: uvm_test_top.agt_sqr[0]@@nseq [TRACE] uvm_test_top.agt_sqr[0].nseq.foo ==  40
UVM_INFO LeafSequence.sv(15) @ 0: uvm_test_top.agt_sqr[1]@@lseq [TRACE] uvm_test_top.agt_sqr[1].lseq.foo == 215
UVM_INFO NeedleSequence.sv(14) @ 0: uvm_test_top.agt_sqr[1]@@nseq [TRACE] uvm_test_top.agt_sqr[1].nseq.foo == 127
UVM_INFO LeafSequence.sv(15) @ 0: uvm_test_top.agt_sqr[2]@@lseq [TRACE] uvm_test_top.agt_sqr[2].lseq.foo == 192
UVM_INFO NeedleSequence.sv(14) @ 0: uvm_test_top.agt_sqr[2]@@nseq [TRACE] uvm_test_top.agt_sqr[2].nseq.foo == 117
UVM_INFO LeafSequence.sv(15) @ 0: uvm_test_top.agt_sqr[3]@@lseq [TRACE] uvm_test_top.agt_sqr[3].lseq.foo ==  17
UVM_INFO NeedleSequence.sv(14) @ 0: uvm_test_top.agt_sqr[3]@@nseq [TRACE] uvm_test_top.agt_sqr[3].nseq.foo == 160

So far this is good.

Why would you want to disable randomization on specific variables within the sequence?

It turns out I have a coverage hole that I could close, if only I could make the nseq sequence, running on agent 2 run with foo==10?

If this is the case, then wouldn’t the parent sequence have to assign a specific value to those variables after the call to randomization? It seems to me that it would be more efficient for the parent sequence to call randomize with the associated variable constrained appropriately:


if (!lseq.randomize() with {foo == required_value;})
`uvm_error("RND_ERROR", "Randomization error");

When writing tests, adding constraints in the parent sequence or test remains the preferred method. However, in order to avoid producing tons of test classes that are nearly identical, this is the situation where plusargs usually get added to the environment. Many tests can then be easily created that differ only by fixing one or two variables.

In this case, simply adding a plusarg into the sequence is insufficient, as it lacks any way to limit the scope of impact to the desired sequence. This pattern allows me to rerun my test with a config_db plusarg:

UVM_INFO @ 0: reporter [UVM_CMDLINE_PROC] Applying config setting from the command line: **+uvm_set_config_int=*.agt_sqr[2].lseq,foo,10**
UVM_INFO LeafSequence.sv(18) @ 0: uvm_test_top.agt_sqr[0]@@lseq [TRACE] uvm_test_top.agt_sqr[0].lseq.foo == 121
UVM_INFO NeedleSequence.sv(17) @ 0: uvm_test_top.agt_sqr[0]@@nseq [TRACE] uvm_test_top.agt_sqr[0].nseq.foo == 228
UVM_INFO LeafSequence.sv(18) @ 0: uvm_test_top.agt_sqr[1]@@lseq [TRACE] uvm_test_top.agt_sqr[1].lseq.foo == 226
UVM_INFO NeedleSequence.sv(17) @ 0: uvm_test_top.agt_sqr[1]@@nseq [TRACE] uvm_test_top.agt_sqr[1].nseq.foo == 133
UVM_INFO LeafSequence.sv(18) @ 0: uvm_test_top.agt_sqr[2]@@lseq [TRACE] **uvm_test_top.agt_sqr[2].lseq.foo ==  10**
UVM_INFO NeedleSequence.sv(17) @ 0: uvm_test_top.agt_sqr[2]@@nseq [TRACE] uvm_test_top.agt_sqr[2].nseq.foo == 236
UVM_INFO LeafSequence.sv(18) @ 0: uvm_test_top.agt_sqr[3]@@lseq [TRACE] uvm_test_top.agt_sqr[3].lseq.foo == 143
UVM_INFO NeedleSequence.sv(17) @ 0: uvm_test_top.agt_sqr[3]@@nseq [TRACE] uvm_test_top.agt_sqr[3].nseq.foo == 162

Without this pattern, what I was seeing was tests were adding their own config_db or plusarg controls to support this capability. Which works, but it’s only happening because the user is trying to overcome a lack being able to target a specific sequence instance with the config_db.

The next result is different tests start copying/pasting this test level code, or reimplement similar functionality slightly differently. Now you have maintenance and usability issues. So phase 2 is an effort to unify this test level code in a new class to collect these knob controls. Phase 3 is this new shared knobs class becomes a dumping ground for a whole bunch of unrelated controls, and your back to having usability and maintenance issues. All that can be avoided by making it possible to target specific sequences with the config_db.

A side effect is you can also ::set these things from your tests. The value of that has been negligible, but it exists. Writing old fashion constraints is still preferred.

Also, you should never wrap a randomization call with an assert statement. You should check the return value as above.

Yep. I was lazy when I wrote my example. I fixed it.

In reply to warnerrs:

I can see what your trying to accomplish, but I still feel that it adds some unnecessary complexity.

Since this always targets a specific sequence, would creating an extended sequence with an additional constraint and using an override work better?

Or perhaps target the command line configuration to the parent sequence to minimize the number of config_db calls?

I think there are a variety of ways to accomplish your goal, and I don’t think that your method is any better or worse, but be aware of the potential performance issues.

One additional option is to look at portable stimulus generators which are designed to provide 100% coverage.

In reply to cgales:

In reply to warnerrs:
Since this always targets a specific sequence, would creating an extended sequence with an additional constraint and using an override work better?

This seems like a good compromise. Skip the work upfront, and insert it later using the factory if needed.

Thanks for your input.