-------------------------------------------------------------
-- MSS copyright 2014-2016
-- Filename:  ARITH.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 1
-- Last modified: 10/20/16

-- Inheritance: 	N/A
--
-- description:  Basic signed arithmetics unit for add,mult,divide,modulo,shift.
-- Relatively slow but small footprint.
--
-- Rev 1 10/20/16 AZ
-- major clean-up
-------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity ARITH is
	generic (
		SIMULATION: std_logic := '0'
			-- 1 during simulation, 0 for release	
	);
    Port ( 
		--GLOBAL CLOCKS
		CLK: std_logic;
		SYNC_RESET: in std_logic;
			-- resets all internal memories
		
		-- INPUTS
		OPERAND_A: in std_logic_vector(17 downto 0);
		OPERAND_A_VALID: in std_logic;
			-- 1 CLK pulse to latch in OPERAND_A. 
			
		OPERAND_B: in std_logic_vector(17 downto 0);
		OPERAND_B_VALID: in std_logic;
			-- 1 CLK pulse to latch in OPERAND_B. 

		OP: in std_logic_vector(3 downto 0);
			-- operation code
			-- 0000 = noop. Clear RESULT,A,B 
			-- 0001 = RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
			-- 0010 = multiply/accumulate RESULT = A*B + RESULT
			-- 0011 = RESULT = A + B
			-- 0100 = RESULT = RESULT + A + B
			-- 0101 = RESULT = RESULT / A
			-- 0110 = RESULT = RESULT modulo A
			-- 0111 = RESULT = B modulo A
		OP_VALID: in std_logic;
			-- 1 CLK pulse to execute the above operation 
			-- always check that the last command is complete (READY = '1') before sending the next command
		
		-- OUTPUTS
		RESULT: out std_logic_vector(35 downto 0):= (others => '0');
		RESULT_VALID: out std_logic := '0';
			-- 1 CLK wide pulse when RESULT becomes valid
		READY: out std_logic
			-- ready to read RESULT and send another OP
		  
			  );
end entity;

architecture Behavioral of ARITH is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
    COMPONENT DIVIDER
	 Generic(
		NBITS: integer
	 );
    PORT(
         CLK : IN  std_logic;
         NUM : IN  std_logic_vector((NBITS-1) downto 0);
         DENOM : IN  std_logic_vector((NBITS-1) downto 0);
         SAMPLE_CLK_IN : IN  std_logic;
         QUOTIENT : OUT  std_logic_vector((NBITS-1) downto 0);
         REMAINDER : OUT  std_logic_vector((NBITS-1) downto 0);
         SAMPLE_CLK_OUT : OUT  std_logic
        );
    END COMPONENT;
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
-- Suffix _D indicates a one CLK delayed version of the net with the same name
-- Suffix _X indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same name

signal CLEAR_ALL_REGISTERS: std_logic := '0';
signal OPERAND_A_D: signed(17 downto 0) := (others => '0');
signal OPERAND_B_D: signed(17 downto 0) := (others => '0');
signal S: signed(18 downto 0) := (others => '0');
signal OP_D: std_logic_vector(3 downto 0) := (others => '0');
signal CNTR: integer range 0 to 255 := 0;
signal P: signed(35 downto 0) := (others => '0');
signal P_D: signed(35 downto 0) := (others => '0');
signal RESULT_local: signed(35 downto 0) := (others => '0');
signal RESULT_VALID_local: std_logic := '0';
signal DV_NUM: signed(35 downto 0) := (others => '0');
signal DV_DENOM: signed(35 downto 0) := (others => '0');
signal DV_QUOTIENT_slv: std_logic_vector(35 downto 0) := (others => '0');
signal DV_REMAINDER_slv: std_logic_vector(35 downto 0) := (others => '0');
signal DV_QUOTIENT: signed(35 downto 0) := (others => '0');
signal DV_REMAINDER: signed(35 downto 0) := (others => '0');
signal DV_SAMPLE_CLK_IN: std_logic := '0';
signal DV_SAMPLE_CLK_OUT: std_logic := '0';
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin


-- latch in the inputs
RECLOCK_INPUTS_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (CLEAR_ALL_REGISTERS = '1') then
			OPERAND_A_D <= (others => '0');
		elsif(OPERAND_A_VALID = '1') then
			OPERAND_A_D <= signed(OPERAND_A);
		elsif(RESULT_VALID_local = '1') and (OP_D = "0001") then
			-- 0001 = RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
			OPERAND_A_D <= RESULT_local(35 downto 18);
		end if;
		
		if(SYNC_RESET = '1') or (CLEAR_ALL_REGISTERS = '1') then
			OPERAND_B_D <= (others => '0');
		elsif(OPERAND_B_VALID = '1') then
			OPERAND_B_D <= signed(OPERAND_B);
		elsif(RESULT_VALID_local = '1') and (OP_D = "0001") then
			-- 0001 = RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
			OPERAND_B_D <= RESULT_local(17 downto 0);
		end if;
		
	end if;
