VERILOG DESIGNS · MODULE 19

Verilog Designs — Half Adder with Testbench — VLSI Trainers
Verilog Designs · Module 19

Half Adder — Design & Testbench

Complete half adder implementation at four abstraction levels — gate, data flow, behavioral, and UDP — each paired with a comprehensive self-checking testbench, waveform output, and simulation log.

Introduction & Theory

A half adder is the most fundamental arithmetic circuit in digital design. It adds two single-bit binary numbers and produces two outputs: a sum bit and a carry bit. It is called a “half” adder because it does not accept a carry input — it can only add two bits, not three (for three-bit addition, a full adder is needed).

Two 1-bit Inputs
a and b — each a single binary digit (0 or 1).
📤
Two 1-bit Outputs
sum = a XOR b (the least significant bit of the result). carry = a AND b (overflow into next bit position).
Two Gates
Requires only one XOR gate (for sum) and one AND gate (for carry) — the simplest possible arithmetic circuit.
🧱
Building Block
Used as a component inside full adders, which are then chained to build ripple carry adders and more complex arithmetic units.
Why study the half adder? Despite its simplicity, the half adder illustrates every key concept in Verilog design — module declaration, port types, all four modelling levels (gate, data-flow, behavioral, UDP), and self-checking testbench methodology. It is the standard first circuit for learning HDL design.

📊 Truth Table & Boolean Equations

The half adder’s behaviour is completely described by this truth table. With only two 1-bit inputs, there are exactly four possible input combinations:

absumcarryMeaning
00 00 0+0 = 0, no carry
01 10 0+1 = 1, no carry
10 10 1+0 = 1, no carry
11 01 1+1 = 10₂ (sum=0, carry=1)
Boolean Equations
sum = a ⊕ b
carry = a · b
⊕ = XOR (Exclusive OR)
· = AND (Conjunction)
No simplification possible — already minimal.

Notice the key pattern: sum is 1 whenever exactly one of the inputs is 1 (XOR behaviour), while carry is 1 only when both inputs are 1 (AND behaviour). When both inputs are 1, the binary result is 10₂ — sum bit is 0, carry bit is 1.

🔌 Circuit Diagram

Fig 1 — Half Adder gate-level circuit: XOR gate (sum) + AND gate (carry)
a b XOR AND sum carry sum = a ⊕ b carry = a · b

🔗 Gate Level Implementation

The gate level model directly instantiates primitive gate cells — the most explicit description, mirroring the circuit diagram one-to-one.

1
Gate Level Model
Structural — explicit XOR and AND primitives
🔗 Gate Level
// ============================================================
// Module   : half_adder_gate
// Level    : Gate Level (Structural)
// Function : sum   = a XOR b
//            carry = a AND b
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module half_adder_gate (
  input  a,      // first addend
  input  b,      // second addend
  output sum,    // a XOR b
  output carry   // a AND b
);
  // Instantiate built-in gate primitives
  xor g_sum   (sum,   a, b);  // output first, then inputs
  and g_carry (carry, a, b);
endmodule

`default_nettype wire

Data Flow Implementation

The data flow model uses continuous assign statements — more concise than gate level while remaining synthesizable. The logic equations map directly to assign statements.

2
Data Flow Model
Continuous assignment — assign statements
⚡ Data Flow
// ============================================================
// Module   : half_adder_dataflow
// Level    : Data Flow
// Equations: sum = a ^ b  |  carry = a & b
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module half_adder_dataflow (
  input  a,
  input  b,
  output sum,
  output carry
);
  // Continuous assignments — both active simultaneously
  assign sum   = a ^ b;   // XOR: 1 when inputs differ
  assign carry = a & b;   // AND: 1 only when both are 1
endmodule

`default_nettype wire

🧠 Behavioral Implementation

The behavioral model uses an always @(*) block — procedural combinational logic. The sensitivity list @(*) ensures the block re-evaluates whenever any input changes.

