Wednesday, April 23, 2014

UVM Drain Time - The Old Fashioned Way

One of the most useful additions of OVM 2.1 was the objection mechanism. You could raise an objection when starting your main traffic sequence and drop it once it was finished, thus stopping the simulation. Being done with input traffic didn't mean that nothing else would happen from that point as the DUT may have required some additional time to drain any transactions that were still being processed. Setting a drain time added an extra delay from the time that all objections were dropped to the stop of the simulation, making sure that there were no outstanding transactions at that point.

The drain time was usually set in the base test, before the run phase was started. I liked to do it in the end_of_elaboration phase, but any other phase would have worked as well:

class test_base extends ovm_test;
  // ...
  
  function void end_of_elaboration();
    ovm_test_done.set_drain_time(this, 100ns);
  endfunction
  
  // ...
endclass

When UVM was released, it brought another cool addition: multiple run-time phases like reset, main, shutdown, etc. While I must admit I have never really used them myself, they do look very useful for someone doing SoC verification. Sadly, along with them my old friend, the test_done objection, was deprecated in favor of phase specific objections.

The way to do it now is to set the drain time inside the phase task by using the phase argument:

class test_base extends uvm_test;
  // ...
  
  task run_phase(uvm_phase phase);
    phase.phase_done.set_drain_time(this, 5);
  endfunction
  
  // ...
endclass

I don't like this, because I can't just set the drain time in the base test anymore and forget about it. Calling super.run_phase(phase) in subclasses is out of the question for obvious reasons. One could create a dummy method and always call that first; at least this way the value of the drain time would be centralized in one location. There are however problems with this approach. We always have to call this method which means it's very easy to forget to do it. Also, if we want different drain times for each phase we would need multiple such methods. I guess it's back to the drawing board.

While looking around through the UVM base class library (BCL) I noticed that each phase has a singleton associated with it. We can get handles to these singletons in the end_of_elaboration phase and set the drain time there:

class test_base extends uvm_test;
  // ...
  
  function void end_of_elaboration_phase(uvm_phase phase);
    uvm_phase run_phase = uvm_run_phase::get();
    run_phase.phase_done.set_drain_time(this, 5);
  endfunction
  
  // ...
endclass

It seems that we're home free, but we're not done just yet. If we try to do the same for the main phase, after setting the drain time it just doesn't' work. After some more experimenting and asking around I found out that the objects we get returned via uvm_*_phase::get() are different that what is passed to the uvm_*_phase(...) methods as the phase argument. Here's a link to the thread in the UVM forums with the explanation - many thanks to kirloy for clearing it up. Applying what we learned gives us this:

class test_base extends uvm_test;
  // ...
  
  function void end_of_elaboration_phase(uvm_phase phase);
    uvm_phase main_phase = phase.find_by_name("main", 0);
    main_phase.phase_done.set_drain_time(this, 10);
  endfunction
  
  // ...
endclass

Now we're really done. As usual, the code can be found on the blog's repository. Thanks for reading and stay tuned for more!

Sunday, April 13, 2014

Custom Field Access Policies in UVM RAL

As promised in my last post today we're going to look at how to define a custom field access policy in the UVM register package.

Let's use the same access policy from last time: "writing to the field is allowed, except when trying to write the value 0; reading from the field is always allowed".

We'll start out by defining our register field class. The uvm_reg_field class provides a set of hook methods for developers to use when extending it. You might be tempted to think that we could use the pre_write() method in our case, but this will only work if we do implicit prediction (i.e. using calls to read() or write() inside our sequences). If we want to make it portable for cases when have traffic to our registers coming from other sources (such as legacy VIP or other DUT blocks when doing vertical reuse) we will want it to work for explicit prediction as well. To achieve this we have to extend the predict() method.

The uvm_reg_field class doesn't provide any pre-/post- hooks for the predict() method, but we can use callbacks for that. First we define our own callback class:

class vgm_wri0_cbs extends uvm_reg_cbs;
  `uvm_object_utils(vgm_wri0_cbs)
  
  function new(string name = "vgm_wri0_cbs");
    super.new(name);
  endfunction
  
  virtual function void post_predict(input uvm_reg_field  fld,
                                     input uvm_reg_data_t previous,
                                     inout uvm_reg_data_t value,
                                     input uvm_predict_e  kind,
                                     input uvm_path_e     path,
                                     input uvm_reg_map    map);
    if (kind == UVM_PREDICT_WRITE && fld.get_access() == "RWI0" && value == 0)
      value = previous;
  endfunction
endclass

We also have to declare our own register field class that defines the new RWI0 policy:

class vgm_rwi0_reg_field extends uvm_reg_field;
  `uvm_object_utils(vgm_rwi0_reg_field)
  
  local static bit m_wri0 = define_access("RWI0");
  
  function new(string name = "vgm_rwi0_reg_field");
    super.new(name);
  endfunction  
endclass

When declaring new access policies, it's best to use all uppercase characters as define_access(...) calls toupper() on the string we give it as an input argument and adds that to the table of access policies. What I would have liked to do at this point is create a callback object inside the constructor and add it to the field. This was unfortunately not possible as adding a callback does a call to get_full_name() which in turn requires a references to the field's parent register be set up. We have to postpone this step for higher up in the instance tree.

The last thing we need to do is declare the register:

class example_reg_type extends uvm_reg;
  `uvm_object_utils(example_reg_type)
  
  rand uvm_reg_field      field1;
  rand vgm_rwi0_reg_field field2;
  
  function new(string name = "example_reg_type");
    super.new(name, 32, UVM_NO_COVERAGE);
  endfunction
  
  virtual function void build();
    field1 = uvm_reg_field::type_id::create("field1");
    field2 = vgm_rwi0_reg_field::type_id::create("field2");
    
    field1.configure(this,  16, 16, "RW",   0, 0, 1, 1, 0);
    field2.configure(this,  16,  0, "RWI0", 0, 0, 1, 1, 0);
    
    // register callback
    // - can't be done in vgm_rwi0_reg_field because it calls get_full_name()
    //   which requires the field's parent
    begin
      vgm_wri0_cbs wri0_cbs = new("wri0_cbs");
      uvm_reg_field_cb::add(field2, wri0_cbs);
    end
  endfunction
endclass

We've defined field2 as being of type vgm_rwi0_reg_field and as having the RWI0 access policy. We've also added a callback to this field to actually implement the access policy.

This is the only way UVM RAL allows us to define custom field access policies and I can't say I'm particularly pleased about it. Using callbacks is slow and it is bloated. We've had to split our code into three sections: the callback class, the register field class and the register class). Had we have had a post_predict() hook in uvm_reg_field we could have just implemented everything inside our register field class, resulting in less lines of code and better encapsulation. This is definitely something I hope they will improve in a future release.

As usual, the code above including everything else needed to test it can be found on the blog's Sourceforge page.

You can also subscribe here if you don't want to miss any of my future posts.