Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
    • Combinational Timing Overview
    • Muxes, Decoders and Encoders
    • Arithmetic Blocks
      • Adder Building Blocks
      • Ripple Carry and Carry Lookahead
      • Shift-Add Multiplier
      • Wallace-Tree Multiplier Intuition
    • Comparators and Shifters
    • Tri-State and Shared Buses
    • Latch Inference
    • Arithmetic Datapaths
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Arithmetic Blocks
  1. Topics
  2. RTL Design
  3. Combinational Design
  4. Arithmetic Blocks
  5. Adder Building Blocks

Part 2 · Combinational · Intermediate

Adder Building Blocks

Half adders, full adders, and one add/subtract unit with carry and overflow flags, proved on all 8192 cases.

On this page

    Why this matters

    The carry flag means unsigned overflow for addition but no borrow for subtraction, and mixing the two conventions gives wrong comparisons. One add/subtract unit shows both.

    After this lesson you can: build a full adder and a ripple adder, turn them into a subtractor by inverting b and setting carry-in, and read carry and overflow flags correctly.

    Half adder and full adder

    Block

    Inputs

    sum

    carry out

    Half adder

    a, b

    a ⊕ b

    a · b

    Full adder

    a, b, cin

    a ⊕ b ⊕ cin

    a·b + cin·(a ⊕ b)

    A full adder adds three bits and reports a two-bit result (cout, sum). Chaining N of them, each cout feeding the next cin, gives an N-bit ripple-carry adder:

    full_adder.v
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    module full_adder (input wire a, b, cin, output wire sum, cout);
      assign sum  = a ^ b ^ cin;
      assign cout = (a & b) | (cin & (a ^ b));
    endmodule
    
    module ripple_adder #(parameter N = 8) (
      input  wire [N-1:0] a, b,
      input  wire         cin,
      output wire [N-1:0] sum,
      output wire         cout
    );
      wire [N:0] c;
      assign c[0] = cin;
      genvar i;
      generate
        for (i = 0; i < N; i = i + 1) begin : stage
          full_adder fa (.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
        end
      endgenerate
      assign cout = c[N];
    endmodule

    Subtraction reuses the adder

    Because −b = ~b + 1, an adder becomes a subtractor if you invert b and set the carry-in to 1. One control bit sub turns the same hardware into an adder or subtractor: y = a + (b ^ {N{sub}}) + sub. The flags follow from the earlier overflow lesson:

    • Carry out (cout) for addition means unsigned overflow. For subtraction it means no borrow (a ≥ b unsigned).

    • Signed overflow uses the effective b operand (after the XOR): operands of the same sign producing a result of the other sign.

    Tip

    In real RTL just write a + b and a - b. The structural versions are for understanding, and for the rare case where you must control carry-in/carry-out. Do not hand-instantiate full adders for production arithmetic: the tool's adder will be faster and smaller.

    Common pitfall

    For subtraction the carry flag is inverted relative to the usual “carry means overflow” idea: cout = 1 means no borrow. Some CPUs invert it again so that C means “borrow”. Check which convention your spec uses before comparing flags.

    Saturating arithmetic clamps on overflow instead of wrapping. On overflow the adder picks the max or min value, using the overflow flag plus an operand sign; DSP blocks use it often, at the cost of one mux after the adder.

    Watch y, cout and ovf as sub switches the unit from add to subtract. The diagram shows hex values, so the bullets give the decimal value in brackets.

    • Slot 1: 3 + 5 = 8, so cout = 0 and ovf = 0.

    • Slot 2: 60 + 7 = 67 needs seven bits, so y = 3 with cout = 1 and ovf = 0.

    • Slot 3: 20 + 20 = 40 (shown as 28) leaves signed range, so ovf = 1 while cout = 0.

    • Slot 4: 20 − 7 = 13 (shown as d) needs no borrow, so cout = 1 and ovf = 0.

    • Slot 5: 5 − 9 borrows, so y wraps to 3c (60) with cout = 0 and ovf = 0.

    Interview check

    Likely question: “How is a full adder built, and how do you reuse it for subtraction?”

    • Full adder is sum = a⊕b⊕cin with cout = majority(a, b, cin).

    • Subtract by inverting b and setting carry-in to 1.

    • Flags fall out: unsigned carry means no borrow on subtract; signed overflow compares effective operand signs.

    Check yourself Inputs a = 1, b = 1, cin = 0. Predict sum and cout from the table before scrolling up.

    sum = 0, cout = 1. Two ones make two (binary 10): the sum bit is 0 with a carry of 1.

    Check yourself Why does adding + sub as the carry-in give two's complement subtraction?

    Inverting b produces ~b = −b − 1. Adding the carry-in of 1 supplies the missing +1, so the sum becomes a + (−b), which is a − b.

    Check yourself Unsigned subtract with a smaller than b, for example 3 − 5. Predict cout.

    cout = 0. A borrow was needed, and for subtraction cout means no borrow, so it reads 0.

    Testbench

    One add/subtract unit proved against references for all 8192 combinations of a, b and sub.

    addsub.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module addsub #(parameter int N = 6) (
      input  logic [N-1:0] a, b,
      input  logic         sub,
      output logic [N-1:0] y,
      output logic         cout, ovf
    );
      logic [N-1:0] b_eff;
      logic [N:0]   sum;
      always_comb begin
        b_eff = b ^ {N{sub}};                       // invert b when subtracting
        sum   = {1'b0, a} + {1'b0, b_eff} + {{N{1'b0}}, sub};
        y     = sum[N-1:0];
        cout  = sum[N];
        ovf   = (a[N-1] == b_eff[N-1]) && (y[N-1] != a[N-1]);
      end
    endmodule
    

    Remember

    • Full adder: sum = a ^ b ^ cin, cout = majority(a, b, cin).

    • Subtract with y = a + (b ^ {N{sub}}) + sub.

    • On subtract, cout = 1 means no borrow. Signed overflow uses the effective b after the XOR.

    Rule of thumb: in production RTL write a + b and a - b; build adders by hand only to understand them.

    How this was checked

    The functional testbenches here use timeunit 1ns and timeprecision 1ps. Each #1 lets zero-delay combinational RTL settle before comparison; it is not a measured 1 ns hardware delay. A mismatch reports the inputs, expected value and actual value; accumulated errors end with $fatal(1, ...), and PASS is printed only on success.

    Keep going

    • Code lab: Build a full adder

    • Interview: run a mock interview on this topic

    Related topics

    • Overflow detection patterns

    • Ripple carry and lookahead

    Previous

    44 / 350

    Next: Ripple Carry and Carry Lookahead

    Up next

    Up nextRipple Carry and Carry Lookahead

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Shift-Add Multiplier
    • Wallace-Tree Multiplier Intuition
    clksuba33c145b571479y8328d3ccoutovf
    One 6-bit unit adding and subtracting, with carry and overflow flags