calyxir/calyx

Components that do a single read/write to a `seq_mem` generate circular combinational logic

Opened this issue · 2 comments

The following program, when run through fud with fud e <program> --from calyx --to vcd --through verilog creates a circular combinational logic error (Simulating with icarus-verilog just hangs):

=====STDERR=====
%Warning-UNOPTFLAT: /tmp/tmp_n0qtzxz:1260:7: Signal unoptimizable: Circular combinational logic: 'TOP.main.my_reader_ref_mem_done'
                                           : ... In instance TOP.main
 1260 | logic my_reader_ref_mem_done;
      |       ^~~~~~~~~~~~~~~~~~~~~~
                    ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=5.006
                    ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
                    /tmp/tmp_n0qtzxz:1260:7:      Example path: TOP.main.my_reader_ref_mem_done
                    /tmp/tmp_n0qtzxz:1474:22:      Example path: ASSIGNW
                    /tmp/tmp_n0qtzxz:1294:7:      Example path: TOP.main.invoke1_go_in
                    /tmp/tmp_n0qtzxz:1465:31:      Example path: ASSIGNW
                    /tmp/tmp_n0qtzxz:1260:7:      Example path: TOP.main.my_reader_ref_mem_done
%Warning-UNOPTFLAT: /tmp/tmp_n0qtzxz:1274:7: Signal unoptimizable: Circular combinational logic: 'TOP.main.my_writer_ref_mem_done'
                                           : ... In instance TOP.main
 1274 | logic my_writer_ref_mem_done;
      |       ^~~~~~~~~~~~~~~~~~~~~~
                    /tmp/tmp_n0qtzxz:1274:7:      Example path: TOP.main.my_writer_ref_mem_done
                    /tmp/tmp_n0qtzxz:1471:22:      Example path: ASSIGNW
                    /tmp/tmp_n0qtzxz:1290:7:      Example path: TOP.main.invoke0_go_in
                    /tmp/tmp_n0qtzxz:1456:31:      Example path: ASSIGNW
                    /tmp/tmp_n0qtzxz:1274:7:      Example path: TOP.main.my_writer_ref_mem_done
%Error: Exiting due to 2 warning(s)

=====STDOUT=====

The program in question:

import "primitives/core.futil";
import "primitives/binary_operators.futil";
import "primitives/memories/seq.futil";

component main () -> (){
    cells {
        my_reader = reader();
        my_writer = writer();
        real_mem = seq_mem_d1(32, 2, 1);
    }
    wires{}

    control{
        seq{
            invoke my_writer[ref_mem = real_mem]()();
            invoke my_reader[ref_mem=real_mem]()();
        }
    }
}



component reader () -> () {
    cells {
        ref ref_mem = seq_mem_d1(32, 2, 1);
    }
    wires{
        group read1 {
            ref_mem.content_en = 1'b1;
            ref_mem.addr0 = 1'b0;
            read1[done] = ref_mem.done;
        }
    }
    control{
        read1;
    }
}

component writer () -> () {
    cells {
        ref ref_mem = seq_mem_d1(32, 2, 1);
    }

    wires{
        group write1 {
            ref_mem.content_en = 1'b1;
            ref_mem.write_en  =1'b1;
            ref_mem.write_data = 32'b1;
            ref_mem.addr0 = 1'b0;
            write1[done] = ref_mem.done;
        }
    }

    control{
        write1;
    }
}

This does not happen if the reader and writer components both have another group thats reads/writes to the second address of the seq_mem_d1. i.e the program listed in this comment of #1955.

cc @rachitnigam

@nathanielnrn, can you try dumping out the version of the program from lowering fud e --to calyx-lowered ... and without optimizations fud e -s calyx.flags ' -p no-opt' ... and see if the error persists. For the former program, you can try reading the final code generated before we emit Verilog and see if there is an obvious problem. Otherwise, we'll have to stare at the Verilog and figure out the problem.

I will not have any debugging cycles so please take a shot and let me know what you learn. I can try to provide async help.

Just posting some updates as I find them. If we disable optimizations the following program also does not simulate (with optimizations this is fine). It produces an error similar to that in the original issue:

import "primitives/core.futil";
import "primitives/binary_operators.futil";
import "primitives/memories/seq.futil";

component main () -> (){
    cells {
        my_writer = writer();
        real_mem = seq_mem_d1(32, 2, 1);
    }
    wires{}
    control{
        seq{
            invoke my_writer[ref_mem = real_mem]()();
        }
    }
}

component writer () -> () {
    cells {
        ref ref_mem = seq_mem_d1(32, 2, 1);
    }

    wires{
        group write1 {
            write1[done] = ref_mem.done;
        }
    }

    control{
        write1;
    }
}

With the following error

=====STDERR=====
%Warning-UNOPTFLAT: /tmp/tmp78bk0ii7:1263:7: Signal unoptimizable: Circular combinational logic: 'TOP.main.my_writer_ref_mem_done'
                                           : ... In instance TOP.main
 1263 | logic my_writer_ref_mem_done;
      |       ^~~~~~~~~~~~~~~~~~~~~~
                    ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=5.006
                    ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
                    /tmp/tmp78bk0ii7:1263:7:      Example path: TOP.main.my_writer_ref_mem_done
                    /tmp/tmp78bk0ii7:1399:22:      Example path: ASSIGNW
                    /tmp/tmp78bk0ii7:1280:7:      Example path: TOP.main.invoke0_go_in
                    /tmp/tmp78bk0ii7:1393:31:      Example path: ASSIGNW
                    /tmp/tmp78bk0ii7:1263:7:      Example path: TOP.main.my_writer_ref_mem_done
%Error: Exiting due to 1 warning(s)

=====STDOUT=====

However, the same program with the seq{} removed in main does seem to simulate (at least, verilator doesn't throw any combinational loop errors.

control{
     invoke my_writer[ref_mem = real_mem]()();
}

So perhaps something is going wrong in some control sequence pass?