Sklansky Adder in Verilog: Minimum-Depth Prefix Adder

The Sklansky adder is a parallel-prefix (carry-lookahead) adder that reaches the theoretical minimum logic depth of log2N prefix stages. It computes all carries in parallel by combining per-bit generate/propagate signals through a divide-and-conquer prefix tree, trading that shallow depth for high internal fanout. This page gives clean, simulator-verified Sklansky adder Verilog code you can read, run, and adapt.

Below are two self-checking implementations: a concrete 8-bit adder and a width-parameterized version swept over hundreds of deterministic vectors. Each example is a single file containing the design and a testbench named tb, verified under Icarus Verilog in strict Verilog-2005 mode.

An 8-bit Sklansky adder built in three steps: per-bit generate/propagate, a three-stage Sklansky prefix network, and the final sum. The carry-in is folded into the completed prefix so a single expression yields every internal carry.

module sklansky_adder8 (
    input  [7:0] a,
    input  [7:0] b,
    input        cin,
    output reg [7:0] sum,
    output reg       cout
);
    localparam N = 8;
    integer i, stage, step, within, base, partner;
    reg [N-1:0] p0, g0;        // bit-level propagate/generate
    reg [N-1:0] G, P;          // prefix generate/propagate (0..i)
    reg [N-1:0] c;             // carry into bit i

    always @(*) begin
        // 1) bit-level p/g
        for (i = 0; i < N; i = i + 1) begin
            p0[i] = a[i] ^ b[i];
            g0[i] = a[i] & b[i];
        end
        // 2) Sklansky prefix network: log2(N) stages
        G = g0;
        P = p0;
        for (stage = 0; (1 << stage) < N; stage = stage + 1) begin
            step = (1 << stage);
            for (i = 0; i < N; i = i + 1) begin
                within = i % (2*step);
                if (within >= step) begin
                    base    = (i / (2*step)) * (2*step);
                    partner = base + step - 1;     // top of lower half
                    G[i] = G[i] | (P[i] & G[partner]);
                    P[i] = P[i] & P[partner];
                end
            end
        end
        // 3) carries (fold cin into the complete prefix) and sum
        for (i = 0; i < N; i = i + 1) begin
            if (i == 0) c[i] = cin;
            else        c[i] = G[i-1] | (P[i-1] & cin);
        end
        for (i = 0; i < N; i = i + 1)
            sum[i] = p0[i] ^ c[i];
        cout = G[N-1] | (P[N-1] & cin);
    end
endmodule

module tb;
    reg  [7:0] a, b;
    reg        cin;
    wire [7:0] sum;
    wire       cout;
    reg  [8:0] ref;
    integer    k;
    reg  [7:0] av [0:7];
    reg  [7:0] bv [0:7];
    reg        cv [0:7];

    sklansky_adder8 dut (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));

    initial begin
        av[0]=8'd0;   bv[0]=8'd0;   cv[0]=1'b0;
        av[1]=8'd255; bv[1]=8'd1;   cv[1]=1'b0;   // carry ripple all the way
        av[2]=8'd255; bv[2]=8'd255; cv[2]=1'b1;
        av[3]=8'd170; bv[3]=8'd85;  cv[3]=1'b0;   // 0xAA + 0x55
        av[4]=8'd123; bv[4]=8'd77;  cv[4]=1'b1;
        av[5]=8'd200; bv[5]=8'd56;  cv[5]=1'b0;
        av[6]=8'd15;  bv[6]=8'd240; cv[6]=1'b1;
        av[7]=8'd99;  bv[7]=8'd99;  cv[7]=1'b0;

        for (k = 0; k < 8; k = k + 1) begin
            a = av[k]; b = bv[k]; cin = cv[k];
            #1;
            ref = a + b + cin;
            if ({cout, sum} !== ref)
                $display("FAIL a=%0d b=%0d cin=%0d got=%0d exp=%0d", a, b, cin, {cout,sum}, ref);
            else
                $display("PASS a=%0d b=%0d cin=%0d -> sum=%0d cout=%0d", a, b, cin, sum, cout);
        end
        $display("ALL TESTS PASSED");
        $finish;
    end
endmodule
PASS a=0 b=0 cin=0 -> sum=0 cout=0
PASS a=255 b=1 cin=0 -> sum=0 cout=1
PASS a=255 b=255 cin=1 -> sum=255 cout=1
PASS a=170 b=85 cin=0 -> sum=255 cout=0
PASS a=123 b=77 cin=1 -> sum=201 cout=0
PASS a=200 b=56 cin=0 -> sum=0 cout=1
PASS a=15 b=240 cin=1 -> sum=0 cout=1
PASS a=99 b=99 cin=0 -> sum=198 cout=0
ALL TESTS PASSED

The log proves correctness: the testbench adds a + b + cin with Verilog's native + as a golden reference and compares the full {cout, sum} result. It passes the all-ones ripple case (255 + 1), full saturation with carry-in (255 + 255 + 1), and the alternating 0xAA + 0x55 pattern, ending in ALL TESTS PASSED.

The same prefix recurrence generalized to a WIDTH parameter and instantiated at 16 bits. The testbench checks directed corner cases and then sweeps 400 deterministic vectors from an LFSR (no $random), so the result is reproducible on every run.

module sklansky_adder #(
    parameter WIDTH = 16
) (
    input  [WIDTH-1:0] a,
    input  [WIDTH-1:0] b,
    input              cin,
    output reg [WIDTH-1:0] sum,
    output reg             cout
);
    localparam N = WIDTH;
    integer i, stage, step, within, base, partner;
    reg [N-1:0] p0, g0;   // bit-level propagate/generate
    reg [N-1:0] G, P;     // prefix generate/propagate over bits 0..i
    reg [N-1:0] c;        // carry into bit i

    always @(*) begin
        for (i = 0; i < N; i = i + 1) begin
            p0[i] = a[i] ^ b[i];
            g0[i] = a[i] & b[i];
        end
        G = g0;
        P = p0;
        // Sklansky: ceil(log2 N) stages, each fans the lower-half tip out
        // to every bit in the upper half of every 2*step-sized block.
        for (stage = 0; (1 << stage) < N; stage = stage + 1) begin
            step = (1 << stage);
            for (i = 0; i < N; i = i + 1) begin
                within = i % (2*step);
                if (within >= step) begin
                    base    = (i / (2*step)) * (2*step);
                    partner = base + step - 1;
                    G[i] = G[i] | (P[i] & G[partner]);
                    P[i] = P[i] & P[partner];
                end
            end
        end
        for (i = 0; i < N; i = i + 1) begin
            if (i == 0) c[i] = cin;
            else        c[i] = G[i-1] | (P[i-1] & cin);
        end
        for (i = 0; i < N; i = i + 1)
            sum[i] = p0[i] ^ c[i];
        cout = G[N-1] | (P[N-1] & cin);
    end
endmodule

