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 PASSEDThe log proves correctness: the testbench adds
a + b + cinwith 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 inALL TESTS PASSED.The same prefix recurrence generalized to a
WIDTHparameter 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 endmodulePASS 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 + cingolden 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 aFAILline; the cleanALL TESTS PASSEDconfirms 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 propagatep[i] = a[i] ^ b[i](this bit passes an incoming carry through). The carry into biti+1is 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 stagek(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, soG[i]andP[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. Foldcinback in withc[i] = G[i-1] | (P[i-1] & cin)andcout = 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), noti - 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 newG[i]first (it uses oldP[i]), then updateP[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) < Ngives ⌈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_onlyconstraints. 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
tbis simulation-only and is not synthesized.How do I change the width?
Use the parameterized module in Example 2 and override
WIDTHat 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 LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
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 Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres