Why does my Verilog ALP give wrong results when I add two negative numbers?

Nov 08, 2026 Last reply: 3 hours ago 5 Replies

First reply

willow_guy

That bug is signed overflow, and yes, the sign bit is exactly where it lives. When -100 + -100 happens, the true answer -200 needs 9 bits in two's complement, so the extra bit gets chopped off and your 8-bit result wraps around to +56. The trick with flags: overflow is detected…

Read the full reply

So I built an 8-bit arithmetic processor in Verilog for a school project. Addition and subtraction, signed output. When I add two positive numbers it works fine. When I add something like -100 plus -100 it shold give -200, but I keep getting some positive garbage instead.



I think it has to do with the sign bit overflowing into the carry, but I am not sure how to catch that in simulation. My testbench just checks a few values and none of them happened to be two negatives togehter, wich is probly why I thought it was done.



Is there a simple way to detect when the result goes past the range my bits can hold? Do I need a ninth bit for the answer or is there a trick with the flags? Also if anyone knows a cleaner way to route the operands for subtraction instead of my giant case statement, I would love to hear it. This thing works but it is ugly.


That bug is signed overflow, and yes, the sign bit is exactly where it lives. When -100 + -100 happens, the true answer -200 needs 9 bits in two's complement, so the extra bit gets chopped off and your 8-bit result wraps around to +56. The trick with flags: overflow is detected just by looking at the top three sign bits. If both operands have the same sign bit and the result has a diferent one, you overflowed. In Verilog that's one line: assign overflow = (a[7] == b[7]) && (sum[7] != a[7]);. For subtraction, flip it around since a - b is a + (-b), so the operand signs differ. Add that as an output flag on your ALU and your testbench can assert on it directly.

As for a ninth bit, you can absolutely do it. Sign-extend both inputs to 9 bits before the adder, take the result from bits 7:0, and if bit 8 doesn't match the sign extension of bit 7, that's your overflow flag for free. Doing both approaches in simulation and checking they agree is a nice sanity check on the whole design. For the testbench, two negatives is only one of four sign combinations, so test all of them. Better yet, loop a through 0 to 255, loop b the same, and compare against a 9-bit or wider reference computation using $signed casts. That's only 65k cases, runs in a blink, and catches every boundary: -128 + -1, 127 + 1, -128 - 1. Those edge values are where hand-picked test vectors always seem to miss something.

On the ugly operand routing: you usually don't need a case statement at all. Compute the two's complement of b as ~b + 1, feed the adder either b or that inverted version, and drive the carry-in with the subtract signal (1 for subtract, 0 for add). That collapses the whole thing to a mux, an XOR layer, and one adder. Same hardware idea the classic 74181 used.

Been down this road with a small adder project of mine. One thing that saved me: print your results with $signed in the display statement, otherwise the simulator shows you 156 when the answer is actually -100 and you stare at it thinking the math is broken when it's just the display lying to you.

Also, don't just test the corner values once and move on. I kept a little text file of every bug my loop testbench caught, and half of them were cases I swore I'd allready checked by hand. Turns out I'd checked them wrong. 25 hours on this at 13 is solid going, the ugly case statement version is how everyone starts anyway.

The $signed thing in the second reply is so real. I lost a whole evening once because my testbench was comparing an 8-bit wire against a negative literal, so the comparison itself was unsigned and every negative test 'failed' even though the hardware was right. Now I always cast both sides explicitly in the checker, not just in the display statements. Also worth doing: dump the mismatches to a log with $fwrite instead of just counting errors, because seeing the actual input pairs side by side makes the pattern obvious fast.

Reading this made me go check my own little adder project and turns out I had the same hole but on the positive side, 120 plus 120 wraps to a negative and I never caught it because my test list was mostly subtraction cases. Funny how you only find the combos you never wrote down. One thing I'm still fuzzy on with that invert-and-carry trick, when I tried somethig like it last week I ended up off by one on every subtraction and it took me forever to realize I was flipping the bits but forgetting to feed the carry in. Is there a common way people mess that up, because I clearly found at least one of them?

Update: added the overflow check like the first reply said, carries into and out of the sign bit not matching. Worked right away on the -100 plus -100 case. But the $signed tip is the real hero, my display was showing the wrapped positive numbers the whole time. Question though, once I detect overflow should my module still output the wrapped value, or does real hardware usually saturate or something? Also tried replacing my case statement with just a + b after flipping b for subtract and it mostly works, except subtracting from zero gave me a weird result. Still poking at that.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required