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
endmodule
PASS 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 PASS

Note how 15+1 sets cout=1 but ovf=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
endmodule
PASS 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 PASS

How the two's-complement trick works

Subtraction in hardware reuses the adder you already built. To compute a − b you add the two's complement of b, which is (~b) + 1. The sub control line does both jobs at once: b ^ {N{sub}} inverts every bit of b only when sub=1 (XOR with 1 flips, XOR with 0 passes through), and the same sub bit is dropped into the carry-in position of a + b_eff + sub, supplying the "+1". When sub=0 no 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 is b_eff (the possibly-inverted operand that actually entered the adder), not raw b — using raw b is 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 b input. 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. Because full is N+1 bits wide, the concatenation {cout, sum} captures the carry cleanly without a separate wider intermediate. The parameterized version is the preferred style: one parameter N lets 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 computes a + (~b), which is a − b − 1, off by one; (2) wiring overflow from b instead of b_eff; and (3) confusing cout with ovf. A strong interview answer states that for unsigned arithmetic you watch cout, while for signed arithmetic you watch ovf, and that ovf can 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 Lab

Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.

Run it in the Lab

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
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD