Signed arithmetics in Verilog: The only rule one needs to know
The golden rule is: All operands must be signed.
Verilog, it seems, is strongly inclined towards unsigned numbers. Any of the following yield an unsigned value:
- Any operation on two operands, unless both operands are signed.
- Based numbers (e.g. 12′d10), unless the explicit “s” modifier is used)
- Bit-select results
- Part-select results
- Concatenations
So the bottom line is to either use the $signed system function, or define signed wires and registers.
For example, to multiply a signed and unsigned register, yielding a signed value (of course), go something like this:
reg [15:0] a; // Unsigned reg signed [15:0] b; wire signed [16:0] signed_a; wire signed [31:0] a_mult_b; assign signed_a = a; // Convert to signed assign a_mult_b = signed_a * b
Note that signed_a is one bit wider than “a”, so there’s room for the sign bit, which is always zero. If this wasn’t for this extra bit, a’s MSB would be treated as the sign bit in signed_a.
It may seem necessary to explicitly determine signed_a’s MSB with sometime like {1′b0, a} instead of just “a”, but the Verilog standard is pretty explicit about the signed vs. unsigned being determined by the expression only, and not by the left hand side. So “a” is treated as an unsigned value, and is hence extended by zero.
Reader Comments
Thank you, man. I thought I was crazy.
Thanks
thanks to you bro
I also find that arrays of signed numbers do not work as expected. Verilog signed math is fraught with hazards.
Good point!
Hey Eli,
thanks for your great blog, it helped me a lot so far! :)
Just recently I realized another issue with Verilog signed operations in Cadence:
I declared two 16-bit operands, one signed & one unsigned. Then I executed the following operation:
prod = $signed(op_a_signed * op_b_unsigned);
and it turned out that the result returned by the typecast $signed() was also a 16-bit value (LSBs) unlike the result reg (prod) which was declared as a 32-bit value.
So apparently the typecasts $signed() or $unsigned() return a value with bitness of the operands, not the bitness of the value the result is returned to.
Maybe this helps someone!
I avoid declaring signed type altogether. Much easier to just manage 2s complement sign explicitly. I have seen inconsistency in how signed type is handled between different simulators and compilers.