Array Multiplier Verilog Code: Parameterized NxN Design

An array multiplier is the most direct way to multiply two unsigned numbers in combinational hardware. It mirrors the pencil-and-paper method you already know: form every partial product with AND gates, then add the shifted rows together with a grid of adders. There is no clock and no state machine, so the full product settles after one propagation delay through the array. This makes it an excellent teaching circuit and the natural first digital multiplier to code.

This tutorial writes clean, synthesizable array multiplier Verilog code for an unsigned N×N multiplier. Everything is parameterized by N, built only from AND gates and full adders, and shipped with a self-checking testbench you can run immediately in Icarus Verilog using iverilog -g2005 -o sim array_multiplier.v && vvp sim.

The multiplication we are building

For inputs a and b, each N bits wide, the product p is 2*N bits wide. Bit i of the partial product row is a & b[i] — a single AND between every bit of a and one bit of b. Row i is then shifted left by i places and all rows are summed. Instead of summing all rows at once, the design below uses a compact shift-and-add structure: it keeps a running accumulator, adds one partial product per stage with a ripple-carry adder, emits one finished product bit per stage, and shifts the accumulator down. After N stages the accumulator holds the upper half of the product.

Full 4×4 array multiplier (design + testbench)

Here is the complete, runnable design. It contains three building blocks — a one-bit full adder, an N-bit ripple-carry adder made from those full adders, and the multiplier that wires everything together — followed by a testbench that checks five products against Verilog’s built-in * operator.

// array_multiplier.v - unsigned NxN array multiplier from AND gates + full adders
module cx_fa (input a, input b, input cin, output s, output cout);
  assign s    = a ^ b ^ cin;
  assign cout = (a & b) | (a & cin) | (b & cin);
endmodule

module cx_rca #(parameter N = 4) (
  input  [N-1:0] a,
  input  [N-1:0] b,
  input          cin,
  output [N-1:0] sum,
  output         cout
);
  wire [N:0] c;
  assign c[0] = cin;
  genvar i;
  generate
    for (i = 0; i < N; i = i + 1) begin : fa_chain
      cx_fa u_fa (.a(a[i]), .b(b[i]), .cin(c[i]), .s(sum[i]), .cout(c[i+1]));
    end
  endgenerate
  assign cout = c[N];
endmodule

