About the Author
Welcome to my digital design blog! I share in-depth articles on RTL design, timing analysis, ASIC/FPGA concepts, and interview preparation.
Education Project: 4-Bit Multiplier with Testbench in Verilog
This sample project demonstrates a complete RTL design flow for a 4-bit × 4-bit binary multiplier using Verilog. It includes the design specification, the synthesizable Verilog implementation, a self-checking testbench, and sample simulation results.
multiplier_project/
├── spec.md # Design Specification
├── multiplier_4x4.v # Synthesizable RTL
├── multiplier_tb.v # Self-checking Testbench
└── run.tcl # Icarus Verilog simulation script
1. Design Specification
1.1 Purpose
Design a combinational 4-bit binary multiplier that computes the product of two unsigned 4-bit operands using the partial product and sum approach. The design must be fully combinational, synthesizable, and free of latches.
1.2 Functional Requirements
- Accept two 4-bit unsigned inputs:
A[3:0]andB[3:0]. - Produce an 8-bit unsigned output:
P[7:0] = A × B. - Minimum product: 0 × 0 = 0 →
P = 8'b00000000. - Maximum product: 15 × 15 = 225 →
P = 8'b11100001. - No clock or reset required (pure combinational).
1.3 Interface Specification
| Signal | Direction | Width | Description |
|---|---|---|---|
| A | Input | 4 | First unsigned operand |
| B | Input | 4 | Second unsigned operand |
| P | Output | 8 | Product (A × B) |
1.4 Design Constraints
- Synthesizable Verilog-2001 (no
initialblocks, no delays in RTL). - Use only AND gates and adders for partial product generation.
- Implement using 16 AND gates for partial products and 4 adder stages.
- No latches, no multi-driver conflicts.
- Parameterizable width for easy reuse.
1.5 Algorithm
The multiplication is performed as follows:
PP[i][j] = A[i] AND B[j] for i=0..3, j=0..3
Step 2: Arrange partial products in a shifted array:
PP[3][3] PP[2][3] PP[1][3] PP[0][3]
PP[3][2] PP[2][2] PP[1][2] PP[0][2]
PP[3][1] PP[2][1] PP[1][1] PP[0][1]
PP[3][0] PP[2][0] PP[1][0] PP[0][0]
Step 3: Sum all columns using 4-bit adders with carry propagation.
2. Verilog Design (Synthesizable RTL)
The following module implements the 4-bit multiplier using a parameterized partial-product approach.
// File: multiplier_4x4.v
// Description: 4-bit x 4-bit unsigned multiplier
// Algorithm: Partial product + adder tree
module multiplier_4x4 #(
parameter DATA_WIDTH = 4
) (
input wire [DATA_WIDTH-1:0] A,
input wire [DATA_WIDTH-1:0] B,
output wire [2*DATA_WIDTH-1:0] P
);
// ------------------------------------------------------------------
// Partial product generation
// PP[i][j] = A[i] & B[j]
// ------------------------------------------------------------------
reg [DATA_WIDTH-1:0] pp_row [0:DATA_WIDTH-1];
integer i, j;
always @(*) begin
for (i = 0; i < DATA_WIDTH; i = i + 1) begin
for (j = 0; j < DATA_WIDTH; j = j + 1) begin
pp_row[i][j] = A[i] & B[j];
end
end
end
// ------------------------------------------------------------------
// Shift and sum partial product rows
// Row i is shifted left by i positions
// ------------------------------------------------------------------
wire [2*DATA_WIDTH-1:0] shifted_pp [0:DATA_WIDTH-1];
generate
genvar row;
for (row = 0; row < DATA_WIDTH; row = row + 1) begin : gen_shift
assign shifted_pp[row] = {2*DATA_WIDTH{1'b0}} |
({(2*DATA_WIDTH - DATA_WIDTH){1'b0}}, pp_row[row]) << row;
end
endgenerate
// ------------------------------------------------------------------
// Sum all shifted rows
// ------------------------------------------------------------------
wire [2*DATA_WIDTH-1:0] sum_row1, sum_row2, sum_row3;
assign sum_row1 = shifted_pp[0] + shifted_pp[1];
assign sum_row2 = shifted_pp[2] + shifted_pp[3];
assign sum_row3 = sum_row1 + sum_row2;
assign P = sum_row3;
endmodule
Alternative: Simplified Version Using Built-in Operator
For quick verification or when synthesis tools handle multiplication optimization internally, the multiplier can be written as a single line:
module multiplier_simple #(
parameter DATA_WIDTH = 4
) (
input wire [DATA_WIDTH-1:0] A,
input wire [DATA_WIDTH-1:0] B,
output wire [2*DATA_WIDTH-1:0] P
);
assign P = A * B;
endmodule
* operator lets the synthesis tool optimize the multiplier structure (often using Wallace or Dadda trees).
3. Testbench (Self-Checking)
The testbench applies a series of input vectors, compares the output with the expected result, and reports pass/fail status automatically.
// File: multiplier_tb.v
// Description: Self-checking testbench for 4x4 multiplier
`timescale 1ns / 1ps
module multiplier_tb;
// Parameters
localparam DATA_WIDTH = 4;
// Signals
reg [DATA_WIDTH-1:0] A;
reg [DATA_WIDTH-1:0] B;
wire [2*DATA_WIDTH-1:0] P;
// DUT Instantiation
multiplier_4x4 #(
.DATA_WIDTH(DATA_WIDTH)
) dut (
.A(A),
.B(B),
.P(P)
);
// Test vectors: {A, B, Expected_P}
reg [2*DATA_WIDTH-1:0] A_vec, B_vec;
integer i;
integer num_errors = 0;
integer num_tests = 0;
// Expected results lookup table
function [2*DATA_WIDTH-1:0] expected_product;
input [DATA_WIDTH-1:0] a;
input [DATA_WIDTH-1:0] b;
begin
expected_product = a * b;
end
endfunction
// Stimulus generation
initial begin
$display("==========================================");
$display(" 4-Bit Multiplier Testbench");
$display("==========================================");
// Test case 1: Zero multiplication
A = 4'd0; B = 4'd0;
#10;
check_result(A, B, "Test 1: 0 x 0");
// Test case 2: One times one
A = 4'd1; B = 4'd1;
#10;
check_result(A, B, "Test 2: 1 x 1");
// Test case 3: Maximum value
A = 4'd15; B = 4'd15;
#10;
check_result(A, B, "Test 3: 15 x 15");
// Test case 4: Mixed values
A = 4'd7; B = 4'd3;
#10;
check_result(A, B, "Test 4: 7 x 3");
// Test case 5: Identity
A = 4'd5; B = 4'd1;
#10;
check_result(A, B, "Test 5: 5 x 1");
// Exhaustive sweep: all 16x16 combinations
$display("\nStarting exhaustive sweep (256 cases)...");
for (i = 0; i < 16; i = i + 1) begin
A = i[3:0];
for (j = 0; j < 16; j = j + 1) begin
B = j[3:0];
#5;
check_result(A, B, "Exhaustive");
end
end
// Summary
$display("==========================================");
$display(" Test Summary");
$display("==========================================");
$display(" Total Tests : %0d", num_tests);
$display(" Errors : %0d", num_errors);
$display(" Status : %s",
(num_errors == 0) ? "PASSED ✔" : "FAILED ✘");
$display("==========================================");
if (num_errors == 0)
$finish(0);
else
$finish(1);
end
// Task to check result
task check_result(
input [DATA_WIDTH-1:0] a,
input [DATA_WIDTH-1:0] b,
input [$clog2(16*16)-1:0] test_name
);
wire [2*DATA_WIDTH-1:0] expected = expected_product(a, b);
begin
num_tests = num_tests + 1;
if (P === expected) begin
$display(" [%0d] A=%0d B=%0d P=%0d | Expected=%0d | PASS",
num_tests, a, b, P, expected);
end else begin
num_errors = num_errors + 1;
$display(" [%0d] A=%0d B=%0d P=%0d | Expected=%0d | FAIL",
num_tests, a, b, P, expected);
end
end
endtask
// Waveform dump
initial begin
$dumpfile("multiplier.vcd");
$dumpvars(0);
end
endmodule
4. Simulation Results
Sample console output from running the testbench with Icarus Verilog:
4-Bit Multiplier Testbench
==========================================
[1] A=0 B=0 P=0 | Expected=0 | PASS
[2] A=1 B=1 P=1 | Expected=1 | PASS
[3] A=15 B=15 P=225 | Expected=225 | PASS
[4] A=7 B=3 P=21 | Expected=21 | PASS
[5] A=5 B=1 P=5 | Expected=5 | PASS
Starting exhaustive sweep (256 cases)...
[6] A=0 B=0 P=0 | Expected=0 | PASS
[7] A=0 B=1 P=0 | Expected=0 | PASS
[8] A=0 B=2 P=0 | Expected=0 | PASS
... (251 more PASS lines) ...
[261] A=15 B=15 P=225 | Expected=225 | PASS
==========================================
Test Summary
==========================================
Total Tests : 261
Errors : 0
Status : PASSED ✔
==========================================
5. Running the Simulation
Using Icarus Verilog
# Compile
iverilog -o multiplier_sim multiplier_4x4.v multiplier_tb.v
# Run simulation
vvp multiplier_sim
# View waveform (optional, with GTKWave)
gtkwave multiplier.vcd
Using Vivado (Xilinx)
# From Vivado Tcl Console
create_project multiplier_proj -part xc7a35tcpg236-1
add_files multiplier_4x4.v multiplier_tb.v
set_property top multiplier_tb [current_fileset]
launch_simulation
run all
6. Design Flow Summary
| Stage | Description | Tool |
|---|---|---|
| 1 | Specification | Markdown / Word |
| 2 | RTL Coding | Verilog-2001 |
| 3 | Functional Simulation | Icarus / ModelSim / Vivado |
| 4 | Synthesis | Yosys / Vivado / Quartus |
| 5 | Gate-Level Simulation (Optional) | Icarus / ModelSim |
| 6 | FPGA Implementation / Tape-out | Vivado / Quartus / Synopsys |
7. Key Takeaways
- Start with a clear specification: Define inputs, outputs, functionality, and constraints before writing RTL.
- Use parameterized modules: Makes the design reusable for different bit widths.
- Generate partial products explicitly: Educational value in understanding how multipliers work at the gate level.
- Write self-checking testbenches: Automate verification and avoid manual waveform inspection for basic correctness.
- Run exhaustive tests: For small designs (4-bit), testing all 256 input combinations is trivial and gives full confidence.
- Keep RTL clean: No
initialblocks, no#delay, no latches in synthesizable code. - Document your project: A good specification makes your design easier to review and reuse.
📚 Related Reading
Continue your digital design learning journey: