1-bit Full Adder — Design & Testbench
Complete 1-bit full adder at four abstraction levels — gate, data flow, behavioral, and half-adder composition — with exhaustive self-checking testbench covering all 8 input combinations.
➕ Introduction & Theory
A full adder is an arithmetic circuit that adds three single-bit inputs: two data bits (a and b) and a carry-in (cin) from a previous stage. It produces two outputs: a sum bit and a carry-out (cout) bit.
Unlike the half adder, the full adder accounts for carry propagation — making it the building block of all multi-bit adders, ALUs, and arithmetic hardware. By chaining full adders in a ripple-carry configuration, any-width binary addition can be performed.
📊 Truth Table & Boolean Equations
With three 1-bit inputs, there are 2³ = 8 possible input combinations. The highlighted rows show cases where cout = 1 — whenever the sum of inputs equals 2 or 3:
| a | b | cin | sum | cout | a+b+cin |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0+0+0 = 0 |
| 0 | 0 | 1 | 1 | 0 | 0+0+1 = 1 |
| 0 | 1 | 0 | 1 | 0 | 0+1+0 = 1 |
| 0 | 1 | 1 | 0 | 1 | 0+1+1 = 10₂ |
| 1 | 0 | 0 | 1 | 0 | 1+0+0 = 1 |
| 1 | 0 | 1 | 0 | 1 | 1+0+1 = 10₂ |
| 1 | 1 | 0 | 0 | 1 | 1+1+0 = 10₂ |
| 1 | 1 | 1 | 1 | 1 | 1+1+1 = 11₂ |
+ (a·cin)
· = AND
+ = OR
Alternate cout form:
cout = (a·b) + cin·(a⊕b)
The sum is the 3-input XOR — it is 1 when an odd number of inputs are 1. The cout is a majority function — it is 1 when at least two of the three inputs are 1.
🔌 Circuit Diagram
🔗 Gate Level Implementation
The gate level model directly instantiates the five primitive gates — two XORs, two ANDs, and one OR — mirroring the circuit diagram exactly.
// ============================================================ // Module : full_adder_gate // Level : Gate Level (Structural) // Inputs : a, b (addends), cin (carry-in) // Outputs : sum = a⊕b⊕cin, cout = (a·b)+(b·cin)+(a·cin) // Gates : 2× XOR, 2× AND, 1× OR (5 gates total) // ============================================================ `timescale 1ns/1ps `default_nettype none module full_adder_gate ( input a, // first addend input b, // second addend input cin, // carry-in from previous stage output sum, // a XOR b XOR cin output cout // majority: 1 when ≥ 2 inputs are 1 ); wire s1; // intermediate: a XOR b wire c1; // intermediate carry: a AND b wire c2; // intermediate carry: s1 AND cin // Stage 1: XOR and AND on primary inputs xor g_xor1 (s1, a, b ); // s1 = a ⊕ b and g_and1 (c1, a, b ); // c1 = a · b // Stage 2: XOR and AND with carry-in xor g_xor2 (sum, s1, cin); // sum = s1 ⊕ cin = a ⊕ b ⊕ cin and g_and2 (c2, s1, cin); // c2 = s1 · cin = (a⊕b) · cin // Output carry: OR of the two partial carries or g_or (cout, c1, c2 ); // cout = c1 + c2 endmodule `default_nettype wire
⚡ Data Flow Implementation
The data flow model condenses the full adder to two assign statements — one per output. The carry-out uses the compact alternative form exploiting the intermediate s1 term.
// ============================================================ // Module : full_adder_dataflow // Level : Data Flow // Note : Intermediate wire s1 avoids re-computing a^b // ============================================================ `timescale 1ns/1ps `default_nettype none module full_adder_dataflow ( input a, b, cin, output sum, cout ); // Intermediate to avoid computing a^b twice wire s1 = a ^ b; // Sum: three-input XOR assign sum = s1 ^ cin; // Carry-out: (a AND b) OR (s1 AND cin) assign cout = (a & b) | (s1 & cin); endmodule `default_nettype wire // ── Alternative: single-line assign using arithmetic ────────── // assign {cout, sum} = a + b + cin; // This is the most concise form — synthesis infers a full adder // The {} concatenation splits the 2-bit sum into cout and sum
assign {cout, sum} = a + b + cin; — a single statement that uses Verilog’s arithmetic operator. The result of adding three 1-bit values is 2 bits wide; the concatenation target {cout, sum} splits it into carry-out and sum. Synthesis tools immediately recognise this as a full adder and map it to the optimal cell.
🧠 Behavioral Implementation
The behavioral model describes the full adder algorithmically using a procedural always @(*) block and an intermediate variable. This style is most readable for complex logic and scales naturally to wider operations.
// ============================================================ // Module : full_adder_behavioral // Level : Behavioral // ============================================================ `timescale 1ns/1ps `default_nettype none module full_adder_behavioral ( input a, b, cin, output reg sum, cout ); always @(*) begin // Option 1: arithmetic (synthesis-friendly) {cout, sum} = a + b + cin; end endmodule `default_nettype wire // ── Alternative behavioral using explicit Boolean expressions ── // always @(*) begin // sum = a ^ b ^ cin; // cout = (a & b) | (b & cin) | (a & cin); // end // ── Alternative using if-else (illustrative, less efficient) ── // always @(*) begin // case ({a, b, cin}) // 3'b000: {cout,sum} = 2'b00; // 3'b001: {cout,sum} = 2'b01; // 3'b010: {cout,sum} = 2'b01; // 3'b011: {cout,sum} = 2'b10; // 3'b100: {cout,sum} = 2'b01; // 3'b101: {cout,sum} = 2'b10; // 3'b110: {cout,sum} = 2'b10; // 3'b111: {cout,sum} = 2'b11; // endcase // end
🧩 Half Adder Composition
A full adder can be constructed from two half adder instances and one OR gate. This demonstrates Verilog’s hierarchical design capability — building a more complex circuit from previously verified components.
// ============================================================ // Module : full_adder_structural // Approach : Hierarchical — instantiates half_adder_gate // Structure: HA1(a,b) → s1,c1 // HA2(s1,cin) → sum,c2 // cout = c1 OR c2 // ============================================================ `timescale 1ns/1ps `default_nettype none module full_adder_structural ( input a, b, cin, output sum, cout ); wire s1; // intermediate sum from HA1 wire c1; // carry from HA1 (a AND b) wire c2; // carry from HA2 ((a XOR b) AND cin) // Half Adder 1: add the two primary inputs half_adder_gate ha1 ( .a (a ), .b (b ), .sum (s1), // s1 = a XOR b .carry(c1) // c1 = a AND b ); // Half Adder 2: add the intermediate sum and carry-in half_adder_gate ha2 ( .a (s1 ), .b (cin), .sum (sum), // final sum = s1 XOR cin = a XOR b XOR cin .carry(c2 ) // c2 = s1 AND cin ); // Output carry: at most ONE of c1 or c2 can be 1 at a time // (proven by the fact that if c1=1 then a=b=1 → s1=0 → c2=0) // Therefore OR is sufficient (no additional logic needed) or g_cout (cout, c1, c2); endmodule `default_nettype wire
🧪 Comprehensive Testbench
The testbench applies all 8 input combinations (exhaustive for 3 inputs), computes expected outputs using a reference model, and reports detailed pass/fail results for each test vector.
// ============================================================ // Testbench : full_adder_tb // DUT : full_adder_gate // Strategy : Exhaustive — all 8 input combinations (2^3) // Reference : arithmetic model {exp_cout,exp_sum} = a+b+cin // ============================================================ `timescale 1ns/1ps `default_nettype none module full_adder_tb; // ── DUT ports ───────────────────────────────────────────── reg a, b, cin; wire sum, cout; // ── Instantiate DUT (swap to test other implementations) ── full_adder_gate dut ( .a (a ), .b (b ), .cin (cin ), .sum (sum ), .cout(cout) ); // ── Waveform dump ────────────────────────────────────────── initial begin $dumpfile("full_adder.vcd"); $dumpvars(0, full_adder_tb); end // ── Test counters ────────────────────────────────────────── integer pass_cnt = 0; integer fail_cnt = 0; integer test_num = 0; // ── Reference model variables ────────────────────────────── reg exp_sum, exp_cout; reg [1:0] ref; // ── Self-checking task ───────────────────────────────────── task apply_and_check; input in_a, in_b, in_cin; begin a = in_a; b = in_b; cin = in_cin; // Compute expected using arithmetic reference model ref = in_a + in_b + in_cin; exp_sum = ref[0]; exp_cout = ref[1]; #1; // propagation settling time test_num = test_num + 1; if (sum === exp_sum && cout === exp_cout) begin $display(" PASS [%0d] a=%b b=%b cin=%b | sum=%b cout=%b", test_num, a, b, cin, sum, cout); pass_cnt++; end else begin $display(" FAIL [%0d] a=%b b=%b cin=%b | got sum=%b cout=%b | exp sum=%b cout=%b", test_num, a, b, cin, sum, cout, exp_sum, exp_cout); fail_cnt++; end end endtask // ── Main stimulus ────────────────────────────────────────── initial begin $display(""); $display("========================================================"); $display(" Full Adder Testbench — Exhaustive 8-Vector Coverage"); $display("========================================================"); $display(" a b cin | sum cout"); $display(" ----------------------------------------"); a=0; b=0; cin=0; #5; // initialise // All 8 combinations: scan a (MSB), b, cin (LSB) apply_and_check(1'b0, 1'b0, 1'b0); #9; // 0+0+0=00 apply_and_check(1'b0, 1'b0, 1'b1); #9; // 0+0+1=01 apply_and_check(1'b0, 1'b1, 1'b0); #9; // 0+1+0=01 apply_and_check(1'b0, 1'b1, 1'b1); #9; // 0+1+1=10 apply_and_check(1'b1, 1'b0, 1'b0); #9; // 1+0+0=01 apply_and_check(1'b1, 1'b0, 1'b1); #9; // 1+0+1=10 apply_and_check(1'b1, 1'b1, 1'b0); #9; // 1+1+0=10 apply_and_check(1'b1, 1'b1, 1'b1); #9; // 1+1+1=11 // ── Summary ──────────────────────────────────────────── $display(" ----------------------------------------"); $display(" Results: %0d / %0d PASS | %0d FAIL", pass_cnt, test_num, fail_cnt); $display("========================================================"); if (fail_cnt == 0) $display(" ✅ ALL TESTS PASSED — Full Adder verified correct"); else $fatal(1, " ❌ %0d TEST(S) FAILED", fail_cnt); #10; $finish; end // ── Continuous monitoring (stable post-NBA values) ───────── initial $monitor(" @%0t a=%b b=%b cin=%b → sum=%b cout=%b", $time, a, b, cin, sum, cout); endmodule `default_nettype wire
📈 Simulation Waveform
The waveform sweeps through all 8 input combinations in binary order. Notice how sum and cout change in response to each input transition — particularly the last vector (1+1+1=11₂) where both outputs are 1.
💻 Simulation Console Output
How to Run
# ── Icarus Verilog ──────────────────────────────────────────── iverilog -o fa_sim \ half_adder_gate.v \ full_adder_gate.v \ full_adder_tb.v vvp fa_sim gtkwave full_adder.vcd # ── Test all implementations ───────────────────────────────── # Edit full_adder_tb.v DUT line: # full_adder_gate → gate level # full_adder_dataflow → data flow # full_adder_behavioral → behavioral # full_adder_structural → half adder composition # ── ModelSim one-liner ──────────────────────────────────────── vlog half_adder_gate.v full_adder_gate.v full_adder_tb.v && \ vsim -c full_adder_tb -do "run -all; quit -f"
🔬 Design Analysis & Extension
| Implementation | Gate count | Code lines | Synthesizable | Best for |
|---|---|---|---|---|
| Gate Level | 5 gates explicit | ~20 | ✅ Yes | Standard cell modelling, post-synthesis netlist |
| Data Flow | Inferred by tool | ~15 | ✅ Yes | RTL design — clearest expression of equations |
| Behavioral | Inferred by tool | ~14 | ✅ Yes | Arithmetic units, best for readability |
| HA Composition | 5 gates (2 HA + OR) | ~25 | ✅ Yes | Demonstrating hierarchy and component reuse |
Fig 4 — Extension: 4-bit Ripple Carry Adder
// ============================================================ // 4-bit ripple carry adder — chains 4 full adder instances // Latency: 4 × full_adder propagation delay (ripple effect) // ============================================================ `timescale 1ns/1ps `default_nettype none module ripple_carry_adder_4bit ( input [3:0] a, b, // 4-bit operands input cin, // initial carry-in (0 for simple add) output [3:0] sum, // 4-bit result output cout // final carry-out (overflow) ); wire c1, c2, c3; // intermediate carries between stages // Bit 0 (LSB): carry-in from port full_adder_gate fa0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1)); // Bit 1: carry-in from bit 0 full_adder_gate fa1 (.a(a[1]), .b(b[1]), .cin(c1), .sum(sum[1]), .cout(c2)); // Bit 2: carry-in from bit 1 full_adder_gate fa2 (.a(a[2]), .b(b[2]), .cin(c2), .sum(sum[2]), .cout(c3)); // Bit 3 (MSB): carry-in from bit 2, carry-out = overflow full_adder_gate fa3 (.a(a[3]), .b(b[3]), .cin(c3), .sum(sum[3]), .cout(cout)); endmodule `default_nettype wire // Behavioral equivalent (1 line — synthesis produces same adder): // assign {cout, sum} = a + b + cin;
full_adder_gate, which in turn uses five gate primitives. This three-level hierarchy (primitives → full adder → 4-bit adder) demonstrates the power of modular, hierarchical Verilog design — each level can be independently verified, then trusted as a correct component at the next level.
