verilator icon indicating copy to clipboard operation
verilator copied to clipboard

Fix virtual interface "combinational region did not converge" error

Open RootCubed opened this issue 1 year ago • 19 comments

Under some conditions I'm not quite sure about (but most likely involving interfaces and a class that drives the interface), a runtime error is erroneously triggered.

To reproduce, compile the following code with verilator --exe --build -j `nproc` --binary bug_ico.sv -o bug_ico (using the git master version):

interface INTF ();
    logic field1;
    logic field2;
endinterface

class intf_driver_class_unused;
    virtual INTF intf;

    function new(virtual INTF intf);
        this.intf = intf;
    endfunction

    function void unused_func1();
        intf.field1 = '0;
    endfunction

    function void unused_func2();
        intf.field2 = '0;
    endfunction
endclass

module idma_backend_top ();
    typedef struct packed {     
        logic field1;
        logic x;                                            
    } s1;

    typedef struct packed {                               
        logic field2;
        logic y;
    } s2;

    // Bug happens with and without initializations
    s1 struct1 = '{1'b0, 1'b0};
    s2 struct2 = '{1'b0, 1'b0};

    assign intf.field1    = struct1.field1;
    assign struct2.field2 = intf.field2;
    assign struct1.x      = struct2.y;

    INTF intf ();

    // Doesn't change the outcome
    // initial begin
    //     intf.field1 = '0;
    //     intf.field2 = '0;
    // end
endmodule

