Upward references in bound interfaces

To avoid a somewhat ugly use of e.g. uvm_hdl_force that is currently in the codebase, I tried being “clever” and using a hierarchical upwards reference from a bound interface.

In the application, the design contains interfaces of some module, M. Let’s suppose it looks like this:

module M (input clk_i);
  logic my_state;
endmodule

I can define an interface like the following to be bound into the module. The idea is that M.my_state walks up the hierarchy to the first instance of M.

interface my_if (input clk_i);
  function flip();
    M.my_state = ~M.my_state;
  endfunction
endinterface

I can then bind in the interface with something like this:

bind M my_if u_my_if(.*);

And everything seems to work: I get an instance of the interface in each of the instances of the module and can interact with each happily! Yippee!

In the actual application, M is the name of some primitive block that is used in some, but not all, of the duts in my testbenches. When I tried the trick above with a dut/testbench that doesn’t happen to use M, the elaboration stage fails. With Xcelium, I get errors about the broken hierarchical up-reference. My suspicion is that this is caused by trying to make sense of an implicit instance of my_if at the top of the hierarchy (where the up-reference obviously won’t work).

It occurred to me to try a hack with parameters:

interface my_if #(bit bound = 0) (input clk_i);
  if (bound) begin
    function flip();
      M.my_state = ~M.my_state;
    endfunction
  end
endinterface

and then binding with

bind M my_if #(.bound(1)) u_my_if(.*);

My idea was that the conditional generate construct that wasn’t enabled wouldn’t be elaborated.

Unfortunately, I still get the same elaboration error (*E,CUVUNF): apparently, I’m not “turning elaboration off” as much as I’d hoped.

This leaves me with two questions:

  1. How could I implement something like this nicely, where an interface gets bound into every occurrence (zero or more) of some module and then interacts with that module’s internals?
  2. Why didn’t my hacks above work? :slight_smile:

Are you actually using unnamed generate block in the hack of your real code? According to 1800-2023 LRM, Section 23.6, subroutines defined inside unnamed generate blocks cannot be called from the outside, which would make your xxx.u_my_if.genblk1.flip function uncallable.

Thank you for the reply. I’ve just checked again, and this behaves the same with a named generate block.

In the actual example I’m working with, I’ve got code like this:

interface prim_count_if #(bit bound=0) (input clk_i, input rst_ni);
  //...
  if (bound) begin : gen_bound
    function void restore_signal();
      // ...
    endfunction
  end
endinterface

This gets used through a “proxy class”, which looks like this:

class prim_count_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
  typedef virtual prim_count_if #(.bound(1)) vif_t;
  local vif_t m_vif;

  //...
  extern task restore_fault();
endclass

task prim_count_if_proxy::restore_fault();
  //...
  m_vif.gen_bound.restore_signal();
endtask

Unfortunately, this doesn’t work.

With Xcelium, I get E,CUVUNF. With VCS, I get the XMRE error:

Error-[XMRE] Cross-module reference resolution error
src/lowrisc_dv_sec_cm_0/prim_count_if.sv, 111
  Error found while trying to resolve cross-module reference.
  token 'prim_count'.  Originating interface 'prim_count_if'.
  Source info: if_proxy.set_vif(prim_count.u_prim_count_if, _bound_path);
  Instance stack trace:
      prim_count_if#(1'b1)
      $dummyOvSvModule

I assume the #dummyOvSvModule line is evidence for my guess about a top-level use.

You’ve encountered a tool-specific issue. Interfaces are never implicitly instantiated at the top level. Your code works in other tools. Refer to section 25.3 Interface syntax for more information. Also, you cannot have a bind statement without having a module M somewhere in your design, as tools won’t understand what M refers to.

Ah, that’s interesting! It sounds like I’m trying to do something dubious (which Synopsys and Cadence both dislike, but in different, inscrutable ways!)

I had (possibly wrongly) imagined that the module M would be a top-level module (following 23.3.1 in IEEE 1800-2017) and then that the bind directive would have bind_target_scope matched as a module_identifier (looking at Syntax 23-5 further up on the same page).

If that isn’t the right way to do this, what would you suggest as a way to do it? The use-case is for a security property. I’d like to be able to:

  • For each instance of M in the design (if there are any), register that instance in some list.
  • Then (in some fault injection test), work through the instances and force a signal to simulate the fault injection.

The code in OpenTitan is reasonably clever (and predates me: I’m not taking credit!) and does this by instantiating a different subclass of a “fault injector” for each of the different target modules. That subclass acts as a converter that gives a uniform way for the testbench to interact with the target module.

The current code isn’t quite right though (I don’t think), because it defines the subclass inside the bound-in interface. This is a bit mysterious if there happen to be two copies of the interface: you get two classes with the same name!

While decoupling the two, I’m hoping to use an up-reference instead of doing the rather hacky trick of accessing things by path with e.g. uvm_hdl_force.

Following up on this a bit further, it occurred to me that you can avoid the upward reference if you use an inout or output port on the bound-in interface. I think that things would probably “just work” if the signal I wanted to force was a net: I could bind in the interface with an inout port and then the force would take precedence over other drivers of the net.

Of course, the signal in question is actually a variable, so can’t be connected to an inout port. Rather stupidly, I tried doing the same idea with an output port. With Xcelium, it actually works! But VCS doesn’t like this: at elaboration time, it notices that I have two things that are trying to drive the same variable. In hindsight: my code was definitely wrong! Not the way to do it…

At this point, I’m feeling rather dejected! Maybe calling uvm_hdl_force is the only solution. I guess that this moves the problem to a runtime check, which might be more guaranteed to work (because customers of the EDA tools have more definite ideas about what is supposed to happen).

But the result feels like I’m writing in some untyped web language! Not quite what I was hoping for :slight_smile: If there’s a more structured way to do this, I’d love to know.