For context-determined arithmetic such as y = a + b, the operation uses the widest operand or assignment target: operands extend first, then the result is assigned. That context does not cross a self-determined boundary. Concatenation operands are self-determined, so y = {a + b} performs the addition at the operands' width even when y is wider. Parentheses alone do not create that boundary.
Code | Width of the add | Result for 15 + 1 |
|---|---|---|
| 5 (target is 5 bits) | 16 |
| 4 (nothing wider) | 0 (carry lost) |
| 4 (concatenation operand is self-determined) | 0 (a wider target cannot recover the carry) |
| 5 (operand extended before the boundary) | 16 |
| 4 | 0 |
| 5 | 8 |
Read each column top to bottom: the step where the operation width is fixed is the one that decides the answer.
Left column: the 5-bit target reaches the operands, so they are extended before the add and the carry has a bit to land in.
Right column: the braces stop the target's width at the boundary, so the add happens at 4 bits and only the 4-bit result is extended.
Same operands, same target: the only difference is where the width was decided, which gives 16 on one side and 0 on the other.
Size every literal: 4'd1, not 1. Small unsized decimal examples are 32 bits wide; larger values can need more. An unsized operand can widen a context-determined expression.
Capture the carry with a concatenated target: {carry, sum} = a + b;.
Make truncation visible: write the slice you keep (wide[7:0]) or use a size cast, so a reader sees it is intentional.
Lint your code. Tools such as Verilator (-Wall) report width mismatches before simulation.
Comparisons with operands of different widths zero-extend the narrower (or sign-extend, if both are signed). Check what you compare.
module add_with_carry (
input wire [7:0] a, b,
output wire [7:0] sum,
output wire carry
);
assign {carry, sum} = a + b; // 9-bit result split into carry and sum
endmoduleassign y = a * b; with 8-bit a, b and a 16-bit y. Is the multiplication done in 8 or 16 bits?In 16 bits, because the target is wider than the operands, so the full product is kept. If y were 8 bits the product would be computed in 8 bits and the upper half lost.
The testbench checks 15 plus 1, then all 256 four-bit operand pairs across the concatenation boundary.
module tb_width;
logic [3:0] a = 4'hF, b = 4'h1;
logic [4:0] s5, s5b, s5_concat, s5_concat_wide;
logic [3:0] s4;
int errors = 0, expected;
initial begin
s5 = a + b; // target is 5 bits, so the add is done in 5 bits
s4 = a + b; // target is 4 bits: carry is lost, 15 + 1 wraps to 0
s5b = {1'b0, a} + b; // explicit 5-bit operand: same result as s5
$display("s5=%0d s4=%0d s5b=%0d", s5, s4, s5b);
if (s5 !== 5'd16) errors++;
if (s4 !== 4'd0) errors++;
if (s5b !== 5'd16) errors++;
// Inside a bare expression the width is the widest *operand* (4 bits), not "big enough".
// Compare against SIZED constants: an unsized 0 would widen the whole expression to 32 bits.
if (((a + b) >> 1) !== 4'd0) errors++; // 16 wrapped to 0 before the shift
if ((({1'b0, a} + b) >> 1) !== 5'd8) errors++; // widened first, so 16 >> 1 = 8
s5_concat = {a + b};
s5_concat_wide = {({1'b0, a} + b)};
if (s5_concat !== 5'd0 || s5_concat_wide !== 5'd16) errors++;
for (int i = 0; i < 16; i++) begin
for (int j = 0; j < 16; j++) begin
a = i[3:0]; b = j[3:0];
expected = i + j;
s5 = a + b;
s5_concat = {a + b};
s5_concat_wide = {({1'b0, a} + b)};
if (s5 !== expected[4:0]) errors++;
if (s5_concat !== {1'b0, expected[3:0]}) errors++;
if (s5_concat_wide !== expected[4:0]) errors++;
end
end
if (errors == 0) $display("PASS: width rules as described");
else $display("FAIL: width errors=%0d", errors);
$finish;
end
endmodules5=16 s4=0 s5b=16