showcase component

Matrix Multiply 4x4 Pipelined (int8)

Open in playground

A 4x4 matrix multiply over int8 values, built as a three-stage pipeline. This is the shape of the workload AI accelerators exist for: quantized neural-network inference multiplies int8 matrices exactly like these, only bigger. Every element of the result needs a four-term dot product, so the full job is 64 multiplies and 48 additions.

The pipeline spends registers to keep every multiplier busy. Stage one captures both matrices from the flattened input ports. Stage two computes all 64 products at once and registers them. Stage three collapses each group of four products through an adder tree into one 20-bit output element. A valid bit rides the same three registers, so out_valid rises exactly three edges after in_valid was accepted. Because each stage holds its own data, a new matrix pair can enter every single cycle.

In the waveform: find the back-to-back test. Two in_valid cycles enter in a row, and two out_valid results leave in a row, three edges behind them. That overlap is the entire argument for pipelining.

In the netlist: the view shows structure, not gate fabric. All 64 multiplier blocks sit in parallel columns feeding the 16 adder trees. Open the design in the playground and synthesize it there to see how much real silicon that structure expands into.

The register discipline that makes this throughput possible is built from first principles in Pipelining and Throughput.

-- 4x4 int8 pipelined matrix multiply: three register stages carry the
-- work - operands in, all 64 products, then 16 four-term adder trees.
-- Element (r,c) of each flattened port lives at slice (r*4+c)*W + W-1
-- downto (r*4+c)*W.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity matmul_4x4 is
  port (
    clk       : in  std_logic;
    rst       : in  std_logic;
    in_valid  : in  std_logic;
    a_flat    : in  std_logic_vector(127 downto 0);
    b_flat    : in  std_logic_vector(127 downto 0);
    out_valid : out std_logic;
    c_flat    : out std_logic_vector(319 downto 0)
  );
end entity matmul_4x4;

architecture rtl of matmul_4x4 is
  signal a_r  : std_logic_vector(127 downto 0);
  signal b_r  : std_logic_vector(127 downto 0);
  signal v1_r : std_logic;
  signal v2_r : std_logic;
  signal v3_r : std_logic;
begin

  -- Stage 1 registers the operands; the valid bit rides the same pipeline
  -- so out_valid rises exactly three edges after in_valid
  stage1_p : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        a_r  <= (others => '0');
        b_r  <= (others => '0');
        v1_r <= '0';
        v2_r <= '0';
        v3_r <= '0';
      else
        a_r  <= a_flat;
        b_r  <= b_flat;
        v1_r <= in_valid;
        v2_r <= v1_r;
        v3_r <= v2_r;
      end if;
    end if;
  end process stage1_p;

  -- One block per output element: stage 2 registers its four products,
  -- stage 3 registers the adder tree. All indices are elaboration-time
  -- constants, so every slice is a fixed wire selection.
  gen_row : for r in 0 to 3 generate
    gen_col : for c in 0 to 3 generate
      signal p0_r  : signed(15 downto 0);
      signal p1_r  : signed(15 downto 0);
      signal p2_r  : signed(15 downto 0);
      signal p3_r  : signed(15 downto 0);
      signal sum_r : signed(19 downto 0);
    begin

      mac_p : process(clk)
      begin
        if rising_edge(clk) then
          if rst = '1' then
            p0_r  <= (others => '0');
            p1_r  <= (others => '0');
            p2_r  <= (others => '0');
            p3_r  <= (others => '0');
            sum_r <= (others => '0');
          else
            p0_r  <= signed(a_r((r*4 + 0)*8 + 7 downto (r*4 + 0)*8))
                   * signed(b_r((0*4 + c)*8 + 7 downto (0*4 + c)*8));
            p1_r  <= signed(a_r((r*4 + 1)*8 + 7 downto (r*4 + 1)*8))
                   * signed(b_r((1*4 + c)*8 + 7 downto (1*4 + c)*8));
            p2_r  <= signed(a_r((r*4 + 2)*8 + 7 downto (r*4 + 2)*8))
                   * signed(b_r((2*4 + c)*8 + 7 downto (2*4 + c)*8));
            p3_r  <= signed(a_r((r*4 + 3)*8 + 7 downto (r*4 + 3)*8))
                   * signed(b_r((3*4 + c)*8 + 7 downto (3*4 + c)*8));
            sum_r <= resize(p0_r, 20) + resize(p1_r, 20)
                   + resize(p2_r, 20) + resize(p3_r, 20);
          end if;
        end if;
      end process mac_p;

      c_flat((r*4 + c)*20 + 19 downto (r*4 + c)*20) <= std_logic_vector(sum_r);

    end generate gen_col;
  end generate gen_row;

  out_valid <= v3_r;

end architecture rtl;
Waveform loading...