Ripple-Carry Adder in Verilog: Structural Design with Full-Adder Cells
The ripple-carry adder (RCA) is the first real arithmetic block every digital-design student builds: a chain of 1-bit full adders where each carry-out feeds the next stage’s carry-in. This page gives you clean, synthesizable ripple carry adder verilog code written in strict Verilog-2005, parameterized for any width N, and wired with genvar/generate.
We first build the structural adder from full_adder cells, then contrast it with the behavioral one-liner {cout,sum} = a + b + cin and prove, by exhaustive simulation, that the two produce identical results for every input.
1. Structural N-bit ripple-carry adder
A full_adder computes sum = a ^ b ^ cin and cout = ab + b·cin + a·cin. The rca module declares a carry chain c[N:0] with c[0] = cin and instantiates N full adders inside a generate loop, so c[i+1] ripples out of stage i. The self-checking testbench drives a 4-bit and an 8-bit instance and verifies carry-out behavior.
// N-bit Ripple-Carry Adder built from full-adder cells (structural)
module full_adder(input a, input b, input cin, output sum, output cout);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
endmodule
module rca #(parameter N = 4)(input [N-1:0] a, input [N-1:0] b, input cin,
output [N-1:0] sum, output cout);
wire [N:0] c; // carry chain, c[0]=cin ... c[N]=cout
assign c[0] = cin;
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin : fa_chain
full_adder u_fa(.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
end
endgenerate
assign cout = c[N];
endmodule
module tb;
reg [3:0] a4, b4;
reg cin;
wire [3:0] s4;
wire co4;
reg [7:0] a8, b8;
wire [7:0] s8;
wire co8;
rca #(.N(4)) dut4 (.a(a4), .b(b4), .cin(cin), .sum(s4), .cout(co4));
rca #(.N(8)) dut8 (.a(a8), .b(b8), .cin(1'b0), .sum(s8), .cout(co8));
integer errors;
initial begin
errors = 0;
// 9 + 8 = 17 -> 4-bit sum 0001, carry-out 1
a4 = 4'd9; b4 = 4'd8; cin = 1'b0; #1;
$display("4-bit: %0d + %0d + %0d = {cout=%b, sum=%0d}", a4, b4, cin, co4, s4);
if (!(co4 === 1'b1 && s4 === 4'd1)) errors = errors + 1;
// all-ones + 1 = 16 -> sum 0000, carry-out 1
a4 = 4'b1111; b4 = 4'd1; cin = 1'b0; #1;
$display("4-bit: 15 + 1 = {cout=%b, sum=%0d}", co4, s4);
if (!(co4 === 1'b1 && s4 === 4'd0)) errors = errors + 1;
// 7 + 6 + carry-in 1 = 14 -> sum 1110, carry-out 0
a4 = 4'd7; b4 = 4'd6; cin = 1'b1; #1;
$display("4-bit: 7 + 6 + 1 = {cout=%b, sum=%0d}", co4, s4);
if (!(co4 === 1'b0 && s4 === 4'd14)) errors = errors + 1;
// 8-bit: 200 + 100 = 300 -> sum 44 (300-256), carry-out 1
a8 = 8'd200; b8 = 8'd100; #1;
$display("8-bit: 200 + 100 = {cout=%b, sum=%0d}", co8, s8);
if (!(co8 === 1'b1 && s8 === 8'd44)) errors = errors + 1;
if (errors == 0) $display("ALL PASS");
else $display("FAIL: %0d case(s)", errors);
$finish;
end
endmodule
4-bit: 9 + 8 + 0 = {cout=1, sum=1}
4-bit: 15 + 1 = {cout=1, sum=0}
4-bit: 7 + 6 + 1 = {cout=0, sum=14}
8-bit: 200 + 100 = {cout=1, sum=44}
ALL PASS
Simulated with iverilog -g2005. Expected lines include 8-bit: 200 + 100 = {cout=1, sum=44} followed by ALL PASS.
2. Behavioral adder, cross-checked against the structural RCA
In practice you rarely hand-wire full adders — you write assign {cout, sum} = a + b + cin; and let the synthesis tool infer a carry-propagate adder. Making the left-hand side N+1 bits wide captures the carry-out cleanly. This testbench sweeps all 16×16×2 input combinations and asserts the behavioral and structural results are bit-identical.
// Behavioral adder: one-line {cout,sum}=a+b+cin, cross-checked vs structural RCA
module full_adder(input a, input b, input cin, output sum, output cout);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
endmodule
module rca #(parameter N = 4)(input [N-1:0] a, input [N-1:0] b, input cin,
output [N-1:0] sum, output cout);
wire [N:0] c;
assign c[0] = cin;
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin : fa_chain
full_adder u_fa(.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
end
endgenerate
assign cout = c[N];
endmodule
// Behavioral: synthesizer infers a carry-propagate adder from the + operator
module adder_beh #(parameter N = 4)(input [N-1:0] a, input [N-1:0] b, input cin,
output [N-1:0] sum, output cout);
assign {cout, sum} = a + b + cin; // width N+1 on the left captures carry-out
endmodule
module tb;
localparam N = 4;
reg [N-1:0] a, b;
reg cin;
wire [N-1:0] s_s, s_b;
wire co_s, co_b;
rca #(.N(N)) u_struct (.a(a), .b(b), .cin(cin), .sum(s_s), .cout(co_s));
adder_beh #(.N(N)) u_beh (.a(a), .b(b), .cin(cin), .sum(s_b), .cout(co_b));
integer ia, ib, ic, errors;
initial begin
errors = 0;
// Exhaustive deterministic sweep: all 16x16x2 input combinations
for (ia = 0; ia < 16; ia = ia + 1)
for (ib = 0; ib < 16; ib = ib + 1)
for (ic = 0; ic < 2; ic = ic + 1) begin
a = ia[3:0]; b = ib[3:0]; cin = ic[0]; #1;
if ({co_s, s_s} !== {co_b, s_b}) begin
$display("MISMATCH a=%0d b=%0d cin=%0d struct={%b,%0d} beh={%b,%0d}",
a, b, cin, co_s, s_s, co_b, s_b);
errors = errors + 1;
end
end
$display("Checked 512 input combinations; structural vs behavioral mismatches = %0d", errors);
// Show a representative line
a = 4'd9; b = 4'd8; cin = 1'b0; #1;
$display("sample 9+8: struct={%b,%0d} beh={%b,%0d}", co_s, s_s, co_b, s_b);
if (errors == 0) $display("ALL PASS");
else $display("FAIL");
$finish;
end
endmodule
Checked 512 input combinations; structural vs behavioral mismatches = 0
sample 9+8: struct={1,1} beh={1,1}
ALL PASSThe exhaustive sweep prints mismatches = 0 then ALL PASS, proving the gate-level chain and the + operator are functionally equivalent.
How the carry chain works
Each full adder is a purely combinational cell: its sum is the three-input XOR of the operand bits and the incoming carry, and its carry-out is the majority function of those same three bits. The structural rca module stitches N of these together through a single wire [N:0] c. Position c[0] is wired to the external cin, each stage consumes c[i] and produces c[i+1], and the final c[N] becomes cout. Using one declared vector for the chain — rather than many named wires — is what makes the design scale to any width with a generate loop and a genvar index.
Why genvar/generate instead of copy-paste
The generate block elaborates at compile time: for N=8 the tool literally creates eight full_adder instances named fa_chain[0].u_fa through fa_chain[7].u_fa. This keeps the RTL width-independent — change the parameter N (or override it at instantiation, as the testbench does with #(.N(8))) and the hardware grows automatically. The named block label fa_chain is also what lets you reference individual cells in waveforms and assertions, which is invaluable when debugging a carry that fails to propagate.
Timing: the price of simplicity
The ripple-carry adder's weakness is delay. Because stage i cannot compute its correct carry-out until stage i-1 has settled, the worst-case path runs the full length of the chain. Critical-path delay therefore grows linearly with N — roughly N carry-cell delays for an N-bit adder. That is fine for narrow adders or non-critical paths, but for wide, high-speed datapaths designers switch to carry-lookahead, carry-select, or carry-save structures that break the serial dependency. The RCA remains the baseline every faster adder is measured against, and area-wise it is hard to beat: one full adder per bit, no extra logic.
Structural vs behavioral: which to write?
The second example shows the pragmatic truth: in production RTL you almost always write a + b + cin and trust synthesis to pick an optimal adder architecture for your timing constraints. The gate-level chain is a teaching and verification artifact — it makes the carry propagation explicit. Note the width trick: assigning to {cout, sum} where the concatenation is N+1 bits wide forces the addition to be evaluated in N+1-bit precision, so the overflow lands in cout rather than being silently truncated. Writing sum = a + b + cin on an N-bit sum alone would discard the carry.
Common pitfalls
Three bugs catch beginners. First, truncating the carry by forgetting the extra bit on the left-hand side. Second, declaring the carry chain one bit too short — it must be [N:0], not [N-1:0], to hold both cin and cout. Third, in strict Verilog-2005 you must declare genvar and use blocking-style C-for loops with integer loop variables — there is no logic, no always_comb, and no SystemVerilog shortcuts. Keeping to these rules is exactly what lets the code compile under iverilog -g2005.
Interview angle
Expect to be asked to draw the 1-bit full adder and derive sum and cout, then explain why an N-bit RCA has O(N) delay and O(N) area. A strong follow-up answer names carry-lookahead as the O(log N)-delay alternative and explains the generate/propagate (g = a&b, p = a^b) formulation. Being able to both wire the structural version and justify when to drop down to the behavioral + signals that you understand the hardware under the operator — which is the whole point of the question.
Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Run it in the LabLearn 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