3
Behavioral Model
Procedural — always @(*) combinational block
🧠 Behavioral
// ============================================================
// Module   : half_adder_behavioral
// Level    : Behavioral
// Uses always @(*) for combinational logic description
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module half_adder_behavioral (
  input      a,
  input      b,
  output reg sum,    // reg because driven inside always block
  output reg carry
);
  always @(*) begin   // re-evaluate any time a or b changes
    sum   = a ^ b;   // blocking — combinational intent
    carry = a & b;
  end
endmodule

`default_nettype wire
Why blocking (=) not non-blocking (<=)? Inside always @(*) combinational blocks, use blocking assignments. Non-blocking is for sequential flip-flop logic. Using <= here would still simulate correctly for a half adder but is a style error that confuses readers and tools.

🧬 UDP Implementation

The UDP (User-Defined Primitive) model defines the half adder’s behaviour using truth tables — two separate UDPs, one for each output, then combined in a wrapper module.

4
UDP Model
User-Defined Primitives — truth table based
🧬 UDP
// ============================================================
// UDPs for Half Adder — one UDP per output
// (UDPs can have only one output — so two are needed)
// ============================================================
`timescale 1ns/1ps

// ── UDP 1: XOR for SUM ────────────────────────────────────────
primitive udp_xor (out, a, b);
  output out;
  input  a, b;
  table
  // a  b : out
     0  0 :  0;  // 0⊕0=0
     0  1 :  1;  // 0⊕1=1
     1  0 :  1;  // 1⊕0=1
     1  1 :  0;  // 1⊕1=0
     x  0 :  x;  // unknown a, b=0 → x
     0  x :  x;  // a=0, unknown b → x
     1  x :  x;
     x  1 :  x;
     x  x :  x;
  endtable
endprimitive

// ── UDP 2: AND for CARRY ──────────────────────────────────────
primitive udp_and (out, a, b);
  output out;
  input  a, b;
  table
  // a  b : out
     0  ? :  0;  // any a=0 → carry=0
     ?  0 :  0;  // any b=0 → carry=0
     1  1 :  1;  // both 1 → carry=1
     x  1 :  x;  // unknown a, b=1 → x
     1  x :  x;  // a=1, unknown b → x
  endtable
endprimitive

// ── Wrapper module using both UDPs ────────────────────────────
`default_nettype none
module half_adder_udp (
  input  a, b,
  output sum, carry
);
  udp_xor u_sum   (sum,   a, b);
  udp_and u_carry (carry, a, b);
endmodule
`default_nettype wire

🧪 Comprehensive Testbench

The testbench is a non-synthesizable module that instantiates the DUT (Device Under Test), applies all four possible input combinations, compares outputs against a pre-computed golden reference, and reports pass/fail for each test case. It is designed to be self-checking — no manual waveform inspection needed.

