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 = 8input 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, andregdeclarations; - 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:
abc
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 & ba & cb & 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
- Compile the DUT and testbench.
- Run the simulation.
- Confirm the pass message.
- Open the VCD and inspect all eight input combinations.
- Temporarily break the DUT by deleting
(a & c). - Re-run the simulation and confirm the testbench fails.
- Restore the correct equation.
- 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.vmatches the DUT instantiation. - Check that every port name in the instantiation is spelled exactly.
- Keep the
#1delay 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 = 32combinations; - compute the expected value in the testbench by counting high bits;
- print
PASS majority5: all 32 cases checkedonly 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.