Adder/Subtractor in Verilog: Two’s Complement Design with Overflow Detection
A controlled adder/subtractor is one of the most-asked building blocks in digital design interviews because it packs so much into a few gates: binary addition, two’s-complement subtraction, carry-out, and the subtle difference between carry and signed overflow. This page delivers working adder subtractor verilog code that you can compile and simulate immediately.
The trick is a single control line, sub. When sub=0 the circuit computes a + b; when sub=1 it computes a − b by feeding the inverted b into the adder along with a carry-in of 1 — exactly the definition of the two’s complement, since −b = (~b) + 1.
4-bit controlled adder/subtractor
One XOR bank conditionally inverts b, and the same sub bit becomes the carry-in. The self-checking testbench drives both additions and subtractions, including two deliberate signed-overflow cases (7+1 and −8−1).
// 4-bit controlled adder/subtractor using two's complement
module adder_subtractor(
input [3:0] a,
input [3:0] b,
input sub, // 0 = add, 1 = subtract
output [3:0] sum,
output cout,
output ovf
);
wire [3:0] b_xor = b ^ {4{sub}}; // invert b when subtracting
wire [4:0] full = a + b_xor + sub; // +1 completes two's complement
assign {cout, sum} = full;
// signed overflow: operands same sign, result differs
assign ovf = (a[3] == b_xor[3]) && (sum[3] != a[3]);
endmodule
module tb;
reg [3:0] a, b;
reg sub;
wire [3:0] sum;
wire cout, ovf;
integer errors = 0;
adder_subtractor dut(.a(a), .b(b), .sub(sub), .sum(sum), .cout(cout), .ovf(ovf));
task check;
input [3:0] ea; input [3:0] eb; input esub;
input [3:0] esum; input ecout; input eovf;
begin
a = ea; b = eb; sub = esub; #1;
if (sum === esum && cout === ecout && ovf === eovf)
$display("PASS op=%s a=%0d b=%0d -> sum=%0d cout=%b ovf=%b",
esub? "SUB":"ADD", ea, eb, sum, cout, ovf);
else begin
$display("FAIL op=%s a=%0d b=%0d -> sum=%0d(exp %0d) cout=%b(exp %b) ovf=%b(exp %b)",
esub? "SUB":"ADD", ea, eb, sum, esum, cout, ecout, ovf, eovf);
errors = errors + 1;
end
end
endtask
initial begin
// ADD cases
check(4'd3, 4'd4, 1'b0, 4'd7, 1'b0, 1'b0); // 3+4=7
check(4'd7, 4'd1, 1'b0, 4'd8, 1'b0, 1'b1); // 7+1 -> -8 signed overflow
check(4'd15,4'd1, 1'b0, 4'd0, 1'b1, 1'b0); // carry out, no signed ovf (-1+1=0)
// SUB cases (a - b)
check(4'd5, 4'd3, 1'b1, 4'd2, 1'b1, 1'b0); // 5-3=2
check(4'd3, 4'd5, 1'b1, 4'd14, 1'b0, 1'b0); // 3-5=-2 (14 unsigned), no ovf
check(4'd8, 4'd1, 1'b1, 4'd7, 1'b1, 1'b1); // -8-1 -> +7 signed overflow
if (errors == 0) $display("ALL PASS");
else $display("TESTS FAILED: %0d", errors);
$finish;
end
endmodulePASS op=ADD a=3 b=4 -> sum=7 cout=0 ovf=0 PASS op=ADD a=7 b=1 -> sum=8 cout=0 ovf=1 PASS op=ADD a=15 b=1 -> sum=0 cout=1 ovf=0 PASS op=SUB a=5 b=3 -> sum=2 cout=1 ovf=0 PASS op=SUB a=3 b=5 -> sum=14 cout=0 ovf=0 PASS op=SUB a=8 b=1 -> sum=7 cout=1 ovf=1 ALL PASSNote how 15+1 sets
cout=1butovf=0: unsigned it overflows the 4-bit range, but signed it is just (−1)+1=0. That gap is the whole point of the example.N-bit parameterized version
The same logic scales to any width with a single
parameter N. Here it is elaborated at 8 bits, with a testbench that exercises the 100+28 and −128−1 overflow corners.// N-bit parameterized controlled adder/subtractor module addsub_n #(parameter N = 8) ( input [N-1:0] a, input [N-1:0] b, input sub, output [N-1:0] sum, output cout, output ovf ); wire [N-1:0] b_eff = b ^ {N{sub}}; wire [N:0] full = a + b_eff + sub; assign {cout, sum} = full; assign ovf = (a[N-1] == b_eff[N-1]) && (sum[N-1] != a[N-1]); endmodule module tb; localparam N = 8; reg [N-1:0] a, b; reg sub; wire [N-1:0] sum; wire cout, ovf; integer errors = 0; addsub_n #(.N(N)) dut(.a(a), .b(b), .sub(sub), .sum(sum), .cout(cout), .ovf(ovf)); task check; input [N-1:0] ea; input [N-1:0] eb; input esub; input [N-1:0] esum; input eovf; begin a = ea; b = eb; sub = esub; #1; if (sum === esum && ovf === eovf) $display("PASS %s a=%0d b=%0d -> sum=%0d ovf=%b", esub?"SUB":"ADD", ea, eb, sum, ovf); else begin $display("FAIL %s a=%0d b=%0d -> sum=%0d(exp %0d) ovf=%b(exp %b)", esub?"SUB":"ADD", ea, eb, sum, esum, ovf, eovf); errors = errors + 1; end end endtask initial begin check(8'd100, 8'd27, 1'b0, 8'd127, 1'b0); // 100+27=127, max pos, no ovf check(8'd100, 8'd28, 1'b0, 8'd128, 1'b1); // 100+28=128 -> -128 signed ovf check(8'd50, 8'd80, 1'b1, 8'hE2, 1'b0); // 50-80=-30 = 226 check(8'd128, 8'd1, 1'b1, 8'd127, 1'b1); // -128-1 -> +127 signed ovf if (errors == 0) $display("ALL PASS"); else $display("TESTS FAILED: %0d", errors); $finish; end endmodulePASS ADD a=100 b=27 -> sum=127 ovf=0
PASS ADD a=100 b=28 -> sum=128 ovf=1
PASS SUB a=50 b=80 -> sum=226 ovf=0
PASS SUB a=128 b=1 -> sum=127 ovf=1
ALL PASSHow the two's-complement trick works
Subtraction in hardware reuses the adder you already built. To compute
a − byou add the two's complement ofb, which is(~b) + 1. Thesubcontrol line does both jobs at once:b ^ {N{sub}}inverts every bit ofbonly whensub=1(XOR with 1 flips, XOR with 0 passes through), and the samesubbit is dropped into the carry-in position ofa + b_eff + sub, supplying the "+1". Whensub=0no bits flip and the carry-in is 0, so the circuit is a plain adder. One XOR per bit plus one shared adder gives you both operations.Carry-out versus signed overflow
These two flags answer different questions and interviewers love to probe the difference. Carry-out (
cout) is the bit that falls off the top of an unsigned addition — it tells you the true result needed one more bit than the word provides. Signed overflow (ovf) tells you the two's-complement result wrapped past the representable range (for 4 bits, outside −8..+7). The classic rule used here is: overflow occurs when the two operands have the same sign but the result has the opposite sign, i.e.(a[N−1] == b_eff[N−1]) && (sum[N−1] != a[N−1]). Note it isb_eff(the possibly-inverted operand that actually entered the adder), not rawb— using rawbis the single most common bug in this circuit.Synthesis notes
This code is fully combinational and synthesizes to a ripple-carry adder with a row of XOR gates on the
binput. Synthesis tools will typically re-map the+operator to a carry-lookahead or carry-select structure for timing, so you rarely hand-write the carry chain. BecausefullisN+1bits wide, the concatenation{cout, sum}captures the carry cleanly without a separate wider intermediate. The parameterized version is the preferred style: oneparameter Nlets the same module serve an 8-bit ALU slice or a 32-bit datapath with no code duplication.Common pitfalls and the interview angle
Three mistakes recur: (1) forgetting the carry-in "+1" and only inverting
b— that computesa + (~b), which isa − b − 1, off by one; (2) wiring overflow frombinstead ofb_eff; and (3) confusingcoutwithovf. A strong interview answer states that for unsigned arithmetic you watchcout, while for signed arithmetic you watchovf, and thatovfcan equivalently be computed as the XOR of the carry-into and carry-out-of the most-significant bit. Being able to sketch both the XOR-bank datapath and the overflow logic in under two minutes is a reliable signal of solid digital-design fundamentals.Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres