Error with counter component in Labs v2

I’m converting some code to Alchitry Labs v2, and I noticed some code that worked in v1 now produces an error in v2 (with Vivado 2024.2 on Windows).

The code in question uses the counter component:

counter ctr1 (#SIZE(5), #DIV(12));

In v1 this worked, but in v2 this results in build errors:

INFO: [Synth 8-6157] synthesizing module ‘counter’ [C:/Users/fairall/Documents/alchitry/apple2c_AL/build/vivado/apple2c_AL.srcs/sources_1/imports/source/counter.sv:7]
Parameter SIZE bound to: 3’b101
Parameter DIV bound to: 4’b1100
Parameter TOP bound to: 1’b0
Parameter UP bound to: 1’b1
ERROR: [Synth 8-524] part-select [0:-4] out of range of prefix ‘D_ctr_q’ [C:/Users/fairall/Documents/alchitry/apple2c_AL/build/vivado/apple2c_AL.srcs/sources_1/imports/source/counter.sv:23]
ERROR: [Synth 8-6156] failed synthesizing module ‘counter’ [C:/Users/fairall/Documents/alchitry/apple2c_AL/build/vivado/apple2c_AL.srcs/sources_1/imports/source/counter.sv:7]
ERROR: [Synth 8-6156] failed synthesizing module ‘au_top’ [C:/Users/fairall/Documents/alchitry/apple2c_AL/build/vivado/apple2c_AL.srcs/sources_1/imports/source/au_top.sv:7]

Here is part of the v2 code generated for counter.sv (last line shown is line 23 with the error):

module counter #(
parameter SIZE = 4’h8,
parameter DIV = 1’h0,
parameter TOP = 1’h0,
parameter UP = 1’h1
) (
input wire clk,
input wire rst,
output reg [(SIZE)-1:0] value
);
logic [(SIZE + DIV)-1:0] D_ctr_d, D_ctr_q = 0;
localparam NON_ZERO_DIV = DIV == 1’h0 ? 1’h1 : DIV;
localparam MAX_VALUE = DIV > 1’h0 ? {TOP, {NON_ZERO_DIV{1’h1}}} : TOP;
always @* begin
D_ctr_d = D_ctr_q;

    value = D_ctr_q[SIZE + DIV - 1'h1-:SIZE];

Here is the corresponding code generated for that particular instance in v1:

module counter_4 (
input clk,
input rst,
output reg [4:0] value
);

localparam SIZE = 3’h5;
localparam DIV = 4’hc;
localparam TOP = 1’h0;
localparam UP = 1’h1;

reg [16:0] M_ctr_d, M_ctr_q = 1’h0;

localparam MAX_VALUE = 13’h0fff;

always @* begin
M_ctr_d = M_ctr_q;

value = M_ctr_q[12+4-:5];

Might this be a bug in the counter component? Thanks.

I did some more analysis, and I think this does appear to be a bug. I verified the same behavior happens when creating a new base project for Au, and basically only adding:

counter ctr1 (#SIZE(5), #DIV(12))

The compilation log (error) for this is:

INFO: [Synth 8-6157] synthesizing module 'counter' [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/counter.sv:7]
	Parameter SIZE bound to: 3'b101
 	Parameter DIV bound to: 4'b1100
 	Parameter TOP bound to: 1'b0
 	Parameter UP bound to: 1'b1
ERROR: [Synth 8-524] part-select [0:-4] out of range of prefix 'D_ctr_q' [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/counter.sv:23]
ERROR: [Synth 8-6156] failed synthesizing module 'counter' [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/counter.sv:7]
ERROR: [Synth 8-6156] failed synthesizing module 'alchitry_top' [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/alchitry_top.sv:7]

The minimum size for the SIZE parameter that compiles successfully for this line is 5d5:

counter ctr1 (#SIZE(5d5), #DIV(12))

The compilation log for this is:

INFO: [Synth 8-6157] synthesizing module 'counter' [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/counter.sv:7]
	Parameter SIZE bound to: 5'b00101
 	Parameter DIV bound to: 4'b1100
 	Parameter TOP bound to: 1'b0 
	Parameter UP bound to: 1'b1
INFO: [Synth 8-6155] done synthesizing module 'counter' (0#1) [C:/Users/fairall/Documents/alchitry/apple2c_AL2_AU1/build/vivado/apple2c_AL2_AU1.srcs/sources_1/imports/source/

I believe this is due to a rollover in this systemverilog generated for counter:

value = D_ctr_q[SIZE + DIV - 1'h1-:SIZE];

With the default SIZE 5 (3’b101), I think it does a 4-bit calculation (due to DIV parameter):

D_ctr_q[3'b101 + 4'b1100 - 1'h1 -:5]
D_ctr_q[4'b0101 + 4'b1100 - 4'b0001 -:5]
D_ctr_q[4'b0000-:5]     (5+12-1 = 16 = 5b'10000, a rollover in 4 bits to 4'b0000)
D_ctr_q[0:-4]   (the error shown)

With a manually-specified SIZE 5d5 (5’b00101), I think it does a 5bit calculation (due to SIZE parameter):

D_ctr_q[5'b00101 + 4'b1100 - 1'h1 -:5]
D_ctr_q[5'b00101 + 5'b01100 - 5'b00001 -:5]
D_ctr_q[5'b10000-:5]
D_ctr_q[16:12]  (the correct result)

It seems like we shouldn’t have to know what extra size to pass to make the counter module’s internal processing work. There seem to be 2 possible solutions:

  1. Find a way to make sure the SIZE+DIV-1 calculation happens with sufficient bits.
  2. Simply change the value calculation in counter.luc to:
        value = ctr.q[DIV+:SIZE] // set the output

I believe the latter to be a valid solution, based on a quote from the systemverilog spec, where value is like “a_vect” below:

logic [31: 0] a_vect;
logic [0 :31] b_vect;
logic [63: 0] dword;
integer sel;
a_vect[ 0 +: 8] // == a_vect[ 7 : 0]
a_vect[15 -: 8] // == a_vect[15 : 8]
b_vect[ 0 +: 8] // == b_vect[0 : 7]
b_vect[15 -: 8] // == b_vect[8 :15]
dword[8*sel +: 8] // variable part-select with fixed width

@alchitry Does this analysis seem reasonable? Thanks for your help.

Thanks for taking the time to outline all of this.

It is definitely a bug and after reproducing it, I can confirm it is caused by inconsistencies in how Verilog handles expresion widths vs Lucid.

Lucid sets the width of literals to their minimum which can be problematic when translating to Verilog. Verilog sets them to 32 bits which feels fairly arbitrary.

The issues arise when you take into account how Verilog defines the width of expressions. Since the widest value in the expression SIZE + DIV - 1 is 4 bits wide, the expression is performed in 4 bit space. However, that causes it to evaluate to 0 instead of 16 in this case.

It can be easily worked around by setting the parameter values to #SIZE(32d5), #DIV(32d12). The explicit 32 makes the expressions be evaluated to a 32 bit result.

I do NOT like how this works as it is super confusing for things like this. Verilog, as far as I’m aware, just kicks the can down the road by making them 32 bits but if an expression needs more than that you have the same problem. I plan to properly solve this by adding in explicit resizing when necessary but this is a major project and I haven’t had the chance to take it on yet.

Follow the GitHub Issue for progress.

Thanks for responding. The 5d5 for SIZE alone solved my case.

For the counter bug specifically, I think you could keep the existing minimum parameter sizes if you just replace

value = D_ctr_q[SIZE + DIV - 1'h1-:SIZE]

with

value = D_ctr_q[DIV+:SIZE]

in counter.luc, since it avoids the troublesome calculation that could overflow. Perhaps a similar type of approach could be used elsewhere.