Algorithm Search⌘K

Kogge-Stone adder in Python

Parallel-prefix adder with logarithmic depth and minimal fan-out-free wiring depth.

File
kogge_stone_adder.py, 114 lines
Level
I0 not checked yet
Toolchain
Python 3.14, pytest 9
Test framework
pytest
Run the tests
python3 -m pytest
kogge_stone_adder.pyRawGitHub
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/kogge-stone-adder/. The authors are listed in prov/PABN90.jsonl.

"""The Kogge-Stone parallel-prefix adder, simulated gate by gate.

Bit lists are least significant bit first. The simulation keeps, for every
signal, its value and its depth (the number of gates on the longest path from
an input), and counts the gates, so that the size and depth of the circuit can
be checked as well as its output.
"""


class Stats:
    def __init__(self) -> None:
        self.gates = 0
        self.cells = 0
        self.levels = 0


def _and(x: tuple[int, int], y: tuple[int, int], st: Stats) -> tuple[int, int]:
    st.gates += 1
    return x[0] & y[0], max(x[1], y[1]) + 1


def _or(x: tuple[int, int], y: tuple[int, int], st: Stats) -> tuple[int, int]:
    st.gates += 1
    return x[0] | y[0], max(x[1], y[1]) + 1


def _xor(x: tuple[int, int], y: tuple[int, int], st: Stats) -> tuple[int, int]:
    st.gates += 1
    return x[0] ^ y[0], max(x[1], y[1]) + 1


def kogge_stone_add(a: list[int], b: list[int], c_in: int = 0, st: Stats | None = None) -> tuple[list[int], int]:
    """Add two n-bit lists; return the n + 1 sum bits (carry out last) and the depth."""
    if st is None:
        st = Stats()
    n = len(a)
    if len(b) != n:
        raise ValueError("the operands must have the same width")
    cin = (c_in & 1, 0)
    if n == 0:
        return [cin[0]], 0
    A = [(x & 1, 0) for x in a]
    B = [(x & 1, 0) for x in b]
    g = [_and(A[i], B[i], st) for i in range(n)]  # @line L1: generate and propagate bits
    p = [_xor(A[i], B[i], st) for i in range(n)]
    g[0] = _or(g[0], _and(p[0], cin, st), st)  # @line L2: fold the carry in into g[0]
    G, P = list(g), list(p)  # @line L3
    d = 1
    while d < n:  # @line L4: about log2 n levels
        newG, newP = list(G), list(P)
        for i in range(d, n):  # @line L5: (G, P)[i] <- (G, P)[i] o (G, P)[i - d], all i at once
            newG[i] = _or(G[i], _and(P[i], G[i - d], st), st)
            if i >= 2 * d:  # P is needed only while the span has not reached position 0
                newP[i] = _and(P[i], P[i - d], st)
            st.cells += 1
        G, P = newG, newP
        st.levels += 1
        d *= 2  # @line L6
    c = [cin] + G  # @line L7: c[i + 1] = G[i], the carry out of position i
    s = [_xor(p[i], c[i], st) for i in range(n)]  # @line L8
    out = s + [c[n]]
    return [x[0] for x in out], max(x[1] for x in out)  # @line L9


def kogge_stone_word(a: int, b: int, n: int, c_in: int = 0) -> int:
    """The same prefix computation with machine-word operations on n-bit integers.

    Each round combines every position i with position i - d at once, as the
    circuit does, by shifting the whole words G and P by d.
    """
    mask = (1 << n) - 1
    a &= mask
    b &= mask
    g = a & b
    p = a ^ b
    G = g | (p & c_in & 1)
    P = p
    d = 1
    while d < n:
        G = G | (P & (G << d))
        P = P & (P << d)
        G &= mask
        P &= mask
        d *= 2
    carries = ((G << 1) | (c_in & 1)) & ((1 << (n + 1)) - 1)
    return (p ^ carries) & mask | (carries >> n) << n


def to_bits(x: int, n: int) -> list[int]:
    return [(x >> i) & 1 for i in range(n)]


