Logic vs wire in black box port list

Hi,

I’m witnessing an unexpected behavior in my testbench: asymmetric behavior of logic and wire while both defined as outputs in the port list of a black box module.
In the code below I built a toy example to demonstrate the issue.
The use case that the example tries to mimic is a full chip environment in which some of the rtl is replaced by black box module that should be driven by an agent in the verification environment.

I thought that the code below is trivial and I expected that both output (‘a’,‘b’), will be driven by my driver. But this is not the case, while ‘b’ is properly initialized, ‘a’ stays as ‘x’ for all the simulation:

  • tb.dut_i.a is const ‘x’ during all the simulation
  • tb.dut_i.b starts as ‘z’ and goes to ‘0’ at the first posedge of clk.

The only difference between ‘a’ and ‘b’ is that in the black box dut ‘a’ is defined as logic and ‘b’ is left as default (hence defined as wire?).
I assumed that an undriven logic output will be modeled as a net and with this assumption it will be symmetric to a wire.

My questions are:

  • How come that tb.dut_i.a is not initialized by the driver when do_reset is called?
  • What can I change in my agent or interface to initialized both signals types with the same code?

// interface with clocking block
interface test_if  (
      input logic clk,
      input logic reset_n,
            logic a,
            logic b);

    clocking master_cb @(posedge clk);
        default input #5ps output #250ps;
        output  a;
        output  b;
    endclocking
endinterface

// black box dut
module dut (input  logic clk,
            input  logic reset_n,
            output logic a, // a is logic
            output       b); // b is wire
endmodule

// driver for verification
class driver;
    virtual test_if vif;
    function new(virtual test_if a_vif);
        vif = a_vif;
    endfunction

    task run();
        forever @(vif.master_cb, negedge vif.reset_n) begin
            if (!vif.reset_n) begin
                do_reset();
            end else begin
                $display("not in reset");
            end
        end
    endtask

    task do_reset();
        vif.master_cb.a <= '0; // fails in simulaiton
        vif.master_cb.b <= '0; // succeeded in simulation
    endtask
endclass

// program to mimics the test
program test(test_if tif);
    driver test_driver;
    initial begin
        test_driver = new(tif);
        fork
            test_driver.run();
        join_none
        #1000;
    end
endprogram
// top level TB
module tb();
    logic clk;
    logic reset_n;
  dut i_dut(.clk(clk),.reset_n(reset_n));
  test_if tif(.clk(i_dut.clk),
              .reset_n(i_dut.reset_n),
              .a(i_dut.a),
              .b(i_dut.b));
  test i_test(tif);
  // clock
  initial begin
      forever begin
          #5;
        clk = ~clk;
    end
  end
  // reset
  initial begin
      clk = '0;
      reset_n = '0;
      #300;
      reset_n = '1;
  end
endmodule

In reply to Amirros:

It’s slight more complicated than just logic versus wire. It is really net versus variable. The value of a net is the resolution function of all the drivers (continuous assignments) connected to the net. A wire is a kind of net that resolves to a Z when there are no active drivers on the wire. A variable is a storage element whose value is procedurally assigned. Prior to the first procedural assignment, the default value of a variable depends on the variable’s type. logic is a 4-state data type and its default initial value is X.

SystemVerilog has a lot of (too many IMHO) implicit declaration conventions. Here is what your black-box dut declaration looks like explicitly :

module dut (input  wire logic clk,
            input  wire logic reset_n,
            output var logic   a, // a is variable
            output wire logic  b); // b is net
endmodule

You might want to look at sections 23.2.2.3 Rules for determining port kind, data type, and direction and 23.3.3 Port connection rules in the IEEE 1800-2017 SystemVerilog LRM.

In reply to dave_59:

Hi Dave,
Thanks a lot. I actually read this section of the LRM today and figured that my previous assumption of the type of ‘a’ was wrong.
I still fail to understand how come that the ‘x’ on ‘a’ sticks for all the simulation time even tough that reset is de asserted and do_reset is called (‘b’ is initialized properly).

In reply to Amirros:

The problem is both i_dut.a and i_dut.b are wires driven by the dut module instance output ports a and b. Port a is being driven a continuous assignment from the variable ‘a’ which has the value 1’bx. That overrides any value assigned through the clocking block. You can fix that by assigning ‘a’ to 1’bz, or changing it back to a wire

In reply to dave_59:

Thanks Dave,
That’s indeed clarify things. Here again I had a miss-assumption that I’m pointing to the port itself and not to the wire that driven by it. Following that I wonder, what is the xmr of the port? Can I change the way I connect the interface in a way that will override the variable assignment?
My motivation is that in many cases such black box dut is provided as part of a release that I can’t easily change.
Again thank a lot

In reply to Amirros:

If the intent of the black box is instantiating a completely passive DUT, then in my opinion, the module is incorrectly written. You should work with the people providing the release.

Barring that, you could

  • leave the port unconnected to the interface cross-reference.
  • write a wrapper around the block box that leaves the output unconnected.
  • use force statements or supply strength drivers to override the output

Without knowing more about the full details of your testbench architecture, it is hard to make a good recommendation.