Sep 25, 2026

Educational Project on Verilog - 4 -bit Multiplier with testbench

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.

📁 Project File Structure:
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] and B[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 initial blocks, 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:

Step 1: Generate 16 partial products:
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
💡 Note: The explicit partial-product version gives you full control over gate-level implementation and is useful for educational purposes. The built-in * 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 initial blocks, 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:

Table of Contents

No comments:

Post a Comment