module tb;
    localparam W = 16;
    reg  [W-1:0] a, b;
    reg          cin;
    wire [W-1:0] sum;
    wire         cout;
    reg  [W:0]   ref;
    integer      k, fails;
    reg  [W-1:0] la, lb;   // deterministic LFSR state

    sklansky_adder #(.WIDTH(W)) dut (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));

    // Galois 16-bit LFSR step (taps 0xB400), fully deterministic
    function [15:0] lfsr;
        input [15:0] s;
        begin
            lfsr = (s >> 1) ^ (16'hB400 & {16{s[0]}});
        end
    endfunction

    initial begin
        fails = 0;
        // Directed corner cases first
        check(16'h0000, 16'h0000, 1'b0);
        check(16'hFFFF, 16'h0001, 1'b0);   // full-length carry ripple
        check(16'hFFFF, 16'hFFFF, 1'b1);
        check(16'hAAAA, 16'h5555, 1'b0);
        check(16'h8000, 16'h8000, 1'b0);   // carry out of MSB only
        check(16'h00FF, 16'h0001, 1'b1);
        // Deterministic pseudo-random sweep (no $random)
        la = 16'hACE1; lb = 16'h1234;
        for (k = 0; k < 400; k = k + 1) begin
            check(la, lb, k[0]);
            la = lfsr(la);
            lb = lfsr(lb);
        end
        if (fails == 0) $display("ALL TESTS PASSED (%0d vectors)", 400+6);
        else            $display("FAIL: %0d mismatches", fails);
        $finish;
    end

    task check;
        input [W-1:0] ta, tb;
        input         tc;
        begin
            a = ta; b = tb; cin = tc;
            #1;
            ref = ta + tb + tc;
            if ({cout, sum} !== ref) begin
                $display("FAIL a=%h b=%h cin=%b got=%h exp=%h", ta, tb, tc, {cout,sum}, ref);
                fails = fails + 1;
            end else begin
                if (k < 3 || ta == 16'hFFFF)
                    $display("PASS a=%h b=%h cin=%b -> sum=%h cout=%b", ta, tb, tc, sum, cout);
            end
        end
    endtask
endmodule
PASS a=ffff b=0001 cin=0 -> sum=0000 cout=1
PASS a=ffff b=ffff cin=1 -> sum=ffff cout=1
PASS a=ace1 b=1234 cin=0 -> sum=bf15 cout=0
PASS a=e270 b=091a cin=1 -> sum=eb8b cout=0
PASS a=7138 b=048d cin=0 -> sum=75c5 cout=0
ALL TESTS PASSED (406 vectors)

The log shows 406 vectors (6 directed + 400 LFSR-driven) all matching the a + b + cin golden model across the full 16-bit range, including maximum-length carry propagation (0xFFFF + 1) and MSB carry-out. A single wrong carry at any bit would print a FAIL line; the clean ALL TESTS PASSED confirms the parameterized prefix network is correct.

How the Sklansky prefix network works

Every adder bit first produces two signals: generate g[i] = a[i] & b[i] (this bit makes a carry regardless of the incoming one) and propagate p[i] = a[i] ^ b[i] (this bit passes an incoming carry through). The carry into bit i+1 is the prefix-OR of these signals over bits 0..i.

A prefix network combines (g, p) pairs with the associative operator (g1,p1) o (g2,p2) = (g1 | (p1 & g2), p1 & p2). Sklansky arranges this combination as a balanced binary tree. At stage k (step = 2k), each bit in the upper half of every 2·step-sized block is combined with the single node at the top of that block's lower half. After ⌈log2N⌉ stages every bit holds the complete prefix from bit 0, so G[i] and P[i] describe the group generate/propagate over 0..i and the carries follow directly.

Depth and fanout: Sklansky vs Brent-Kung vs Kogge-Stone

Parallel-prefix adders differ in how they balance three costs: logic depth (stages), wiring/area (cells), and fanout (load on each node). Sklansky sits at the minimum-depth corner.

  • Sklansky — depth log2N, moderate cell count, but high fanout: a single lower-half node can drive up to N/2 upper-half nodes, so the physical delay often needs buffering.
  • Kogge-Stone — also log2N depth and fanout of 2 everywhere, at the cost of the most wires and cells (the widest, most routing-heavy tree).
  • Brent-Kung — fewest cells and low fanout, but roughly 2·log2N depth because of its forward-then-reverse tree.

In short: Sklansky minimizes stages, Brent-Kung minimizes area, Kogge-Stone minimizes fanout, and Han-Carlson is a common hybrid. The ideal logic depth of all three examples here is the same shallow log2N; what distinguishes Sklansky is the fanout penalty you pay to get it.

Common mistakes

  • Forgetting carry-in. A pure prefix tree computes carries assuming cin = 0. Fold cin back in with c[i] = G[i-1] | (P[i-1] & cin) and cout = G[N-1] | (P[N-1] & cin), as shown, rather than treating it as an extra LSB column and reindexing.
  • Wrong partner index. The Sklansky partner is the top of the lower half of the current block (base + step - 1), not i - step. Using a fixed offset gives a Kogge-Stone-like tree, not Sklansky.
  • Overwriting G before P. Both updates read the old P[i]; compute the new G[i] first (it uses old P[i]), then update P[i]. In a procedural block within one stage this ordering is already correct, but be careful if you refactor.
  • Non-power-of-two widths. The loop condition (1 << stage) < N gives ⌈log2N⌉ stages, which is correct for any N, but verify the top partial block when N is not a power of two.

Synthesis notes

The design is written with always @(*) and integer loop variables, so synthesis fully unrolls the stage/bit loops into combinational logic — there is no clock and no state. Each prefix cell maps to a small AND-OR-AND cluster, and a good synthesizer will balance and buffer the high-fanout Sklansky nodes automatically when you constrain timing.

Because the HDL expresses the function (prefix generate/propagate) rather than a hand-placed gate netlist, the tool is free to remap it; if you need the exact Sklansky structure preserved for a custom datapath, instantiate the prefix cells structurally and apply dont_touch/size_only constraints. For most ASIC and FPGA flows, the behavioral form here synthesizes to a fast, area-reasonable adder and is the recommended starting point.

FAQ

Why is the Sklansky adder called a minimum-depth adder?

Its prefix tree has exactly ⌈log2N⌉ stages, which is the information-theoretic minimum number of combination steps needed to compute a prefix over N inputs. No prefix adder can be shallower.

What is the downside of the Sklansky adder?

High fanout. Low-order nodes fan out to up to N/2 nodes in the next stage, so without buffering the real delay grows faster than the stage count suggests, and the worst-case load dominates timing in wide adders.

Is this Verilog synthesizable?

Yes. It is purely combinational RTL using loops that unroll at elaboration; it synthesizes on both ASIC and FPGA flows. The testbench module tb is simulation-only and is not synthesized.

How do I change the width?

Use the parameterized module in Example 2 and override WIDTH at instantiation, e.g. sklansky_adder #(.WIDTH(32)) u_add (...);. The prefix network scales automatically.

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