Loading header...

Exercise: Build Combinational Logic and a Testbench

This exercise builds a small FPGA combinational module and proves it with an exhaustive Verilog testbench. The circuit is intentionally simple so the verification habit is easy to see: define expected behavior, run every case, and make failures useful.

Learning Objectives

After completing this exercise, you will be able to:

  • implement a 3-input majority function in synthesizable Verilog;
  • write a self-checking testbench;
  • run all 2^3 = 8 input combinations;
  • inspect a waveform without using it as the only proof;
  • recognize common failure symptoms and debug them methodically.

Prerequisites

You should already know:

  • Boolean AND, OR, and truth tables;
  • Verilog module, input, output, wire, and reg declarations;
  • continuous assignments with assign;
  • how to run at least one simulator, such as Icarus Verilog, Verilator, Vivado Simulator, ModelSim, Questa, or another equivalent tool.

No FPGA board is required. This exercise is simulation-first.

Concrete Task

Build a module named majority3.

Inputs:

  • a
  • b
  • c

Output:

  • y, high when at least two inputs are high.

Required truth table:

a b c y
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1

Design Reasoning

The output is high when any pair of inputs is high:

  • a & b
  • a & c
  • b & c

The Boolean equation is:

y = (a and b) or (a and c) or (b and c)

In Verilog:

assign y = (a & b) | (a & c) | (b & c);

This maps naturally to LUT logic in an FPGA.

Implementation

Create majority3.v:

module majority3 (
    input  wire a,
    input  wire b,
    input  wire c,
    output wire y
);
    assign y = (a & b) | (a & c) | (b & c);
endmodule

Self-Checking Testbench

Create tb_majority3.v:

`timescale 1ns/1ps

module tb_majority3;
    reg a;
    reg b;
    reg c;
    wire y;

    integer i;
    reg expected;

    majority3 dut (
        .a(a),
        .b(b),
        .c(c),
        .y(y)
    );

    initial begin
        $dumpfile("majority3.vcd");
        $dumpvars(0, tb_majority3);

        for (i = 0; i < 8; i = i + 1) begin
            {a, b, c} = i[2:0];
            #1;

            expected = ((a + b + c) >= 2);

            if (y !== expected) begin
                $display("FAIL i=%0d a=%0b b=%0b c=%0b y=%0b expected=%0b",
                         i, a, b, c, y, expected);
                $finish;
            end
        end

        $display("PASS majority3: all 8 cases checked");
        $finish;
    end
endmodule

The !== operator is deliberate. It catches x and z values that ordinary != comparisons can hide.

Build and Run

With Icarus Verilog:

iverilog -g2012 -o tb_majority3 tb_majority3.v majority3.v
vvp tb_majority3

Expected console output:

PASS majority3: all 8 cases checked

Open majority3.vcd in GTKWave or another waveform viewer if you want to inspect the transitions.

Expected Behavior

The waveform below is conceptual, matching the exhaustive sequence used by the testbench. The simulator output is the authority; the waveform is a visual debugging aid.

title "Exhaustive majority3 input sweep"
time start=0 end=8 unit=ns divisions=8

A: square label="a" low=0 high=1 duty=50 cycles=1 unit=logic color=#2563eb
B: square label="b" low=0 high=1 duty=50 cycles=2 unit=logic color=#7c3aed
C: square label="c" low=0 high=1 duty=50 cycles=4 unit=logic color=#dc2626

marker Y3 at=3 label="011 high"
marker Y5 at=5 label="101 high"
marker Y6 at=6 label="110 high"
marker Y7 at=7 label="111 high"
marker DONE at=8 label="8 cases"

For a real VCD, the exact display depends on the waveform viewer and signal ordering.

Verification Steps

  1. Compile the DUT and testbench.
  2. Run the simulation.
  3. Confirm the pass message.
  4. Open the VCD and inspect all eight input combinations.
  5. Temporarily break the DUT by deleting (a & c).
  6. Re-run the simulation and confirm the testbench fails.
  7. Restore the correct equation.
  8. Run lint or simulator warnings and resolve any unexpected warning.

Common Failure Symptoms

Symptom Likely cause First check
y is always 0 output not connected or expression always false DUT port map
fails for 101 missing (a & c) term majority equation
fails for 011 missing (b & c) term majority equation
fails for 110 missing (a & b) term majority equation
y is x uninitialized or multiply driven signal declarations and connections
compile error near i[2:0] simulator using old Verilog mode enable Verilog 2001 or 2012

Debugging Guidance

  • Print the case index and all signal values, as shown in the testbench.
  • Check that the module name in majority3.v matches the DUT instantiation.
  • Check that every port name in the instantiation is spelled exactly.
  • Keep the #1 delay after changing inputs so combinational updates settle in simulation time.
  • Use a waveform only after the self-checking assertion tells you which case failed.
  • If a simulator does not accept i[2:0], assign through a temporary 3-bit register instead.

Extension Challenge

Extend the design to a 5-input majority function named majority5. The output should be high when at least three inputs are high.

Requirements:

  • use input wire [4:0] x;
  • use output wire y;
  • test all 2^5 = 32 combinations;
  • compute the expected value in the testbench by counting high bits;
  • print PASS majority5: all 32 cases checked only after all combinations pass.

Concise Explained Solution

The three-input majority function is true when any two-input pair is true. The expression (a & b) | (a & c) | (b & c) covers all three pairs. If all three inputs are high, all three product terms are high. If zero or one input is high, no pair term can be high.

The testbench loops from 0 to 7, assigns the loop index bits to {a, b, c}, waits one time unit, computes the expected result, and stops immediately on a mismatch. Because every possible input combination is checked, this is an exhaustive proof for the combinational truth table.

Safety and Lab Notes

This exercise does not drive external hardware. If you later put the module on a board, constrain pins correctly, avoid shorting output pins together, and use a current-limited LED resistor when observing an output with an LED.

Summary

A tiny combinational module is enough to learn a professional FPGA habit: write the intended behavior, verify it with a self-checking testbench, and make the testbench fail when the design is wrong. Exhaustive tests are practical for small input spaces and catch mistakes long before hardware testing.

Next: Sequential Logic in Verilog.

Further Reading

  • Icarus Verilog user guide for compiling and running simulations.
  • Verilator documentation for linting and C++-based simulation.
  • GTKWave documentation for reading VCD waveform files.
  • Yosys manual for how Verilog logic is synthesized into generic gates.

Mind Map

mindmap root((Majority3 Exercise)) Core concept y high for two or more ones Combinational DUT Self checking testbench Exhaustive input sweep Applications Voting logic Fault masking basics Truth table practice Small module verification Calculations Cases equals two to three Eight total cases expected equals sum bits ge two Majority5 has thirty two cases Design rules Use assign for equation Wait after input change Compare with !== Dump VCD for debug Practical checks PASS message required Break one term to test checker Inspect failed vector Check port names Common mistakes Missing pair term No settle delay Wrong module name Treating waveform as proof Hiding x values