(Note that the class is not even used - in the original project where this occurred it did, but while compressing it (~5k lines post-preprocessing :-( ) down to this reproducible example I found out that the class didn't even need instantiating)

The following error will show: %Error: bug_ico.sv:22: Input combinational region did not converge.

Adding -CFLAGS -DVL_DEBUG=1 additionally prints out this line before the error message: -V{t1,1} 'ico' region trigger index 1 is active: Internal 'ico' trigger - virtual interface: INTF

I would appreciate any insight into what exactly is going wrong here, happy to look into it as well at some point too though. I have a feeling it has something to do with some update trigger maybe being globally on the interface rather than the individual wires of the interface, or something like that. Though I have no idea why it needs that extra driver class.

RootCubed avatar May 11 '24 20:05 RootCubed

Verilator considers all wires together in scheduling, even when they consists of structs/unions. Normally module/interface signals would not be likewise scheduled together, but virtual interfaces can do nearly anything so signals in a virt interface likewise schedules together.

So basically you have to verilator:

assign intf    = struct1;
assign struct2 = intf;
assign struct1 = struct2;

Using this works:

s1 struct1 /*verilator split_var*/ = '{1'b0, 1'b0};
s2 struct2 /*verilator split_var*/ = '{1'b0, 1'b0};

wsnyder avatar May 11 '24 21:05 wsnyder

Alright, thanks for the info. Unfortunately, in the original project I'm still getting the error, even after spraying /*verilator split_var*/ pretty much anywhere I could (both inside the modules and in their port lists)... Any suggestions on how to debug this more efficiently?

RootCubed avatar May 12 '24 11:05 RootCubed

You could look at the .dot files that come with --debug but those are complicated. Maybe see if Version 4 of Verilator, if it works at all, gives UNOPTFLAT indication that helps you out?

wsnyder avatar May 12 '24 20:05 wsnyder

OK, so what I did now is I now forced m_attrSplitVar to be true by default in V3AstNodeOther.h:1837 to ensure I didn't miss anything, but the ico error still comes up. FYI, I also got an internal error on one of the assign statements (which I circumvented for testing the ico error by just assigning a constant instead), which might warrant a separate issue once I get around to it:

%Error: Internal Error: ../../rtl/idma_transport_layer_rw_axi.sv:207:40: ../V3SplitVar.cpp:1124: not enough split variables
                                                                       : ... note: In instance 'idma_backend_top.i_idma_backend.i_idma_transport_layer'
  207 |     assign buffer_out_shifted       = {buffer_out, buffer_out}             >>  (w_dp_req_i.shift*8);
      |                                        ^~~~~~~~~~

I'll try to create another minimal example from here where adding the split_var metacomment does not help and report back again then.

RootCubed avatar May 13 '24 07:05 RootCubed

interface INTF();
    logic x;
    logic y;
endinterface

class intf_driver;
    virtual INTF intf;
    function new(virtual INTF intf);
        this.intf = intf;

        // Not really relevant what exactly is done with the fields,
        // but they just have to be referenced for this bug to appear
        while (intf.x != 1) begin end
        intf.y = 1;
    endfunction
endclass

module top();
    logic s1 = '1;
    logic s2 = '1;

    INTF vintf ();

    assign vintf.x = s1;

    assign vintf.y = '1;
    assign s2 = vintf.y;

    assign s1 = s2;

    initial begin
        $finish;
    end
endmodule

Note that in this example there aren't any structs involved, so I believe there is currently no workaround for this issue at the moment.

Also note that this is definitely related to the virtual interface stuff, as doing

module top();
    logic s1 = '1;
    logic s2 = '1;

    // INTF vintf ();
    logic vintf_x;
    logic vintf_y;

    assign vintf_x = s1;

    assign vintf_y = '1;
    assign s2 = vintf_y;

    assign s1 = s2;

    initial begin
        $finish;
    end
endmodule

instead works, and same if the intf_driver class is removed. Any ideas on how to approach getting this to work?

RootCubed avatar May 26 '24 10:05 RootCubed

Here is another example which is a bit more realistic and also shows a bit better what I'm trying to achieve. I have confirmed that it compiles and simulates correctly on QuestaSim and vcs. With verilator I get %Error: ico_bug.sv:54: Input combinational region did not converge - so I think this issue does warrant being reopened (or I can make a new issue, if you prefer).

`timescale 1ns/1ps

interface INTF();
    logic clk;
    logic [7:0] data;
    logic valid;
    logic ready;
endinterface

time TA = 1ns; // application time

class intf_driver;
    virtual INTF intf;
    function new(virtual INTF intf);
        this.intf = intf;
    endfunction

    task cycle_start();
        #TA;
    endtask

    task cycle_end();
        @(posedge intf.clk);
    endtask

    task init_master();
        intf.data = '0;
        intf.valid = 0;
    endtask

    task init_slave();
        intf.ready = 0;
    endtask

    task recv_data(output logic [7:0] data);
        intf.ready <= #TA 1;
        cycle_start();
        while (intf.valid != 1) begin cycle_end(); cycle_start(); end
        cycle_end();
        data = intf.data;
        intf.ready <= #TA 0;
    endtask

    task send_data(input logic [7:0] data);
        intf.data <= #TA data;
        intf.valid <= #TA 1;
        cycle_start();
        while (intf.ready != 1) begin cycle_end(); cycle_start(); end
        cycle_end();
        intf.valid <= #TA 0;
    endtask
endclass

module top();
    logic clk;
    logic [7:0] data;
    logic valid;
    logic ready;

    INTF read_intf();
    assign read_intf.clk = clk;
    assign read_intf.data = data;
    assign read_intf.valid = valid;
    assign ready = read_intf.ready;

    INTF write_intf();
    assign write_intf.clk = clk;
    assign data = write_intf.data;
    assign valid = write_intf.valid;
    assign write_intf.ready = ready;

    intf_driver driver_master;
    intf_driver driver_slave;

    initial begin
        forever begin
            clk = '1;
            #10ns;
            clk = '0;
            #10ns;
        end
    end

    initial begin
        driver_master = new(write_intf);
        driver_master.init_master();

        #32ns;
        driver_master.send_data(8'h42);
    end

    logic [7:0] recv_data;
    initial begin
        driver_slave = new(read_intf);
        driver_slave.init_slave();

        #22ns;
        driver_slave.recv_data(recv_data);

        $display("Got data: %02x", recv_data);
        $finish;
    end
endmodule

RootCubed avatar May 28 '24 09:05 RootCubed

@wsnyder Is this area something that might be fixed / improved on in the future or am I better off trying to work around the issue with different SystemVerilog functionalities for now? (I could also try fixing it myself, but I'm guessing it would be quite involved...)

RootCubed avatar Jun 06 '24 15:06 RootCubed

I agree this should be open. I'd suggest working around it - it's not clear to me how to fix this with the current scheduling, perhaps @kbieganski has ideas.

wsnyder avatar Jun 07 '24 11:06 wsnyder

I think what would help in most (all?) of these cases is more granular virtual interface triggers, i.e. a separate trigger per interface member.

Right now, there's one trigger per virtual interface type which leads to spurious combinational loops. This:

    assign vintf.x = s1;
    assign s2 = vintf.y;
    assign s1 = s2;

Becomes a loop like this:

... -> s1 -> vintf -> s2 -> s1 -> ...

If we had separate triggers for x and y, there would be no loop.

vintf.y -> s2 -> s1 -> vintf.x

kbieganski avatar Jun 07 '24 12:06 kbieganski

Agreed, if this were to be implemented, I think it would solve these issues I'm having. I wonder if the "one trigger per virtual interface" approach was done because of performance reasons or just because it was easier to implement... either way, would be really cool to see this get worked on.

RootCubed avatar Jun 07 '24 17:06 RootCubed

It was easier. I didn't foresee this problem.

kbieganski avatar Jun 10 '24 08:06 kbieganski

I also got this problem but never was able to create a small re-producible test case b/c of other failures like #5203

solomatnikov avatar Jul 01 '24 16:07 solomatnikov

Hi gang, I wonder if there is an update pending on this issue. My environment is very dependent on virtual interfaces and I lack the skill to debug the verilator sources.

MikeOpenHWGroup avatar May 01 '25 16:05 MikeOpenHWGroup

Based on looking at this thread and pull requests, I don't see anyone working on this.

wsnyder avatar May 01 '25 21:05 wsnyder

We’d be happy to try and help support this issue. Any progress will be reported here. ;)

YilouWang avatar May 20 '25 13:05 YilouWang

Great, I think they are all tagged with "lint" so any in the list would be great! https://github.com/verilator/verilator/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22area%3A%20lint%22

e.g. #5876 for ANDs, #5681 for assign, etc.

wsnyder avatar May 20 '25 13:05 wsnyder

Hello, Everyone

After the recent PR #6148 , I believe the "combinational region did not converge" issue has been resolved. We've adopted the member-level trigger approach mentioned in the issue to handle virtual interface assignments.

However, during development, I discovered another issue related to virtual interface. To distinguish it from this one, I’ve created a new issue #6175 describing the problem. I plan to work on it later, and any suggestions on how to address it would be greatly appreciated.

YilouWang avatar Jul 14 '25 08:07 YilouWang

Passes now on master, almost certainly thanks to #6148.

wsnyder avatar Sep 09 '25 01:09 wsnyder