TB
Self-Checking Testbench
Exhaustive coverage · Golden reference comparison · Automatic pass/fail
🧪 Testbench
// ============================================================
// Testbench  : half_adder_tb
// DUT        : half_adder_gate  (swap module name to test others)
// Strategy   : Exhaustive — all 4 input combinations (2^2)
// Checking   : Golden reference table, $fatal on first failure
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module half_adder_tb;

  // ── DUT ports ─────────────────────────────────────────────
  reg  a, b;
  wire sum, carry;

  // ── DUT instantiation (gate level) ────────────────────────
  half_adder_gate dut (
    .a    (a    ),
    .b    (b    ),
    .sum  (sum  ),
    .carry(carry)
  );

  // ── Waveform dump ──────────────────────────────────────────
  initial begin
    $dumpfile("half_adder.vcd");
    $dumpvars(0, half_adder_tb);
  end

  // ── Test tracking ──────────────────────────────────────────
  integer pass_cnt = 0;
  integer fail_cnt = 0;
  integer test_num = 0;

  // ── Self-checking task ─────────────────────────────────────
  task check;
    input exp_sum, exp_carry;
    begin
      test_num = test_num + 1;
      #1;   // allow outputs to settle

      if (sum === exp_sum && carry === exp_carry) begin
        $display("  PASS [%0d]  a=%b b=%b | sum=%b carry=%b",
                 test_num, a, b, sum, carry);
        pass_cnt = pass_cnt + 1;
      end else begin
        $display("  FAIL [%0d]  a=%b b=%b | got sum=%b carry=%b | exp sum=%b carry=%b",
                 test_num, a, b, sum, carry, exp_sum, exp_carry);
        fail_cnt = fail_cnt + 1;
      end
    end
  endtask

  // ── Stimulus and checking ──────────────────────────────────
  initial begin
    $display("");
    $display("================================================");
    $display("  Half Adder Testbench — Exhaustive Verification");
    $display("================================================");
    $display("  Test  a b | sum carry");
    $display("  --------------------------------");

    // Initialise
    a = 1'b0; b = 1'b0; #10;

    // ── Test 1: a=0, b=0 → sum=0, carry=0 ─────────────────
    a = 1'b0; b = 1'b0; check(1'b0, 1'b0);

    // ── Test 2: a=0, b=1 → sum=1, carry=0 ─────────────────
    a = 1'b0; b = 1'b1; check(1'b1, 1'b0);

    // ── Test 3: a=1, b=0 → sum=1, carry=0 ─────────────────
    a = 1'b1; b = 1'b0; check(1'b1, 1'b0);

    // ── Test 4: a=1, b=1 → sum=0, carry=1 ─────────────────
    a = 1'b1; b = 1'b1; check(1'b0, 1'b1);

    // ── Summary ────────────────────────────────────────────
    $display("  --------------------------------");
    $display("  Results: %0d / %0d PASS  |  %0d FAIL",
             pass_cnt, test_num, fail_cnt);
    $display("================================================");
    $display("");

    if (fail_cnt == 0)
      $display("  ✅ ALL TESTS PASSED");
    else
      $fatal(1, "  ❌ %0d TEST(S) FAILED", fail_cnt);

    #10; $finish;
  end

  // ── Continuous monitoring ──────────────────────────────────
  initial
    $monitor("  @%0t  a=%b b=%b sum=%b carry=%b",
             $time, a, b, sum, carry);