def from_bits(s: list[int]) -> int:
    return sum(bit << i for i, bit in enumerate(s))


def add_limbs(a: list[int], b: list[int], w: int = 8, c_in: int = 0) -> list[int]:
    """Add two limb arrays in radix 2**w with one Kogge-Stone adder of their full width.

    Returns len + 1 limbs; the last limb is the carry out (0 or 1).
    """
    n = max(len(a), len(b))
    a = list(a) + [0] * (n - len(a))
    b = list(b) + [0] * (n - len(b))
    abits = [(a[i // w] >> (i % w)) & 1 for i in range(n * w)]
    bbits = [(b[i // w] >> (i % w)) & 1 for i in range(n * w)]
    s, _ = kogge_stone_add(abits, bbits, c_in)
    out = [0] * (n + 1)
    for i, bit in enumerate(s):
        out[i // w] |= bit << (i % w)
    return out
test_kogge_stone_adder.py 82 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/kogge-stone-adder/. The authors are listed in prov/PABN90.jsonl.

import math
import random

import pytest

from kogge_stone_adder import Stats, add_limbs, from_bits, kogge_stone_add, kogge_stone_word, to_bits


def limbs(x: int, w: int, n: int) -> list[int]:
    return [(x >> (w * i)) & ((1 << w) - 1) for i in range(n)]


def value(d: list[int], w: int) -> int:
    v = 0
    for x in reversed(d):
        v = (v << w) | x
    return v


def test_example() -> None:
    s, depth = kogge_stone_add(to_bits(182, 8), to_bits(91, 8))
    assert from_bits(s) == 273
    assert depth == 9
    assert kogge_stone_word(182, 91, 8) == 273


def test_size_and_depth() -> None:
    for n in range(1, 130):
        st = Stats()
        s, depth = kogge_stone_add([1] * n, [0] * n, 1, st)
        assert from_bits(s) == 1 << n
        L = math.ceil(math.log2(n)) if n > 1 else 0
        assert st.levels == L
        assert st.cells == n * L - (1 << L) + 1
        assert depth <= 2 * L + 3
        if n & (n - 1) == 0:
            assert depth == 2 * L + 3


def test_exhaustive_small() -> None:
    for n in (1, 2, 3, 4, 5):
        for x in range(1 << n):
            for y in range(1 << n):
                for c in (0, 1):
                    s, _ = kogge_stone_add(to_bits(x, n), to_bits(y, n), c)
                    assert from_bits(s) == x + y + c
                    assert kogge_stone_word(x, y, n, c) == x + y + c


def test_edges() -> None:
    assert kogge_stone_add([], [], 1)[0] == [1]
    for n in (1, 7, 8, 9, 31, 64):
        top = (1 << n) - 1
        for x, y, c in [(0, 0, 0), (top, 0, 1), (top, 1, 0), (top, top, 1), (top, top, 0), (top >> 1, 1, 0)]:
            s, _ = kogge_stone_add(to_bits(x, n), to_bits(y, n), c)
            assert from_bits(s) == x + y + c
    with pytest.raises(ValueError):
        kogge_stone_add([1, 0], [1], 0)


def test_random_limbs() -> None:
    rng = random.Random(1973)
    for w in (4, 8):
        for _ in range(300):
            n = rng.randint(1, 12)
            x = rng.getrandbits(n * w)
            y = rng.getrandbits(n * w)
            c = rng.randint(0, 1)
            out = add_limbs(limbs(x, w, n), limbs(y, w, n), w, c)
            assert len(out) == n + 1
            assert value(out, w) == x + y + c


def test_random_words() -> None:
    rng = random.Random(7)
    for _ in range(3000):
        n = rng.randint(1, 200)
        x, y, c = rng.getrandbits(n), rng.getrandbits(n), rng.randint(0, 1)
        assert kogge_stone_word(x, y, n, c) == x + y + c

What the next level needs

  • the tests did not run in this build

Read the full page about Kogge-Stone adder