Skip to content

foreach inside branch inside always_comb with --exclude=Always is rejected by yosys due to inferred latch on loop index #319

@Arnavion

Description

@Arnavion

sv2v 0.0.13
Yosys 0.54 (git sha1 db72ec3bde296a9512b2d1e6fabf81cfb07c2c1b, g++ 15.1.1 -fPIC -O3)


cat >foo.sv <<-EOF
module foo (
    input bit[7:0] arg,
    input bit cond,
    output bit result
);
    always_comb begin
        if (cond)
            result = '0;
        else begin
            result = '0;
            foreach (arg[i])
                result ^= arg[i];
        end
    end
endmodule
EOF

sv2v --exclude=Always foo.sv >foo.translated.sv

yosys -p 'read_verilog -sv foo.translated.sv; proc'

The last command fails with:

3.8. Executing PROC_DLATCH pass (convert process syncs to latches).
No latch inferred for signal '\foo.\result' from process '\foo.$proc$foo.translated.sv:0$2'.
ERROR: Latch inferred for signal '\foo.\sv2v_autoblock_1.i' from always_comb process '\foo.$proc$foo.translated.sv:0$2'.

The sv2v output in foo.translated.sv is:

module foo (
	arg,
	cond,
	result
);
	input wire [7:0] arg;
	input wire cond;
	output reg result;
	always_comb if (cond)
		result = 1'sb0;
	else begin
		result = 1'sb0;
		begin : sv2v_autoblock_1
			integer i;
			for (i = 7; i >= 0; i = i - 1)
				result = result ^ arg[i];
		end
	end
endmodule

The problem is caused by the integer i; declaration. yosys would've been accepted it if sv2v had emitted for (integer i = 7; ...; it just doesn't like when integer i; is declared separately.

Note this only happens when the foreach is inside a branch like the else here. If it was at the top-level yosys is fine. I'm guessing that yosys's behavior is that it lifts the integer i; declaration to static lifetime, and then notices that it is only assigned definitely in the else branch, hence infers a latch.

Removing --exclude=Always makes it work because yosys has no problem inferring a latch inside always @(*), though it (correctly) does not generate a latch in the final netlist.

This problem also exists for regular for ($declations; ... because sv2v moves the declaration outside the loop, eg for (int i = 7; ... -> reg signed [31:0] i; for (i = 7; ..., which then trips up yosys in the same way.


I'm a Verilog novice, but if I'm reading the Verilog and SystemVerilog specs correctly:

  • In Verilog, variable declarations are always static lifetime. In SystemVerilog this is still the case for declarations not annotated with automatic. So yosys is justified in inferring a latch for integer i;

  • In SystemVerilog, in a for-loop where the initialization statement declares a variable, eg for (integer i = ..., the variable has an automatic lifetime by default. So yosys is justified in not inferring a latch for i in this case.

I guess the more correct downleveling would be to move the variable declaration to the scope containing the always block (module / function / task), with hygienic renaming if necessary, and then assign it to 'x at the start of the always block?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions