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
endmodulePASS: 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
endmodulePASS: 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
| Mistake | Why it breaks |
|---|---|
Making p only N bits wide | An 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 replication | b[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 shifting | Dropping c_row[g] from acc[g+1] loses the overflow of each stage and corrupts the upper product bits. |
Using == instead of !== in checks | With ==, an x on an output compares as unknown rather than failing. !== catches uninitialized or wrong bits outright. |
Expecting it to be fast for large N | The 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.
Share your question in comments or talk to our mentor team for batch guidance.
Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
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