Bit-width of arithmetic expressions in Verilog-2001? broken

P

PCBman

Guest
I've written the following broken-code:

module v2001_practice;

reg signed [7:0] in_a, in_b;
wire signed [8:0] out_raw;
wire signed [7:0] out_c = out_raw; // truncate the upper bit!
wire signed [7:0] out_d; // <---- why does this guy truncate the result?!?

assign out_raw = (in_a + in_b)>>>1; // GOOD, correct 2's Complement result
assign out_d = (in_a + in_b)>>>1; // BAD, certain combos of in_a, in_b cause
truncated upper bit!
integer ectr, tctr;

initial begin
ectr = 0;
tctr = 0;
for ( in_a = -128; in_a < 127; in_a = in_a + 1 )
for ( in_b = -128; in_b < 127; in_b = in_b + 1 )
begin
#1;
if (out_c !== out_d)
begin
$display("ERROR, (%d+%d)>>>1 out_c(%d) !== out_d(%d)",
in_a, in_b, out_c, out_d );
ectr = ectr + 1; // increment error-counter
end // if
tctr = tctr + 1; // increment 'total' counter
end // for ( in _b

$display("tctr = %0d, ectr = %0d", tctr, ectr );
#100 $finish;
end

endmodule

///////////////////

Output from Modelsim 6.1c:

# tctr = 65025, ectr = 16131
# ** Note: $finish : sva_practice.v(28)
# Time: 65125 ps Iteration: 0 Instance: /v2001_practice

/////////////////

Oh the horror! Could someone explain to me why out_c[7:0] and out_d[7:0]
don't match?
Apparently, storing the intermediate result (in out_raw[8:0]) makes a
difference in the
final result of out_c[]!

Does it have something to do with how Verilog resolves the bit-width of
arithmetic expressions?
 
On Wed, 14 Mar 2007 06:43:12 GMT, "PCBman" <anon@anon.com> wrote:

in_a, in_b;
wire signed [8:0] out_raw;
wire signed [7:0] out_c = out_raw; // truncate the upper bit!
wire signed [7:0] out_d; // <---- why does this guy truncate the result?!?

assign out_raw = (in_a + in_b)>>>1; // GOOD, correct 2's Complement result
assign out_d = (in_a + in_b)>>>1; // BAD, certain combos of in_a, in_b cause

Oh the horror!
No, it's just Verilog :)

) makes a
difference in the final result of out_c[]!

Does it have something to do with how Verilog resolves the bit-width of
arithmetic expressions?
Most definitely so.

assign out_raw = (in_a + in_b)>>>1; // GOOD, correct 2's Complement result

out_raw has 9 bits. The expression (in_a + in_b) is
context-determined, and "knows" the 9-bit width of the assignment
target. So in_a and in_b are extended to 9 bits (and, they being
signed, that's done by 2s complement sign extension) before
the addition and then the shift are performed. The full 9 bits of
the sum are preserved, and then you shift down by one bit and
get 8 useful bits of result, but of course it's correctly
sign-extended into the result by your >>> operation.


assign out_d = (in_a + in_b)>>>1; // BAD

The sum (in_a+in_b) is calculated only to 8 bits, so you
can easily get an overflow. Let's add two large-ish
negative numbers:

-96 = 8'sb10100000
-64 = 8'sb11000000
=============
-160 = 9'sb101100000

But your target out_d is only 8 bits wide, so Verilog did NOT do 9-bit
arithmetic - it did only 8-bit. So your result is 8'sb01100000, which
of course looks positive! Now, when that's right-shifted, you do NOT
collect the lost sign bit, and you get 8'sb00110000, which of course
is wildly wrong.

This stuff is rather badly explained in the 2001 LRM, rather better
explained in the 1364-2005 LRM.

Paranoia is a useful survival skill for an engineer :)
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
Jonathan Bromley wrote:
On Wed, 14 Mar 2007 06:43:12 GMT, "PCBman" <anon@anon.com> wrote:
<snip>

assign out_d = (in_a + in_b)>>>1; // BAD

The sum (in_a+in_b) is calculated only to 8 bits, so you
can easily get an overflow. Let's add two large-ish
negative numbers:

-96 = 8'sb10100000
-64 = 8'sb11000000
=============
-160 = 9'sb101100000

But your target out_d is only 8 bits wide, so Verilog did NOT do 9-bit
arithmetic - it did only 8-bit. So your result is 8'sb01100000, which
of course looks positive! Now, when that's right-shifted, you do NOT
collect the lost sign bit, and you get 8'sb00110000, which of course
is wildly wrong.

This stuff is rather badly explained in the 2001 LRM, rather better
explained in the 1364-2005 LRM.

Paranoia is a useful survival skill for an engineer :)

So would the proper result be had by sign extending in_a and in_b?

assign d_out = ({in_a[7],in[a]}+in_b[7],in_b})>>>1;

Not pretty, but accurate?
 
On Wed, 14 Mar 2007 13:36:43 GMT, John_H
<newsgroup@johnhandwork.com> wrote:

So would the proper result be had by sign extending in_a and in_b?

assign d_out = ({in_a[7],in[a]}+in_b[7],in_b})>>>1;
Depressingly, no. (You've missed an opening brace before
in_b[7], but that's not my concern.) Your concatenations
are *unsigned*, so the result of the sum is unsigned,
and therefore the >>> operator is operating on an
unsigned value so it zero-fills instead of sign-filling.

This would work, though:

assign d_out = $signed({in_a[7],in[a]}+{in_b[7],in_b}) >>> 1;

And then you would need to sign-extend only one of the
two operands of +, because the other will be widened
(and sign extended, since it's signed) to the same
width as the widest operand:

assign d_out = $signed({in_a[7],in[a]}) + in_b >>> 1;
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
On Wed, 14 Mar 2007 14:44:19 +0000, Jonathan Bromley
<jonathan.bromley@MYCOMPANY.com>
was obviously suffering a coffee shortfall when he wrote:

}) + in_b >>> 1;
Oops, missing parentheses. Try this:

}) + in_b) >>> 1;
Sorry.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
Jonathan Bromley <jonathan.bromley@MYCOMPANY.com> writes:
<on adding signed numbers together>

assign d_out = $signed({in_a[7],in[a]}) + in_b >>> 1;
So *that's* why I use VHDL :)

Cheers,
Martin

--
martin.j.thompson@trw.com
TRW Conekt - Consultancy in Engineering, Knowledge and Technology
http://www.conekt.net/electronics.html
 
On Thu, 15 Mar 2007 14:06:43 +0000, Martin Thompson
<martin.j.thompson@trw.com> wrote:

So *that's* why I use VHDL :)
The rules, especially concerning signed/unsigned
arithmetic, have been responsible for many a
scorched neuron on our advanced Verilog classes.
Such cerebral damage is not limited to the students ;-)
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 

Welcome to EDABoard.com

Sponsor

Back
Top