**Design process**

### This project is aimed to develop and implement a synthesizable matrix multiplier core, which is able to perform matrix calculation for matrixes with the size of 32x32. Each component of the matrixes is 16-bit unsigned integer. The core is implemented on Xilinx FPGA Spartan-6 XC6SLX45-CSG324-3. Both behavior and post-route verification are completed. The simulated result is accurately compared to Matlab implementation result.

Block diagram of the design core

The design core is based on the reference design of matrix addition, which input and output buffers are generated by Xilinx Core Generator to save input and output data. The main work is the block to calculate matrix multiplication. Based on the theory of matrix multiplication, the matrix multiplication is done by the following equation:

To calculate cij , during the state of “stReadBufferAB”, we have to read out all 32 columns of row i and 32 the rows of column j, and do multiplication and accumulation like the equation. Furthermore, we add one more state “stSaveData” to the FSM of the reference core which is aimed to save the accumulated data before going to the next state of “stWriteBufferC”.

Block interface of the design core

**VHDL top level code for the matrix multiplier:**

```
--
-- IntMatMulCore.vhd
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- Required entity declaration
entity IntMatMulCore is
port(
Reset, Clock, WriteEnable, BufferSel: in std_logic;
WriteAddress: in std_logic_vector (9 downto 0);
WriteData: in std_logic_vector (15 downto 0);
ReadAddress: in std_logic_vector (9 downto 0);
ReadEnable: in std_logic;
ReadData: out std_logic_vector (63 downto 0);
DataReady: out std_logic
);
end IntMatMulCore;
architecture IntMatMulCore_arch of IntMatMulCore is
COMPONENT dpram1024x16
PORT (
clka : IN STD_LOGIC;
wea : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
addra : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
dina : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
clkb : IN STD_LOGIC;
enb : IN STD_LOGIC;
addrb : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
doutb : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
);
END COMPONENT;
COMPONENT dpram1024x64
PORT (
clka : IN STD_LOGIC;
wea : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
addra : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
dina : IN STD_LOGIC_VECTOR(63 DOWNTO 0);
clkb : IN STD_LOGIC;
enb : IN STD_LOGIC;
addrb : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
doutb : OUT STD_LOGIC_VECTOR(63 DOWNTO 0)
);
END COMPONENT;
type stateType is (stIdle, stWriteBufferA, stWriteBufferB, stReadBufferAB, stSaveData, stWriteBufferC, stComplete);
signal presState: stateType;
signal nextState: stateType;
signal iReadEnableAB, iCountReset,iCountEnable, iCountEnableAB,iCountResetAB: std_logic;
signal iWriteEnableA, iWriteEnableB, iWriteEnableC: std_logic_vector(0 downto 0);
signal iReadDataA, iReadDataB: std_logic_vector (15 downto 0);
signal iWriteDataC: std_logic_vector (63 downto 0);
signal iCount, iReadAddrA, iReadAddrB,iRowA : unsigned(9 downto 0);
signal CountAT,CountBT:unsigned(9 downto 0);
signal iColB:unsigned(19 downto 0);
signal irow,icol,iCountA,iCountB: unsigned(4 downto 0);
signal iCountEnableAB_d1,iCountEnableAB_d2,iCountEnableAB_d3: std_logic;
begin
--Write Enable for RAM A
iWriteEnableA(0) <= WriteEnable and BufferSel;
--Write Enable for RAM B
iWriteEnableB(0) <= WriteEnable and (not BufferSel);
--Input Buffer A Instance
InputBufferA : dpram1024x16
PORT MAP (
clka => Clock,
wea => iWriteEnableA,
addra => WriteAddress,
dina => WriteData,
clkb => Clock,
enb => iReadEnableAB,
addrb => std_logic_vector(iReadAddrA),
doutb => iReadDataA
);
InputBufferB : dpram1024x16
PORT MAP (
clka => Clock,
wea => iWriteEnableB,
addra => WriteAddress,
dina => WriteData,
clkb => Clock,
enb => iReadEnableAB,
addrb => std_logic_vector(iReadAddrB),
doutb => iReadDataB
);
OutputBufferC : dpram1024x64
PORT MAP (
clka => Clock,
wea => iWriteEnableC,
addra => std_logic_vector(iCount),
dina => iWriteDataC,
clkb => Clock,
enb => ReadEnable,
addrb => ReadAddress,
doutb => ReadData
);
process(Clock,Reset)
begin
if(rising_edge(Clock)) then
if(Reset='1') then
iCountEnableAB_d1 <= '0';
iCountEnableAB_d2 <= '0';
else
iCountEnableAB_d1 <= iCountEnable;
iCountEnableAB_d2 <= iCountEnableAB_d1;
end if;
end if;
end process;
iCountEnableAB_d3 <= (not iCountEnableAB_d2) AND iCountEnableAB_d1 ;
process (Clock)
begin
if rising_edge (Clock) then
if(Reset='1') then
iWriteDataC <= (others => '0');
elsif(iWriteEnableC(0)='1') then
iWriteDataC <= (others => '0');
elsif(iCountEnableAB_d3='1') then
iWriteDataC <= (others => '0');
elsif(iReadEnableAB='1') then
iWriteDataC <= iWriteDataC + std_logic_vector(signed(iReadDataA(15)&iReadDataA)*signed(iReadDataB(15)&iReadDataB));
end if;
end if;
end process;
process (Clock)
begin
if rising_edge (Clock) then
if Reset = '1' then
presState <= stIdle;
iCountA <= (others=>'0');
iCountB <= (others=>'0');
else
presState <= nextState;
if iCountResetAB = '1' then
iCountA <= (others=>'0');
iCountB <= (others=>'0');
elsif iCountEnableAB = '1' then
iCountA <= iCountA + 1;
iCountB <= iCountB + 1;
end if;
end if;
if iCountReset = '1' then
iCount <= (others=>'0');
elsif iCountEnable = '1' then
iCount <= iCount + 1;
end if;
end if;
end process;
iRowA <= iCount srl 5;
iColB <= ("0000000000"&iCount) - iRowA*32;
irow <= iRowA(4 downto 0);
icol <= iColB(4 downto 0);
CountAT <= "00000"&iCountA;
CountBT <= "00000"&iCountB;
iReadAddrA <= (iRowA sll 5)+CountAT;
iReadAddrB <= (CountBT sll 5)+ iColB(9 downto 0);
process (presState, WriteEnable, BufferSel, iCount, iCountA, iCountB)
begin
-- signal defaults
iCountResetAB <= '0';
iCountReset <= '0';
iCountEnable <= '1';
iReadEnableAB <= '0';
iWriteEnableC(0) <= '0';
Dataready <= '0';
iCountEnableAB <= '0';
case presState is
when stIdle =>
if (WriteEnable = '1' and BufferSel = '1') then
nextState <= stWriteBufferA;
else
iCountReset <= '1';
nextState <= stIdle;
end if;
when stWriteBufferA =>
if iCount = x"3FF" then
report "Writing A";
iCountReset <= '1';
nextState <= stWriteBufferB;
else
nextState <= stWriteBufferA;
end if;
when stWriteBufferB =>
report "Writing B";
if iCount = x"3FF" then
iCountReset <= '1';
nextState <= stReadBufferAB;
else
nextState <= stWriteBufferB;
end if;
when stReadBufferAB =>
iReadEnableAB <= '1';
iCountEnable <= '0';
report "CalculatingAB";
if iCountA = x"1F" and iCountB = x"1F" then
nextState <= stSaveData;
report "Calculating";
iCountEnableAB <= '0';
iCountResetAB <= '1';
else
nextState <= stReadBufferAB;
iCountEnableAB <= '1';
iCountResetAB <= '0';
end if;
when stSaveData =>
iReadEnableAB <= '1';
iCountEnable <= '0';
nextState <= stWriteBufferC;
when stWriteBufferC =>
iWriteEnableC(0) <= '1';
report "finish 1 component";
if iCount = x"3FF" then
iCountReset <= '1';
nextState <= stComplete;
else
nextState <= stReadBufferAB;
end if;
when stComplete =>
DataReady <= '1';
nextState <= stIdle;
end case;
end process;
end IntMatMulCore_arch;
```