endmodule
`default_nettype wire

Testbench Design Decisions

DecisionRationale
Exhaustive stimulus With only 4 input combinations (2²), full exhaustive testing is trivial and complete. No random or directed subset needed.
#1 settle delay Applied inside the check task before sampling outputs. Allows propagation through combinational logic before reading.
=== not == Case equality catches x values — if a bug causes the output to be x, == would evaluate to x (treated as false), but === explicitly checks that expected matches actual including x/z.
$fatal on failure Non-zero exit code signals failure to CI systems. More reliable than manual log inspection for automated regression testing.
$dumpvars VCD file enables post-simulation waveform viewing in GTKWave or any waveform viewer — essential for debugging failures.
$monitor Automatically logs every value change — gives a time-ordered audit trail without manually placing $display everywhere.

📈 Simulation Waveform

The waveform shows all four input combinations applied sequentially. Each transition demonstrates one row of the truth table. The outputs respond immediately (combinational, zero simulation delay).

Fig 2 — Half adder simulation waveform: all four test vectors
a b sum carry t=0 t=11 t=22 t=33 Test 1: 0+0=0 Test 2: 0+1=1 Test 3: 1+0=1 Test 4: 1+1=2 0 0 1 1 0 1 0 1 0 1 1 0 0 1 Inputs (a, b) sum carry

💻 Simulation Console Output

The expected console output when running the testbench — showing the $monitor log, the formatted test results table, and the final pass/fail summary:

================================================ Half Adder Testbench — Exhaustive Verification ================================================ Test a b | sum carry ——————————– @0 a=0 b=0 sum=0 carry=0 <– $monitor fires at t=0 PASS [1] a=0 b=0 | sum=0 carry=0 @11 a=0 b=1 sum=1 carry=0 <– $monitor fires on b transition PASS [2] a=0 b=1 | sum=1 carry=0 @22 a=1 b=0 sum=1 carry=0 PASS [3] a=1 b=0 | sum=1 carry=0 @33 a=1 b=1 sum=0 carry=1 <– both outputs change simultaneously PASS [4] a=1 b=1 | sum=0 carry=1 ——————————– Results: 4 / 4 PASS | 0 FAIL ================================================ ✅ ALL TESTS PASSED

How to Run the Simulation

Command-line simulation flow using common open-source tools
# ── Icarus Verilog (iverilog) — free, open-source ─────────────
iverilog -o half_adder_sim \
    half_adder_gate.v half_adder_tb.v
vvp half_adder_sim
# View waveform:
gtkwave half_adder.vcd

# ── Verilator (fast simulation, C++ backend) ──────────────────
verilator --binary -j 0 half_adder_gate.v half_adder_tb.v \
    --top-module half_adder_tb
./obj_dir/Vhalf_adder_tb

# ── ModelSim / Questa ─────────────────────────────────────────
vlog half_adder_gate.v half_adder_tb.v
vsim -c half_adder_tb -do "run -all; quit"

# ── Cadence Xcelium ───────────────────────────────────────────
xrun half_adder_gate.v half_adder_tb.v \
    -top half_adder_tb -access +rwc

# ── To test other implementations, swap the DUT file:
#    half_adder_dataflow.v   → change module name in TB to half_adder_dataflow
#    half_adder_behavioral.v → change module name in TB to half_adder_behavioral
#    half_adder_udp.v        → change module name in TB to half_adder_udp

🔬 Design Analysis & Comparison

All four implementations are functionally equivalent — they produce identical outputs for all input combinations. The differences lie in style, verbosity, and intended use case:

Implementation Lines of code Synthesizable Simulation speed Best for
Gate Level ~15 ✅ Yes Fastest Standard cell library modelling, post-synthesis netlists
Data Flow ~14 ✅ Yes Fast RTL design — most common style for combinational logic
Behavioral ~17 ✅ Yes Fast Complex combinational logic, algorithmic descriptions
UDP ~40 ⚠️ Depends Fastest Heavily-used primitives in cell libraries needing speed

Extension: Building a Full Adder from Two Half Adders

Fig 3 — Full adder using two half adder instances + one OR gate
// A full adder accepts a carry-in and adds three bits
// Architecture: FA = HA(a,b) → HA(sum1, cin) → carry = c1 OR c2
`timescale 1ns/1ps
`default_nettype none

module full_adder (
  input  a, b, cin,
  output sum, cout
);
  wire sum1, c1, c2;

  // Stage 1: half adder on primary inputs
  half_adder_gate ha1 (.a(a), .b(b), .sum(sum1), .carry(c1));

  // Stage 2: half adder on intermediate sum + carry-in
  half_adder_gate ha2 (.a(sum1), .b(cin), .sum(sum), .carry(c2));

  // Output carry: at most one of c1/c2 can be 1 at a time
  or g_or (cout, c1, c2);
endmodule

`default_nettype wire

// Truth table for reference:
// a  b  cin | sum  cout
// 0  0   0  |  0    0
// 0  0   1  |  1    0
// 0  1   0  |  1    0
// 0  1   1  |  0    1
// 1  0   0  |  1    0
// 1  0   1  |  0    1
// 1  1   0  |  0    1
// 1  1   1  |  1    1   ← 1+1+1 = 3 = 11₂
Design hierarchy in action: The full adder above demonstrates Verilog’s most important feature — hierarchical composition. Two instances of half_adder_gate are connected by internal wires to build a higher-level circuit. This bottom-up approach scales to any complexity: two full adders make a 2-bit adder, four make a 4-bit adder, and so on — each level verified independently before integration.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top