The issue is {N{1'b0}}
is an unsigned value. When verilog compares an unsigned and signed value it will treat both values as unsigned.
The following quote exists in IEE1364-2005 (Verilog) § 5.1.7 and IEEE1800-2012 (SystemVerilog) & section 11.4.4. A near identical quote is in IEEE1364-2001 (Verilog) § 4.1.7:
When one or both operands of a relational expression are unsigned, the expression shall be interpreted as a comparison between unsigned values. If the operands are of unequal bit lengths, the smaller operand shall be zero-extended to the size of the larger operand.
When both operands are signed, the expression shall be interpreted as a comparison between signed values. If the operands are of unequal bit lengths, the smaller operand shall be sign-extended to the size of the larger operand.
You need to cast the unsinged as signed, ie $signed({N{1'b0}})
. Alternatively, you could look at the MSB of a
to know if it is negative.
parameter N = 16;
wire signed [N-1:0] a;
assign a = -100;
function [N-1:0] abs_old (input signed [N-1:0] a);
abs_old = (a < {N{1'b0}}) ? -a : a; // unsigned compare
endfunction
function [N-1:0] abs_new (input signed [N-1:0] a);
abs_new = (a < $signed({N{1'b0}})) ? -a : a; // signed compare
endfunction
function [N-1:0] abs_msb (input signed [N-1:0] a);
abs_msb = (a[N-1]) ? -a : a; // MSB check
endfunction
initial begin
$strobe("a:%0d abs(old):%0d", a, abs_old(a)); // a:-100 abs(old):65436
$strobe("a:%0d abs(new):%0d", a, abs_new(a)); // a:-100 abs(new):100
$strobe("a:%0d abs(msb):%0d", a, abs_msb(a)); // a:-100 abs(msb):100
end