**Below is the testbench code to read matrix inputs from text files and write the result to an output text file:**

```
--
-- tb_IntMatMultCore.vhd
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_textio.all;
use ieee.numeric_std.all;
use std.textio.all;
entity tb_IntMatMultCore is
end tb_IntMatMultCore;
architecture behavior of tb_IntMatMultCore is
component IntMatMulCore
port(
Reset, Clock, WriteEnable, BufferSel: in std_logic;
WriteAddress: in std_logic_vector (9 downto 0);
WriteData: in std_logic_vector (15 downto 0);
ReadAddress: in std_logic_vector (9 downto 0);
ReadEnable: in std_logic;
ReadData: out std_logic_vector (63 downto 0);
DataReady: out std_logic
);
end component;
signal tb_Reset : std_logic := '0';
signal tb_Clock : std_logic := '0';
signal tb_BufferSel : std_logic := '0';
signal tb_WriteEnable : std_logic := '0';
signal tb_WriteAddress : std_logic_vector(9 downto 0) := (others => '0');
signal tb_WriteData : std_logic_vector(15 downto 0) := (others => '0');
signal tb_ReadEnable : std_logic := '0';
signal tb_ReadAddress : std_logic_vector(9 downto 0) := (others => '0');
signal tb_DataReady : std_logic;
signal tb_ReadData : std_logic_vector(63 downto 0);
-- Clock period definitions
constant period : time := 100 ns;
begin
-- Instantiate the Unit Under Test (UUT)
uut: IntMatMulCore
PORT MAP (
Reset => tb_Reset,
Clock => tb_Clock,
WriteEnable => tb_WriteEnable,
BufferSel => tb_BufferSel,
WriteAddress => tb_WriteAddress,
WriteData => tb_WriteData,
ReadEnable => tb_ReadEnable,
ReadAddress => tb_ReadAddress,
ReadData => tb_ReadData,
DataReady => tb_DataReady
);
-- Test Bench Statements
process is
begin
--while now <= 10000000000000 * period loop
tb_Clock <= '0';
wait for period/2;
tb_Clock <= '1';
wait for period/2;
--end loop;
--wait;
end process;
process is
begin
tb_Reset <= '1';
wait for 10*period;
tb_Reset <= '0';
wait;
end process;
writingA: process is
file FIA: TEXT open READ_MODE is "InputA.txt"; -- the input file must have 17 rows
file FIB: TEXT open READ_MODE is "InputB.txt"; -- the input file must have 17 rows
variable L: LINE;
variable tb_MatrixData: std_logic_vector(15 downto 0);
begin
tb_WriteEnable <= '0';
tb_BufferSel <= '1';
tb_WriteAddress <= "11"&x"FF";
wait for 20*period;
READLINE(FIA, L);
while not ENDFILE(FIA) loop
READLINE(FIA, L);
HREAD(L, tb_MatrixData);
wait until falling_edge(tb_Clock);
tb_WriteAddress <= std_logic_vector(unsigned(tb_WriteAddress)+1);
tb_BufferSel <= '1';
tb_WriteEnable <= '1';
tb_WriteData <=tb_MatrixData;
end loop;
READLINE(FIB, L);
while not ENDFILE(FIB) loop
READLINE(FIB, L);
HREAD(L, tb_MatrixData);
wait until falling_edge(tb_Clock);
tb_WriteAddress <= std_logic_vector(unsigned(tb_WriteAddress)+1);
tb_BufferSel <= '0';
tb_WriteEnable <= '1';
tb_WriteData <=tb_MatrixData;
end loop;
wait for period;
tb_WriteEnable <= '0';
wait;
end process;
reading: process is
file FO: TEXT open WRITE_MODE is "OutputMultC.txt";
file FI: TEXT open READ_MODE is "OutputMultC_matlab.txt";
variable L, Lm: LINE;
variable v_ReadDatam: std_logic_vector(63 downto 0);
variable v_OK: boolean;
begin
tb_ReadEnable <= '0';
tb_ReadAddress <=(others =>'0');
---wait for Mul done
wait until rising_edge(tb_DataReady);
wait until falling_edge(tb_DataReady);
READLINE(FI, Lm);
Write(L, STRING'("OutputMultC"));
WRITELINE(FO, L);
tb_ReadEnable<= '1' ;
while not ENDFILE(FI) loop
wait until rising_edge(tb_Clock);
wait for 20 ns;
READLINE(FI, Lm);
HREAD(Lm, v_ReadDatam);
if v_ReadDatam = tb_ReadData then
v_OK :=True;
report "Matched";
else
v_OK :=False;
end if;
HWRITE(L, '0'& tb_ReadData, Left, 10);
WRITE(L, v_OK, Right, 10);
WRITELINE(FO, L);
tb_ReadAddress <= std_logic_vector(unsigned(tb_ReadAddress)+1);
end loop;
tb_ReadEnable <= '0';
assert false report "Simulation Finished" severity failure; -- to stop simulation
wait;
end process;
end;
```

**Behavior Simulation**

After finishing the design of the multiplier core, we carry out the behavior simulation for core. Test bench is reading input A and B, then produce output C and then compare with Matlab result. If the result is 100% compared to Matlab, data in output file “OutputMultC.txt” will be “true”. Otherwise, it will be false.

Behavior simulation waveform

After 34816 clock cycles, the matrix multiplication for the matrix with size 32x32 is completed and the signal “dataready” is asserted high. It is reasonable because it would take 32 cycles to calculate each matrix component of the output matrix C. There are also 2 cycles which are saving data and writing data to buffer C for each matrix component. Thus, there are 34 clock cycles being used to calculate one component of matrix C. The size of matrix C is 32x32, then we have the matrix multiplication time is 32x32x34 = 34816 cycles.

The behavior simulation result is correctly compared to Matlab. In fact, the output file “OutputMultC.txt” is all “true”. We also check the memory content of the buffer output and the results are of course similar to Matlab calculation.

**Post-route simulation**

To perform post-route simulation, we synthesis and get the post translate netlist to be simulated. The synthesis result indicates that the maximum frequency of the clock used for this design is 120.757MHz. It mean that the minimum period of the clock is 8.281ns. Thus, the minimum multiplication time for the 32x32 matrixes is 34816x8.281ns = 288.311 us.

The post-route simulation result is accurately similar to the Matlab result and the behavior simulation. Indeed, the output file “OutputMultC.txt” is all “true” as our expectation.

In this project, the matrix multiplication for the matrixes with 32x32 16-bit unsigned integers is implemented on FPGA Spartan6 of Xilinx. The minimum multiplication time for the matrix of 32x32 is 288.311 us.

Nice

ReplyDeletegood

ReplyDeleteI saw this site very very helpful, great work and keep posting.

ReplyDeletePlease keep updating the blog via http://www.fpga4student.com/ to get various FPGA projects using Verilog/ VHDL

ReplyDeleteHi!

ReplyDeleteCan I implement this project with Vivado 2015.4 on board Basys 3. I tried to do this, but Vivado 2015.4 doesn't read/take these files (InputBufferA - dpram1024x16, InputBufferB - dpram1024x16 and OutputBufferC - dpram1024x64)

Or I'll have to create them and throw in a folder "sources".

Can you help me? I would be happy for the answer.