The interviewer asks for a parameterized N-input adder: what breaks when N is 1 or not a power of two, and what guards the long carry chain at timing closure.
The question tests two skills at once. Parameters must stay correct at every legal configuration, and the arithmetic cone must still meet setup.
For positive W and N, W plus $clog2(N) bits is a safe unsigned sum width, including N = 1 and non-powers of two; it is not always the minimum width for very small W. Accumulate into that full width so no configuration truncates.
The registered output option creates a sampling boundary and adds latency; it does not split the addition cone before that register. If that cone misses its available budget, balance the add tree or insert registers inside it, aligning the other operands and valid bits with each stage. Confirm the mapped topology and timing report.
Read each panel top to bottom from the inputs. The shaded boxes are registers; the depth that matters is the number of adders between two of them.
Left: three adders sit between the input registers and REG_OUT. REG_OUT added latency but removed no depth.
Right: the tree has two levels, and a register rank between them cuts each stage to one adder.
The cost: one more cycle, so valid and any side-band signals need the same extra stage.
Configuration | Width and behaviour |
|---|---|
N = 1 | Sum is W bits; the loop is a single pass |
N a power of two | No out-of-range case; |
N = 5, W = 8 | 11-bit sum; holds 5 times 255 with margin |
Chain misses setup | Balance or internally pipeline the cone; an output register alone does not reduce its depth |
Why `$clog2(N)` and not a fixed margin? Five 8-bit operands reach 1275, which needs 11 bits. $clog2(5) is 3, so the margin tracks N instead of guessing.
What can fail for large N? Addition depth, input arrival budgets, fanout and routing can all matter. REG_OUT puts the entire cone before one output register; it does not cut that cone in half. A balanced tree or internal pipeline requires a separate implementation and latency-aware checks.
How are illegal configurations stopped? Require W >= 1 and N >= 1, and emit a fatal configuration diagnostic. Run illegal configurations as separate expected-failure elaboration/startup jobs. The finite legal matrix below does not prove every parameter value or a tool's handling of malformed port ranges.
“Keep the W-bit result; the testbench used small values” hides truncation behind weak stimulus. Full-scale vectors are the minimum.
“Fix the chain at the gate level only” repeats layout pain on every ECO. A structural register slice is the RTL answer when latency allows.
Five times 255 is 1275, which needs 11 bits. The testbench below checks exactly that vector on both paths.
No. All additions still lie between the input registers and the output register. Reduce combinational depth or pipeline inside the cone, then verify operand/valid alignment and timing.
The bench checks 65 vectors per configuration for (W,N) = (1,1), (3,4), (8,5). This covers singleton, power-of-two and non-power-of-two examples, with full-scale inputs on both output modes.
module para_add #(
parameter int W = 8,
parameter int N = 4,
parameter int REG_OUT = 0
) (
input logic clk,
input logic [(N*W)-1:0] d,
output logic [W+$clog2(N)-1:0] sum
);
localparam int SW = W + $clog2(N);
if (W < 1 || N < 1) begin : g_bad_config
initial $fatal(1, "para_add requires W >= 1 and N >= 1");
end
logic [SW-1:0] sum_comb, sum_r;
always_comb begin
sum_comb = '0;
for (int i = 0; i < N; i++)
sum_comb = sum_comb + d[i*W +: W];
end
always_ff @(posedge clk) sum_r <= sum_comb;
assign sum = (REG_OUT != 0) ? sum_r : sum_comb;
endmodule
module para_add_case #(parameter int W = 8, N = 5) (output logic done = 0);
timeunit 1ns; timeprecision 1ps;
localparam int SW = W + $clog2(N);
logic clk = 0;
logic [(N*W)-1:0] d;
logic [SW-1:0] sum_c, sum_r;
para_add #(.W(W), .N(N), .REG_OUT(0)) dut_c (.clk(clk), .d(d), .sum(sum_c));
para_add #(.W(W), .N(N), .REG_OUT(1)) dut_r (.clk(clk), .d(d), .sum(sum_r));
always #5 clk = ~clk;
int errors = 0;
int v, acc, prev;
initial begin
d = '0; prev = 0;
@(negedge clk);
@(posedge clk); #1;
for (int k = 0; k < 64; k++) begin
acc = 0;
for (int i = 0; i < N; i++) begin
v = (k * 13 + i * 29 + 7) % (2**W);
d[i*W +: W] = v[W-1:0];
acc = acc + v;
end
#1;
if (sum_c !== acc[SW-1:0]) errors++;
if (sum_r !== prev[SW-1:0]) errors++;
@(posedge clk); #1;
if (sum_r !== acc[SW-1:0]) errors++;
prev = acc;
end
acc = 0;
for (int i = 0; i < N; i++) begin
d[i*W +: W] = {W{1'b1}};
acc = acc + (2**W - 1);
end
#1;
if (sum_c !== acc[SW-1:0]) errors++;
if (sum_r !== prev[SW-1:0]) errors++;
@(posedge clk); #1;
if (sum_r !== acc[SW-1:0]) errors++;
if (errors != 0) $fatal(1, "FAIL: W=%0d N=%0d errors=%0d", W, N, errors);
done = 1;
end
endmodule
module tb_para_add;
timeunit 1ns; timeprecision 1ps;
wire done_single, done_power, done_nonpower;
para_add_case #(1, 1) singleton_case (done_single);
para_add_case #(3, 4) power_case (done_power);
para_add_case #(8, 5) nonpower_case (done_nonpower);
initial begin
wait (done_single && done_power && done_nonpower);
$display("PASS: three legal configurations, 65 vectors each, both output modes");
$finish;
end
initial begin #10000; $fatal(1, "FAIL: parameter matrix timeout"); end
endmoduleBench run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It checks 65 vectors per configuration for (W,N) = (1,1), (3,4), (8,5). This is finite evidence, not proof of the entire legal parameter range.