Search code examples
system-verilogverificationuvm

UVM enforce clocking block usage


I want to use the following SystemVerilog concepts:

  • clocking block: to avoid race conditions between driver and monitor, i want to have it in a central place, in the interface
  • modport: normally (e.g. passing a modport instead of the interface to a module) I could use it to ensure that the interface signals could only be accessed through the clocking block

Now in UVM we instantiate a virtual interface and make it available through the config database, so I don't see how it would be possible for the driver to only get a reference to the modport instead of the virtual interface. What is the standard solution to avoid accidentally accessing a signal without the modport?

I could only come up with the following (untested) code sample, however I'm not sure whether there is a better way:

interface my_interface(input clk);
  logic I;
  logic O;
  
  clocking cb_drv @(posedge clk);
    output I;
    input O;
  endclocking
  
  clocking cb_mon @(posedge clk);
    input I;
    output O;
  endclocking
  
  modport drv(clocking cb_drv, output clk);
  modport mon(clocking cb_mon, input clk);
endinterface


class my_driver extends uvm_driver #(my_transaction);
  `uvm_component_utils(my_driver);
  
  virtual my_interface.drv vif;
  
  ...
  
  virtual function void build_phase(uvm_phase phase);
    virtual my_interface temp;
    super.build_phase(phase);
            
    if(!uvm_config_db#(virtual my_interface)::get(this, "", "my_interface", temp))
      `uvm_fatal(...);
    
    vif = temp.drv;
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(transaction);
      @(vif.cb_drv);
      vif.cb_drv.I = transaction.I;
      seq_item_port.item_done();      
    end
  endtask
  
endclass

The issue with this solution is, that I have functions/tasks, that are implemented in the interface (e.g. read/write functions), which become unaccessible this way.


Solution

  • Clocking blocks are just one of many ways to prevent race conditions between your test bench and DUT. You can use nonblocking assignments in the same way that designers use them to prevent races between their RTL code. You can use the negative or offset clock edge to drive and monitor your DUT. See my DVCon Paper: The Missing Link: The Testbench to DUT Connection.

    Regarding the use of tasks and functions in an interface modport, you can certainly make them accessible using an import. But most people have given up using modports for verification because even in the case you show, it's very easy to access the entire interface. You would have to separate the config db for each modport .

    uvm_config_db#(virtual my_interface.drv)::set(this, "*", "my_interface", itf_instance.drv)
    uvm_config_db#(virtual my_interface.mon)::set(this, "*", "my_interface", itf_instance.mon)
    

    The the driver and monitor would need to get their respective virtual interface instances from the uvm_config_db.