Multipliers

Multiplication is repeated addition with shifts, exactly like the long-multiplication you learned in school, but in base 2, where each "digit product" is just an AND. The design computes a * b two ways:

Watch the sequential unit in the waveform: after start, busy rises, the accumulator builds up over 4 clocks, then done pulses with the same answer the combinational multiplier produced instantly.

The width rule: multiplying N-bit by M-bit needs N+M bits, 4x4 -> 8. Truncating a product without thinking is the classic DSP-path bug; decide explicitly which bits you keep (see the fixed-point converter for how Q-formats track this).

Experiment: make the sequential multiplier 8x8, or change it to skip runs of zero bits in b and count how many cycles typical inputs save.

The design

Verilog, design.v
// Combinational (DSP) multiply next to a shift-and-add sequential one.
module multipliers (
    input  wire       clk,
    input  wire       rst,
    input  wire       start,
    input  wire [3:0] a, b,
    output wire [7:0] product_comb,   // instant: uses a DSP block
    output reg  [7:0] product_seq,    // 4 clocks: one adder
    output reg        busy,
    output reg        done
);
    assign product_comb = a * b;

    reg [7:0] acc, addend;
    reg [3:0] multiplier;
    reg [2:0] count;

    always @(posedge clk) begin
        done <= 1'b0;
        if (rst) begin
            busy <= 1'b0;
            product_seq <= 8'd0;
        end else if (start && !busy) begin
            busy       <= 1'b1;
            acc        <= 8'd0;
            addend     <= {4'd0, a};
            multiplier <= b;
            count      <= 3'd0;
        end else if (busy) begin
            if (multiplier[0])
                acc <= acc + addend;
            addend     <= addend << 1;
            multiplier <= multiplier >> 1;
            count      <= count + 3'd1;
            if (count == 3'd3) begin
                busy        <= 1'b0;
                done        <= 1'b1;
                product_seq <= multiplier[0] ? acc + addend : acc;
            end
        end
    end
endmodule
Show the VHDL version
VHDL, design.vhd
-- Combinational multiply next to a shift-and-add sequential one.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity multipliers is
    port (
        clk, rst, start : in  std_logic;
        a, b            : in  unsigned(3 downto 0);
        product_comb    : out unsigned(7 downto 0);
        product_seq     : out unsigned(7 downto 0);
        busy, done      : out std_logic
    );
end entity;

architecture rtl of multipliers is
    signal acc, addend : unsigned(7 downto 0);
    signal m           : unsigned(3 downto 0);
    signal count       : unsigned(2 downto 0);
    signal busy_i      : std_logic := '0';
begin
    product_comb <= a * b;
    busy <= busy_i;

    process (clk) begin
        if rising_edge(clk) then
            done <= '0';
            if rst = '1' then
                busy_i <= '0';
                product_seq <= (others => '0');
            elsif start = '1' and busy_i = '0' then
                busy_i <= '1';
                acc    <= (others => '0');
                addend <= "0000" & a;
                m      <= b;
                count  <= (others => '0');
            elsif busy_i = '1' then
                if m(0) = '1' then
                    acc <= acc + addend;
                end if;
                addend <= shift_left(addend, 1);
                m      <= shift_right(m, 1);
                count  <= count + 1;
                if count = 3 then
                    busy_i <= '0';
                    done   <= '1';
                    if m(0) = '1' then
                        product_seq <= acc + addend;
                    else
                        product_seq <= acc;
                    end if;
                end if;
            end if;
        end if;
    end process;
end architecture;
Show the MyHDL (Python) version
MyHDL, design.py
from myhdl import block, Signal, intbv, modbv, always, always_comb, instance, delay, StopSimulation

@block
def multipliers(clk, rst, start, a, b, product_comb, product_seq, busy, done):
    acc    = Signal(modbv(0)[8:])
    addend = Signal(modbv(0)[8:])
    m      = Signal(intbv(0)[4:])
    count  = Signal(intbv(0)[3:])
    busy_i = Signal(bool(0))

    @always_comb
    def comb():
        product_comb.next = a * b          # instant: a DSP multiply

    @always_comb
    def bw():
        busy.next = busy_i

    @always(clk.posedge)
    def seq():
        done.next = 0
        if rst:
            busy_i.next = 0
            product_seq.next = 0
        elif start and not busy_i:
            busy_i.next = 1
            acc.next = 0
            addend.next = a
            m.next = b
            count.next = 0
        elif busy_i:
            if m[0]:
                acc.next = acc + addend
            addend.next = addend << 1
            m.next = m >> 1
            count.next = count + 1
            if count == 3:
                busy_i.next = 0
                done.next = 1
                if m[0]:
                    product_seq.next = acc + addend
                else:
                    product_seq.next = acc
    return comb, bw, seq

@block
def tb():
    clk = Signal(bool(0)); rst = Signal(bool(1)); start = Signal(bool(0))
    a, b = Signal(intbv(0)[4:]), Signal(intbv(0)[4:])
    product_comb = Signal(intbv(0)[8:]); product_seq = Signal(intbv(0)[8:])
    busy, done = Signal(bool(0)), Signal(bool(0))
    dut = multipliers(clk, rst, start, a, b, product_comb, product_seq, busy, done)

    @always(delay(5))
    def clkgen():
        clk.next = not clk

    @instance
    def stim():
        yield delay(12); rst.next = 0
        for x, y in [(3, 5), (7, 9), (15, 15)]:
            yield clk.negedge; a.next, b.next, start.next = x, y, 1
            yield clk.negedge; start.next = 0
            yield done.posedge
            yield clk.negedge
        yield delay(20); raise StopSimulation
    return dut, clkgen, stim

inst = tb()
inst.config_sim(trace=True)
inst.run_sim()

The testbench

Verilog, tb.v
`timescale 1ns/1ns
module tb;
    reg clk = 0, rst = 1, start = 0;
    reg [3:0] a = 0, b = 0;
    wire [7:0] product_comb, product_seq;
    wire busy, done;

    multipliers dut (.clk(clk), .rst(rst), .start(start), .a(a), .b(b),
                     .product_comb(product_comb), .product_seq(product_seq),
                     .busy(busy), .done(done));

    always #5 clk = ~clk;

    task mul(input [3:0] x, input [3:0] y);
        begin
            @(negedge clk); a = x; b = y; start = 1;
            @(negedge clk); start = 0;
            wait (done); @(negedge clk);
        end
    endtask

    initial begin
        $dumpfile("wave.vcd"); $dumpvars(0, tb);
        #12 rst = 0;
        mul(4'd3,  4'd5);    // 15
        mul(4'd7,  4'd9);    // 63
        mul(4'hF,  4'hF);    // 225: max case
        #20 $finish;
    end
endmodule
Show the VHDL testbench
VHDL, tb.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb is end entity;
architecture sim of tb is
  signal clk : std_logic := '0';
  signal rst : std_logic := '1';
  signal start : std_logic := '0';
  signal a, b : unsigned(3 downto 0) := (others => '0');
  signal product_comb, product_seq : unsigned(7 downto 0);
  signal busy, done : std_logic;
begin
  dut : entity work.multipliers port map (clk=>clk, rst=>rst, start=>start,
        a=>a, b=>b, product_comb=>product_comb, product_seq=>product_seq,
        busy=>busy, done=>done);
  clk <= not clk after 5 ns;
  process
    procedure do_mul(constant x, y : in integer) is
    begin
      wait until falling_edge(clk); a <= to_unsigned(x,4); b <= to_unsigned(y,4); start <= '1';
      wait until falling_edge(clk); start <= '0';
      wait until done = '1';
      wait until falling_edge(clk);
    end procedure;
  begin
    wait for 12 ns; rst <= '0';
    do_mul(3, 5);      -- 15
    do_mul(7, 9);      -- 63
    do_mul(15, 15);    -- 225
    wait for 20 ns; std.env.stop;
  end process;
end architecture;

The MyHDL version keeps the design and its testbench in one design.py.

Simulated waveform

This trace was produced by actually simulating the code above with Icarus Verilog.

21 42 63 84 105 126 147 168 189 t (ns) product_seq[7:0] x 0 F 3F E1 product_comb[7:0] 0 F 3F E1 done busy a[3:0] 0 3 7 F b[3:0] 0 5 9 F clk rst start acc[7:0] x 0 3 F 0 7 3F 0 F 2D 69 E1 addend[7:0] x 3 6 C 18 30 7 E 1C 38 70 F 1E 3C 78 F0 count[2:0] x 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 multiplier[3:0] x 5 2 1 0 9 4 2 1 0 F 7 3 1 0 x[3:0] x 3 7 F y[3:0] x 5 9 F

Try it live

Open this lesson in a playground, edit the code, and re-run it.

Verilog → VHDL → MyHDL →

Put it to work

Tools that apply what this lesson covers.

Cores to explore

Open IP from the registry that builds on this.