verilator / verilator

Verilator open-source SystemVerilog simulator and lint system

Home Page:https://verilator.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Submodule port var not updated when module is not inlined

b-chmiel opened this issue · comments

The problem

After disabling module inlining for the submodule, val value is not updated properly.

module t;
   logic val = 0;
   sub dut(.val(val));

   initial begin
      val = 1;
      assert(dut.val == 1); // This fails when 'sub' is not inlined.

      $write("*-* All Finished *-*\n");
      $finish;
   end
endmodule

module sub(output logic val);
   /* verilator no_inline_module */
   // Pragma needed for the test failure.
endmodule

For the inlined version, assignments are aliased to the ports during V3Inline using AstAssignAlias and later optimized out to use local variable t__DOT__val. This is done both in the assignment before assertion and in the assertion condition.

Generated code:

    t__DOT__val = 1U;
    if (vlSymsp->_vm_contextp__->assertOn()) {
        if (VL_UNLIKELY((1U & (~ (IData)(t__DOT__val))))) {
          ... assertion code ...
        }
     }
     ... the rest of the initial block code ...

However, the non-inlined variant only correctly resolves the assignment t__DOT__val = 1U; but does not resolve reference to that AstVar in the assert condition:

    t__DOT__val = 1U;
    if (vlSymsp->_vm_contextp__->assertOn()) {
        if (VL_UNLIKELY((1U & (~ (IData)(vlSymsp->TOP__t__DOT__dut.val))))) {
          ... assertion code ...
        }
     }
     ... the rest of the initial block code ...

Non-inlined alias is generated but in stl_sequent phase, which runs too late for the earlier assignments to succeed.

VL_ATTR_COLD void Vt_assign_bug___024root___stl_sequent__TOP__0(Vt_assign_bug___024root* vlSelf) {
   ...  function init code ...

    // Init
    CData/*0:0*/ t__DOT__val;
    t__DOT__val = 0;
    // Body
    t__DOT__val = vlSymsp->TOP__t__DOT__dut.val;
}

Maybe t__DOT__val should be aliased with the port var and watched for its changes (only when inline is disabled), or t__DOT__val should be artificially updated each time it is modified (probably using AstAssignW).

Verilator command

./bin/verilator -cc --exe --main --build --assert t_assign_bug.v

Verilator version

Master at d4b3583.

Thanks for the good writeup.

By IEEE scheduling rules this runs in a single process

      val = 1;
      assert(dut.val == 1); // This fails when 'sub' is not inlined.

A simulator is free to run other processes in between, e.g. calculate "dut.val" or not calculate it, and Verilator makes different choices for performance based on inlining or not. Therefore it is legal for the assertion to either pass or fail. To be legal it needs to be e.g. this:

      val = 1;
      #1;
      assert(dut.val == 1); // This fails when 'sub' is not inlined.

Specifically see section "4.8 Race conditions" of the standard (version 2017/2023)