end process;

-- op command handler
CLEAR_ALL_REGISTERS <= '1' when ((OP = "0000") and (OP_VALID = '1')) else '0';
COMMAND_HANDLER_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			OP_D <= (others => '0');
			CNTR <= 0;
		elsif(OP_VALID = '1') then
			OP_D <= OP;
			
			if(OP = "0001") then
				-- 0001 = RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
				CNTR <= 2;
			elsif(OP = "0010") then
				-- 0010 = multiply/accumulate RESULT = A*B + RESULT
				CNTR <= 2;
			elsif(OP = "0011") then
				-- 0011 = RESULT = A + B
				CNTR <= 2;
			elsif(OP = "0100") then
				-- 0100 = RESULT = RESULT + A + B
				CNTR <= 2;
			elsif(OP = "0110") then
				-- modulo. Divider takes control of RESULT_VALID timing
				CNTR <= 39;	-- maximum division latency
			end if;
			
		elsif(CNTR /= 0) then
			CNTR <= CNTR - 1;
		end if;
	end if;
end process;
 
READY <= '1' when (OP_VALID = '0') and (CNTR = 0) else '0';

-- multiplication. 
P <= OPERAND_A_D * OPERAND_B_D;

-- reclock multiplier output for better timing
MULT_001: process(CLK)
begin
	if rising_edge(CLK) then
		P_D <= P;
	end if;
end process;

SUM_001: process(CLK)
begin
	if rising_edge(CLK) then
		S <= resize(OPERAND_A_D,S'length) + resize(OPERAND_B_D,S'length);
	end if;
end process;

DIVIDER_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			DV_SAMPLE_CLK_IN <= '0';
		elsif(OP_VALID = '1') then
			if(OP = "0101") then
				-- 0101 = RESULT = RESULT / A
				DV_SAMPLE_CLK_IN <= '1';
				DV_NUM <= RESULT_local;
			elsif(OP = "0110") then
				-- 0110 = RESULT = RESULT modulo A
				DV_SAMPLE_CLK_IN <= '1';
				DV_NUM <= RESULT_local;
			elsif(OP = "0111") then
				-- 0111 = RESULT = B modulo A
				DV_SAMPLE_CLK_IN <= '1';
				DV_NUM <= resize(signed(OPERAND_B), 36);
			end if;
		else
			DV_SAMPLE_CLK_IN <= '0';
		end if;
	end if;
end process;

DV_DENOM <= resize(OPERAND_A_D, 36);
DIVIDER_002: DIVIDER 
GENERIC MAP(
	NBITS => 36
)
PORT MAP (
	 CLK => CLK,
	 NUM => std_logic_vector(DV_NUM),
	 DENOM => std_logic_vector(DV_DENOM),
	 SAMPLE_CLK_IN => DV_SAMPLE_CLK_IN,
	 QUOTIENT => DV_QUOTIENT_slv,
	 REMAINDER => DV_REMAINDER_slv,
	 SAMPLE_CLK_OUT => DV_SAMPLE_CLK_OUT
  );


-- select output
RESULT_OUT_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (CLEAR_ALL_REGISTERS = '1') then
			RESULT_local <= (others => '0');
			RESULT_VALID_local <= '0';
		elsif(OP_D = "0001") and (CNTR = 1) then
			-- 0001 = RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
			RESULT_local <= P_D;
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0010") and (CNTR = 1) then
			-- 0010 = multiply/accumulate RESULT = A*B + RESULT
			RESULT_local <= RESULT_local + P_D;
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0011") and (CNTR = 1) then
			-- 0011 = RESULT = A + B
			RESULT_local <= resize(S,RESULT_local'length);
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0100") and (CNTR = 1) then
			-- 0100 = RESULT = RESULT + A + B
			RESULT_local <= RESULT_local + resize(S,RESULT_local'length);
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0101") and (DV_SAMPLE_CLK_OUT = '1') then
			-- 0101 = RESULT = RESULT / A
			RESULT_local <= resize(signed(DV_QUOTIENT_slv),RESULT_local'length);
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0110") and (DV_SAMPLE_CLK_OUT = '1') then
			-- 0110 = RESULT = RESULT modulo A
			RESULT_local <= resize(signed(DV_REMAINDER_slv),RESULT_local'length);
			RESULT_VALID_local <= '1';
		elsif(OP_D = "0111") and (DV_SAMPLE_CLK_OUT = '1') then
			-- 0111 = RESULT = B modulo A
			RESULT_local <= resize(signed(DV_REMAINDER_slv),RESULT_local'length);
			RESULT_VALID_local <= '1';
		else
			RESULT_VALID_local <= '0';
		end if;
	end if;
end process;
RESULT <= std_logic_vector(RESULT_local);

RESULT_VALID <= RESULT_VALID_local and (not OP_VALID);	-- **10/20/16


end Behavioral;