module array_multiplier #(parameter N = 4) (
  input  [N-1:0]   a,
  input  [N-1:0]   b,
  output [2*N-1:0] p
);
  // Partial products: pp[i] = a ANDed with the i-th bit of b
  wire [N-1:0] pp [0:N-1];
  genvar g;
  generate
    for (g = 0; g < N; g = g + 1) begin : gen_pp
      assign pp[g] = a & {N{b[g]}};
    end
  endgenerate

  // Shift-and-add accumulation using ripple-carry adders
  wire [N-1:0] acc   [0:N];
  wire [N-1:0] s_row [0:N-1];
  wire         c_row [0:N-1];

  assign acc[0] = {N{1'b0}};

  generate
    for (g = 0; g < N; g = g + 1) begin : gen_rows
      cx_rca #(.N(N)) u_add (
        .a   (acc[g]),
        .b   (pp[g]),
        .cin (1'b0),
        .sum (s_row[g]),
        .cout(c_row[g])
      );
      assign p[g]      = s_row[g][0];
      assign acc[g+1]  = {c_row[g], s_row[g][N-1:1]};
    end
  endgenerate

  assign p[2*N-1:N] = acc[N];
endmodule

module tb_array_multiplier;
  localparam N = 4;
  reg  [N-1:0]   a, b;
  wire [2*N-1:0] p;
  integer errors;

  array_multiplier #(.N(N)) dut (.a(a), .b(b), .p(p));

  task check;
    input [N-1:0] ta;
    input [N-1:0] tb;
    begin
      a = ta; b = tb;
      #5;
      if (p !== (ta * tb)) begin
        $display("FAIL: %0d x %0d = %0d (expected %0d)", ta, tb, p, ta * tb);
        errors = errors + 1;
      end else begin
        $display("PASS: %0d x %0d = %0d", ta, tb, p);
      end
    end
  endtask

  initial begin
    errors = 0;
    check(4'd3,  4'd3);
    check(4'd7,  4'd9);
    check(4'd15, 4'd15);
    check(4'd12, 4'd10);
    check(4'd0,  4'd13);
    if (errors == 0) $display("All 5 tests passed.");
    else             $display("%0d test(s) failed.", errors);
    $finish;
  end
endmodule
PASS: 3 x 3 = 9
PASS: 7 x 9 = 63
PASS: 15 x 15 = 225
PASS: 12 x 10 = 120
PASS: 0 x 13 = 0
All 5 tests passed.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp5je0wpws/array_multiplier.v:95: $finish called at 25 (1s)

How the code works

Partial products from AND gates

The line assign pp[g] = a & {N{b[g]}}; replicates one bit of b across N bits and ANDs it with all of a. If b[g] is 1, row g is a copy of a; if it is 0, the row is all zeros. That single bitwise-AND is exactly the grid of AND gates a textbook draws for the partial-product matrix.

Adders from full adders

Module cx_fa is a one-bit full adder: s = a ^ b ^ cin and cout = (a&b)|(a&cin)|(b&cin). Module cx_rca chains N of them through a carry wire c, giving a ripple-carry adder. Keeping the adder in its own parameterized module is what lets the multiplier scale to any width without rewriting the arithmetic.

Shift, add, and emit one bit per stage

Each stage adds the current accumulator acc[g] to partial product pp[g]. The least significant bit of that sum is a finished product bit, so p[g] = s_row[g][0]. The remaining bits plus the carry out form the next accumulator, acc[g+1] = {c_row[g], s_row[g][N-1:1]} — this is the one-position right shift relative to the growing product. After the loop, acc[N] holds product bits p[2*N-1:N]. The generate blocks unroll this into pure combinational logic, so the whole thing is a single large circuit, not a sequential loop.

Scaling up: the same core as an 8×8 multiplier

Because every module is parameterized, widening the multiplier is just one number. The file below is the identical core instantiated with N = 8, driven by a testbench that throws random vectors and the full-scale corner case 255×255 at it.

// array_mult8.v - same parameterized core, instantiated as 8x8
module cx_fa (input a, input b, input cin, output s, output cout);
  assign s    = a ^ b ^ cin;
  assign cout = (a & b) | (a & cin) | (b & cin);
endmodule

module cx_rca #(parameter N = 4) (
  input  [N-1:0] a,
  input  [N-1:0] b,
  input          cin,
  output [N-1:0] sum,
  output         cout
);
  wire [N:0] c;
  assign c[0] = cin;
  genvar i;
  generate
    for (i = 0; i < N; i = i + 1) begin : fa_chain
      cx_fa u_fa (.a(a[i]), .b(b[i]), .cin(c[i]), .s(sum[i]), .cout(c[i+1]));
    end
  endgenerate
  assign cout = c[N];
endmodule

module array_multiplier #(parameter N = 4) (
  input  [N-1:0]   a,
  input  [N-1:0]   b,
  output [2*N-1:0] p
);
  wire [N-1:0] pp [0:N-1];
  genvar g;
  generate
    for (g = 0; g < N; g = g + 1) begin : gen_pp
      assign pp[g] = a & {N{b[g]}};
    end
  endgenerate

  wire [N-1:0] acc   [0:N];
  wire [N-1:0] s_row [0:N-1];
  wire         c_row [0:N-1];

  assign acc[0] = {N{1'b0}};

  generate
    for (g = 0; g < N; g = g + 1) begin : gen_rows
      cx_rca #(.N(N)) u_add (
        .a(acc[g]), .b(pp[g]), .cin(1'b0),
        .sum(s_row[g]), .cout(c_row[g])
      );
      assign p[g]     = s_row[g][0];
      assign acc[g+1] = {c_row[g], s_row[g][N-1:1]};
    end
  endgenerate

  assign p[2*N-1:N] = acc[N];
endmodule

module tb_array_multiplier;
  localparam N = 8;
  reg  [N-1:0]   a, b;
  wire [2*N-1:0] p;
  integer i, errors;

  array_multiplier #(.N(N)) dut (.a(a), .b(b), .p(p));

  initial begin
    errors = 0;
    for (i = 0; i < 8; i = i + 1) begin
      a = $random;
      b = $random;
      #5;
      if (p !== (a * b)) begin
        $display("FAIL: %0d x %0d = %0d (expected %0d)", a, b, p, a * b);
        errors = errors + 1;
      end else begin
        $display("PASS: %0d x %0d = %0d", a, b, p);
      end
    end
    // Corner case: full-scale inputs
    a = 8'd255; b = 8'd255; #5;
    if (p !== 16'd65025) begin
      $display("FAIL: 255 x 255 = %0d (expected 65025)", p);
      errors = errors + 1;
    end else begin
      $display("PASS: 255 x 255 = %0d", p);
    end
    if (errors == 0) $display("All tests passed.");
    else             $display("%0d test(s) failed.", errors);
    $finish;
  end
endmodule
PASS: 36 x 129 = 4644
PASS: 9 x 99 = 891
PASS: 13 x 141 = 1833
PASS: 101 x 18 = 1818
PASS: 1 x 13 = 13
PASS: 118 x 61 = 7198
PASS: 237 x 140 = 33180
PASS: 249 x 198 = 49302
PASS: 255 x 255 = 65025
All tests passed.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpdswb85a7/array_mult8.v:89: $finish called at 45 (1s)

The only difference from the 4×4 version is localparam N = 8 in the testbench. The multiplier itself was not touched, which is the whole point of parameterization.

The single building block, on its own

If you want to study the atom of this design in isolation, the full adder is just two assignments. This fragment is for reference only, so it is not simulated here.

module cx_fa (input a, input b, input cin, output s, output cout);
  assign s    = a ^ b ^ cin;                       // sum bit
  assign cout = (a & b) | (a & cin) | (b & cin);    // carry out
endmodule

Common mistakes

MistakeWhy it breaks
Making p only N bits wideAn N×N product needs 2*N bits. A narrow output silently truncates large results such as 255×255.
Writing pp[g] = a & b[g] without replicationb[g] is one bit, so this ANDs only a[0]. Use {N{b[g]}} to spread the bit across the whole word.
Forgetting the carry when shiftingDropping c_row[g] from acc[g+1] loses the overflow of each stage and corrupts the upper product bits.
Using == instead of !== in checksWith ==, an x on an output compares as unknown rather than failing. !== catches uninitialized or wrong bits outright.
Expecting it to be fast for large NThe ripple carry means delay grows roughly with N. For wide, high-speed designs you move to carry-save or Wallace-tree reduction.

Quick check

Why is the product 2*N bits wide instead of N?

The largest product of two N-bit numbers is (2^N - 1)^2, which needs up to 2*N bits. For example, two 8-bit values give 255×255 = 65025, a 16-bit result.

What does a & {N{b[g]}} compute?

It replicates bit b[g] into an N-bit word and bitwise-ANDs it with a, producing one partial-product row: a copy of a when b[g] is 1, otherwise all zeros.

Which modules provide the “AND gates and adders” of the title?

The AND gates come from the partial-product line pp[g] = a & {N{b[g]}}; the adders are cx_rca, a ripple-carry adder built from the one-bit full adder cx_fa.

How do you turn the 4×4 design into 16×16?

Change the parameter to N = 16 at instantiation (or the testbench’s localparam). The generate loops rebuild the partial-product grid and adder chain automatically; no other edits are needed.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.

Run it in the Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD