-------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: Testbench for JCPU
--            also writes 'opc.lst' for JASM-assembler
--
-- Copyright (C) 2007 J. Ahrensfeld
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

LIBRARY ieee;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL;

library work;
use work.mips_types.all;
use work.mips_instr.all;

ENTITY tb_mips_muldiv IS
END tb_mips_muldiv;

ARCHITECTURE behavior OF tb_mips_muldiv IS 

	COMPONENT muldiv is
	Port 
	(
		rst			: in  std_logic;
		clk			: in  std_logic;
		hilo_we		: in  std_logic;
		din_hi		: in  word_t;
		din_lo		: in  word_t;
		mul_divn	: in  std_logic;
		s_un		: in  std_logic;
		start		: in  std_logic;
		hilo_sel	: in  std_logic;
		busy		: out std_logic;
		dout		: out word_t
	);
	END COMPONENT;

	constant CLK_PERIOD : time := 10 ns;
	signal ENABLE_FAIL_REPORT : boolean := true;
	signal rst 			: std_logic := '1';
	signal clk 			: std_logic := '1';
	signal busy			: std_logic;
	signal mul_divn		: std_logic := '0';
	signal s_un			: std_logic := '1';
	signal hilo_we		: std_logic := '0';
	signal start		: std_logic := '0';
	signal din_hi		: word_t := (others => '-');
	signal din_lo		: word_t := (others => '-');
	signal dout			: word_t;
	signal hilo_sel		: std_logic := '0';
	signal md_result	: unsigned (2*word_t'length-1 downto 0) := (others => '0');
	signal ref_result	: unsigned (2*word_t'length-1 downto 0);
	signal ref_hi		: unsigned (word_t'length-1 downto 0);
	signal rtmp 		: unsigned (2*word_t'length-1 downto 0);
	signal tmp2 		: unsigned (word_t'length-1 downto 0);
	signal check		: std_logic;
	
	type word_array_t is array (natural range <>) of word_t;
	
	constant operands : word_array_t(0 to 63) := 
	(
		X"00000000",
		X"00000001",
		X"00000010",
		X"00000100",
		X"00001000",
		X"00010000",
		X"00100000",
		X"01000000",
		X"10000000",
		X"0000000F",
		X"000000FF",
		X"00000FFF",
		X"0000FFFF",
		X"000FFFFF",
		X"00FFFFFF",
		X"0FFFFFFF",
		X"12345678",
		X"23456789",
		X"7FFFFFFF",
		X"80000000",
		X"A541B3B9",
		X"80000001",
		X"80000010",
		X"80000100",
		X"80001000",
		X"80010000",
		X"80100000",
		X"81000000",
		X"87654321",
		X"98765432",
		X"FFFFFFFF",
		X"FFFFFFFE",
		X"FFFFFFFD",
		X"FFFFFFFB",
		X"FFFFFFF7",
		X"FFFFFFEF",
		X"FFFFFFDF",
		X"FFFFFFBF",
		X"FFFFFF7F",
		X"FFFFFEFF",
		X"FFFFFDFF",
		X"FFFFFBFF",
		X"FFFFF7FF",
		X"FFFFEFFF",
		X"FFFFDFFF",
		X"FFFFBFFF",
		X"FFFF7FFF",
		X"FFFEFFFF",
		X"FFFDFFFF",
		X"FFFBFFFF",
		X"FFF7FFFF",
		X"FFEFFFFF",
		X"FFDFFFFF",
		X"FFBFFFFF",
		X"FF7FFFFF",
		X"FEFFFFFF",
		X"FDFFFFFF",
		X"FBFFFFFF",
		X"F7FFFFFF",
		X"EFFFFFFF",
		X"DFFFFFFF",
		X"BFFFFFFF",
		X"CFFFFFFF",
		X"7FFFFFFF"
	);
	
BEGIN

uut: muldiv
	PORT MAP(
		rst				=> rst,
		clk				=> clk,
		hilo_we			=> hilo_we,
		din_hi			=> din_hi,
		din_lo			=> din_lo,
		mul_divn		=> mul_divn,
		s_un			=> s_un,
		start			=> start,
		hilo_sel		=> hilo_sel,
		busy			=> busy,
		dout			=> dout
	);

CLK_GEN: process
	begin
		wait for CLK_PERIOD/2;
		clk <= not clk;
	end process;

REF_GEN: process
	begin
		if mul_divn = '0' then
			wait until rising_edge(clk) and check = '1';
			if s_un = '1' then
				rtmp		<= unsigned(signed(md_result(31 downto 0)) * signed(din_lo));
				wait until rising_edge(clk);
				tmp2 		<= unsigned(signed(rtmp(31 downto 0)) + signed(md_result(63 downto 32)));
				wait until rising_edge(clk);
				ref_hi		<= tmp2(31 downto 0);
			else
				rtmp		<= md_result(31 downto 0) * din_lo;
				wait until rising_edge(clk);
				tmp2 		<= rtmp(31 downto 0) + md_result(63 downto 32);
				wait until rising_edge(clk);
				ref_hi		<= tmp2(31 downto 0);
			end if;	
			wait until rising_edge(clk);
				
			if din_lo /= X"00000000" then
				if ENABLE_FAIL_REPORT  then
					assert din_hi = ref_hi report "Error on divison" severity failure;
				end if;	
			end if;	
		else
			wait until rising_edge(clk) and check = '1';
			if s_un = '1' then
				ref_result <= unsigned(signed(din_hi) * signed(din_lo));
			else
				ref_result <= unsigned(din_hi) * unsigned(din_lo);
			end if;	
			wait until rising_edge(clk);
			wait until rising_edge(clk);
			wait until rising_edge(clk);
			if ENABLE_FAIL_REPORT then
				assert ref_result = md_result report "Error on multiplication" severity failure;
			end if;	
		end if;
		
	end process;

STIMULUS: process

	begin

		wait for 3*CLK_PERIOD;
		wait until rising_edge(clk);
		rst <= '0';
		wait for 2*CLK_PERIOD;
	
		mul_divn	<= '0';
		s_un		<= '0';
		for i in 0 to operands'length-1 loop
			for j in 0 to operands'length-1 loop
				check	<= '0';
				wait until rising_edge(clk) and busy = '0';
				din_hi		<= operands(i);
				din_lo		<= operands(j);
				start		<= '1';
				wait until rising_edge(clk);
				start		<= '0';
				hilo_sel	<= '0';

				-- Read result
				wait until rising_edge(clk) and busy = '0';
				wait until rising_edge(clk);
				md_result(31 downto 0) <= dout;
				wait until rising_edge(clk);
				hilo_sel	<= '1';
				wait until rising_edge(clk);
				md_result(63 downto 32) <= dout;
				check	<= '1';
				wait until rising_edge(clk);
				check	<= '0';
 				wait for 8*CLK_PERIOD;
			end loop;
		end loop;

		s_un	<= '1';
		for i in 0 to operands'length-1 loop
			for j in 0 to operands'length-1 loop
				check	<= '0';
				wait until rising_edge(clk) and busy = '0';
				din_hi		<= operands(i);
				din_lo		<= operands(j);
				start		<= '1';
				wait until rising_edge(clk);
				start		<= '0';
				hilo_sel	<= '0';

				-- Read result
				wait until rising_edge(clk) and busy = '0';
				wait until rising_edge(clk);
				md_result(31 downto 0) <= dout;
				wait until rising_edge(clk);
				hilo_sel	<= '1';
				wait until rising_edge(clk);
				md_result(63 downto 32) <= dout;
				check	<= '1';
				wait until rising_edge(clk);
				check	<= '0';
 				wait for 8*CLK_PERIOD;
			end loop;
		end loop;
	
		mul_divn	<= '1';
		s_un		<= '0';
		for i in 0 to operands'length-1 loop
			for j in 0 to operands'length-1 loop
				check	<= '0';
				wait until rising_edge(clk) and busy = '0';
				din_hi		<= operands(i);
				din_lo		<= operands(j);
				start		<= '1';
				wait until rising_edge(clk);
				start		<= '0';
				hilo_sel	<= '0';

				-- Read result
				wait until rising_edge(clk) and busy = '0';
				wait until rising_edge(clk);
				md_result(31 downto 0) <= dout;
				wait until rising_edge(clk);
				hilo_sel	<= '1';
				wait until rising_edge(clk);
				md_result(63 downto 32) <= dout;
				check	<= '1';
				wait until rising_edge(clk);
				check	<= '0';
 				wait for 8*CLK_PERIOD;
			end loop;
		end loop;

		s_un	<= '1';
		for i in 0 to operands'length-1 loop
			for j in 0 to operands'length-1 loop
				check	<= '0';
				wait until rising_edge(clk) and busy = '0';
				din_hi		<= operands(i);
				din_lo		<= operands(j);
				start		<= '1';
				wait until rising_edge(clk);
				start		<= '0';
				hilo_sel	<= '0';

				-- Read result
				wait until rising_edge(clk) and busy = '0';
				wait until rising_edge(clk);
				md_result(31 downto 0) <= dout;
				wait until rising_edge(clk);
				hilo_sel	<= '1';
				wait until rising_edge(clk);
				md_result(63 downto 32) <= dout;
				check	<= '1';
				wait until rising_edge(clk);
				check	<= '0';
 				wait for 8*CLK_PERIOD;
			end loop;
		end loop;
		
		wait; 
	
	end process;
	
END;
