Skip to content
academia.sh

Lesson 12 / 16

The Hardware Implementation of Arithmetic

From logic gates to adder circuits, how subtraction reuses the same circuit, and how status flags are produced.

Contents

The second lesson stated that the add instruction writes a result to register 66, but left open how that result is produced. This lesson fills that gap: what structures in hardware carry out addition?

The answer combines two lessons from the first topic: the AND, OR, and XOR operations defined in the bit-level operations lesson, and the two’s complement representation defined in the signed integers lesson. Brought together, these two build the entire arithmetic unit.

Logic Gates

A logic gate is a physical circuit element that produces one output bit from one or two input bits. Its behavior is identical to the truth tables defined in the previous topic: an AND gate produces 11 only if both inputs are 11, an OR gate if at least one is 11, an XOR gate if the inputs differ.

This also explains the origin of that lesson’s operators: the expression a & b means as many AND gates as the word is wide, all operating at once. This is why bit-level operations are cheap — a single gate suffices per bit, and bits do not wait on each other.

Arithmetic, however, breaks this independence: a digit’s result depends on the carry bit coming from the digits below it.

The Half Adder

Adding two bits is the same as addition done by hand. The operation 1+11 + 1 leaves 00 at that digit and carries 11 to the digit above.

aa bb Sum Carry
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

The sum column is identical to the XOR table, the carry column to the AND table:

sum=ab,carry=ab\text{sum} = a \oplus b, \qquad \text{carry} = a \wedge b

This two-gate circuit is called a half adder. Its name reflects a deficiency: it does not account for the carry coming from the digit below.

The Full Adder

In a real addition, every digit has three inputs: two bits and the carry coming from the digit below. The circuit that processes these three inputs is the full adder:

sum=abcin\text{sum} = a \oplus b \oplus c_{\text{in}}

cout=(ab)(cin(ab))c_{\text{out}} = (a \wedge b) \vee \big(c_{\text{in}} \wedge (a \oplus b)\big)

The carry expression can be read in words: a carry is produced if both bits are 11; or if exactly one bit is 11 and a carry comes from the digit below.

The Ripple-Carry Adder

An nn-bit addition is built by chaining nn full adders: each adder’s carry output connects to the carry input of the adder above it. This arrangement is called a ripple-carry adder.

The chain has a cost: the top digit’s result is not stable until the carry bit has propagated through every digit. The circuit’s delay grows in proportion to the number of bits.

Reducing this delay is a classic problem in digital design. Designs that compute carry bits directly from the input bits, rather than waiting for them digit by digit — carry-lookahead adders — achieve shorter delay by using more gates. This trade-off between area and speed repeats at every layer of hardware design.

Subtraction Needs No Separate Circuit

The signed integers lesson stated that two’s complement is preferred because it leaves the addition circuit unchanged. Its counterpart in the circuit is this:

ab=a+(¬b)+1a - b = a + (\lnot b) + 1

The value ¬b\lnot b is produced by passing bb’s bits through XOR gates; the trailing +1+1 is supplied by feeding 11 into the lowest adder’s carry input. A single control bit thus governs both the inversion and the carry input: at 00 the circuit performs addition, at 11 it performs subtraction.

Comparison operations use the same circuit. Comparing two values means computing their difference and examining the result’s flags; the result itself is not stored.

Status Flags

The arithmetic and logic unit produces, alongside the result, bits that qualify it. Four flags are common:

Flag Condition that sets it
Zero All bits of the result are 00
Sign The result’s leftmost bit is 11
Carry The carry out of the top digit is 11 (unsigned overflow)
Overflow The carry into and out of the top digit differ (signed overflow)

The last row is the hardware definition of signed overflow. When two positive numbers are added, if a carry enters the top digit but does not exit it, the result has overflowed into the sign bit — the value appears negative. This is exactly what happens in the example 127+1127 + 1.

Keeping the carry and overflow flags separate lets the same addition circuit support both the unsigned and signed interpretations. Which flag is meaningful is decided by the program, which knows under which interpretation it is using the value. Conditional branch instructions also read these flags.

Building the Circuit in a Program

The following program builds the full adder and an eight-bit ripple-carry adder using bit-level operators:

def full_adder(a: int, b: int, carry_in: int) -> tuple[int, int]:
    """Produces the sum and carry-out bits from three input bits."""
    total = a ^ b ^ carry_in
    carry_out = (a & b) | (carry_in & (a ^ b))
    return total, carry_out


def add(x: int, y: int, bits: int = 8, subtract: bool = False) -> dict:
    """Ripple-carry adder; if subtract=True, computes x - y."""
    carry = 1 if subtract else 0
    result = 0
    for i in range(bits):
        a = (x >> i) & 1
        b = ((y >> i) & 1) ^ (1 if subtract else 0)   # b is inverted for subtraction
        if i == bits - 1:
            top_carry_in = carry                      # carry entering the top digit
        t, carry = full_adder(a, b, carry)
        result |= t << i
    return {
        "result": result,
        "zero": result == 0,
        "sign": ((result >> (bits - 1)) & 1) == 1,
        "carry": carry == 1,
        "overflow": top_carry_in != carry,
    }


print(add(5, 3))                     # 8, no overflow
print(add(5, 3, subtract=True))      # 2, subtraction in the same circuit
print(add(127, 1)["overflow"])       # True  — signed overflow
print(add(255, 1)["carry"])          # True  — unsigned overflow
print(add(255, 1)["result"])         # 0     — wrapped around

The subtract flag being used in two places at once — both in inverting b’s bits and in the initial carry — is the counterpart of the single control bit in hardware.

The outputs confirm the claims made in the first topic: 255+1255 + 1 wraps around under the unsigned interpretation, 127+1127 + 1 sets the overflow flag under the signed interpretation. That lesson stated that “hardware reports both kinds of overflow through separate flags”; the circuit in this lesson is the very structure that produces those flags.

Summary

  • Logic gates are the physical counterpart of bit-level operators; because every bit is processed independently, these operations are cheap.
  • A half adder adds two bits: the sum is produced by an XOR gate, the carry by an AND gate.
  • A full adder also accounts for the carry from the digit below; chaining nn full adders gives an nn-bit adder.
  • Chaining delay grows with the number of bits; carry-lookahead designs reduce the delay by using more gates.
  • Subtraction needs no separate circuit: the second operand is inverted and 11 is fed into the carry input.
  • Alongside the result, the arithmetic unit produces zero, sign, carry, and overflow flags; carry reports overflow under the unsigned interpretation, overflow under the signed one.

Next Step

This topic has established how data resides in memory and how instructions are executed in hardware. One link remains: how does the text a programmer writes turn into these instructions? The next topic covers the chain that reaches from source code to a running process — the steps of compiling, linking, and loading.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close