Verilog & SystemVerilog DISCUSSION

Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

Started by zeeshanarain Verilog expression bit widthcontext-determined sizingsigned arithmetic$signedcarry truncation
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

zeeshanarain Verilog & SystemVerilog Forum
#1

I average two 8-bit samples with assign avg = (a + b) >> 1; where all three are [7:0]. For a = 200 and b = 100 I get 22 instead of 150. Yet assign sum = a + b; with a 9-bit sum gives the correct 300. In another module a signed 8-bit value of -1 added to an unsigned 8-bit value of 1 and stored in a 16-bit signed result gives 256 instead of 0.

How does Verilog decide the width and signedness of an expression, and how should I write these two calculations?

Community replies 4

Re: Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

#2

Verilog sizes an expression from its context, not from the values. For operators like +, the working width is the largest of the operand widths and the width of the left-hand side of the assignment, and every operand is extended to that width before anything is calculated. In avg = (a + b) >> 1 the operands and the target are all 8 bits, so the addition is done in 8 bits: 300 becomes 300 - 256 = 44, and 44 shifted right is 22. The carry was discarded before the shift.

In sum = a + b the 9-bit target makes the context 9 bits wide, both operands are extended first, and the carry survives.

Re: Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

#3

Fix the average by making the addition wider than its operands. Either extend one operand, assign avg = ({1'b0, a} + b) >> 1;, which makes the expression 9 bits, or use a named intermediate: wire [8:0] sum = a + b; assign avg = sum[8:1];. The second form documents the carry bit and is easier to check in a waveform. Both give 300 / 2 = 150.

Be careful with concatenation and replication: operands inside { } are self-determined, so {a + b} is evaluated in 8 bits whatever it is assigned to, and the carry is lost again.

Re: Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

#4

Signedness follows a harsher rule: an expression is signed only if every operand is signed. One unsigned operand makes the whole expression unsigned, and then the extension to the context width uses zeros, not sign bits. Your -1 is 8'hFF; in an unsigned 16-bit context it becomes 16'h00FF, that is 255, and 255 + 1 = 256. Declaring the result signed does not help, because the type of the left-hand side does not affect how the right-hand side is evaluated.

Make both operands signed. For an unsigned value that should stay positive, add a zero bit first: a_s + $signed({1'b0, b_u}). Plain $signed(b_u) would reinterpret values of 128 and above as negative.

Re: Verilog expression widths: why does my 8-bit average lose the carry, and how does signed work?

#5

Other places where signedness disappears without notice: a bit-select or part-select is always unsigned, even x[7:0] of a signed x; a concatenation is unsigned; and sized literals are unsigned unless written with s, as in 8'sd5, while a bare decimal such as 5 is signed. The arithmetic shift >>> fills with the sign bit only when its left operand is signed: for a signed -8, x >>> 1 is -4, but in an unsigned expression it behaves like >>.

Multiplication needs the same care with width: the product of two 8-bit values needs 16 bits, so assign it to a 16-bit target or the upper half is dropped.

TEP COMMUNITY