-- altera vhdl_input_version vhdl_2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library lpm;
use lpm.lpm_components.all;

use work.fifo_pkg.all;

entity ci_div is
	port (
		clk   : in std_logic;
		clk_en : in std_logic;
		reset : in std_logic;
		
		dataa : in std_logic_vector(31 downto 0); 
		datab : in std_logic_vector(31 downto 0);
		result : out std_logic_vector(31 downto 0);

		start : in std_logic;
		done : out std_logic;
		
		n : in std_logic_vector(0 downto 0)
	);
end entity;


architecture arch of ci_div is

	constant CAPACITY : integer := 64;
	constant SHORT_ZERO : std_logic_vector(15 downto 0) := (others => '0');
	constant LONG_ZERO : std_logic_vector(47 downto 0) := (others => '0');

	component lpm_div
		port (
			clken		: in std_logic;
			clock		: in std_logic;
			denom		: in std_logic_vector(31 downto 0);
			numer		: in std_logic_vector(47 downto 0);
			quotient	: out std_logic_vector(47 downto 0);
			remain		: out std_logic_vector(31 downto 0)
		);
	end component;

	signal reg_shift_request : std_logic_vector(47 downto 0) := LONG_ZERO;
	signal reg_next_shift_request : std_logic_vector(47 downto 0);

	signal enqueue : std_logic := '0';
	signal dequeue : std_logic := '0';
	signal reg_queued : integer range 0 to CAPACITY := 0;
	signal reg_next_queued : integer range 0 to CAPACITY := 0;
	signal capacity_available : std_logic := '0';

	signal reg_dequeue_pending : std_logic := '0';
	signal reg_next_dequeue_pending : std_logic := '0';

	signal reg_enqueue_pending : std_logic := '0';
	signal reg_next_enqueue_pending : std_logic := '0';

	signal lpm_div_done : std_logic := '0';
	signal div_read : std_logic := '0';
	signal div_write : std_logic := '0';
        
	signal fifo_in : fifo_in_t := FIFO_IN_NOP;
	signal fifo_out : fifo_out_t;

	type lpm_in_t is
	record
		denom : std_logic_vector(31 downto 0);
		numer : std_logic_vector(47 downto 0);
	end record;
	constant LPM_IN_NOP : lpm_in_t := (ZERO, LONG_ZERO);

	type lpm_out_t is
	record
		quotient : std_logic_vector(47 downto 0);
		remain : std_logic_vector(31 downto 0);
	end record;

	signal lpm_in : lpm_in_t := LPM_IN_NOP;
	signal lpm_out : lpm_out_t;

begin

	alt_fwft_fifo_inst : entity work.alt_fwft_fifo 
	generic map (
		DATA_WIDTH => 32,
		NUM_ELEMENTS => CAPACITY
	)
	port map (
		aclr => reset,
		clock => clk,
		data => fifo_in.data,
		rdreq => fifo_in.rdreq,
		wrreq => fifo_in.wrreq,
		empty => fifo_out.empty,
		full => fifo_out.full,
		q => fifo_out.q
	);

	lpm_div_inst : component lpm_div 
	port map (
		clken => clk_en,
		clock => clk,
		numer => lpm_in.numer,
		denom => lpm_in.denom,
		quotient => lpm_out.quotient,
		remain => lpm_out.remain
	);

	result <= fifo_out.q;

	enqueue <= (not n(0)) and start;
	dequeue <= n(0) and start;
	done <= div_read or div_write;

	capacity_available <= '1' when reg_queued < CAPACITY else '0';
	lpm_div_done <= reg_shift_request(47);
	div_read <= (dequeue or reg_dequeue_pending) and not fifo_out.empty;
	div_write <= (enqueue or reg_enqueue_pending) and capacity_available;

	fifo_in.wrreq <= lpm_div_done;
	fifo_in.data <= lpm_out.quotient(31 downto 0);
	fifo_in.rdreq <= div_read;

	sync : process(clk, clk_en, reset)
	begin
		if reset = '1' then
			-- reset values
			reg_shift_request <= LONG_ZERO;
			reg_enqueue_pending <= '0';
			reg_dequeue_pending <= '0';
			reg_queued <= 0;
		elsif clk_en = '1' and rising_edge(clk) then
			-- register transfer
			reg_shift_request <= reg_next_shift_request;
			reg_enqueue_pending <= reg_next_enqueue_pending;
			reg_dequeue_pending <= reg_next_dequeue_pending;
			reg_queued <= reg_next_queued;
		end if;
	end process;

	async : process(all)
	begin
		-- implement shift register for enqueue operations (to know when lpm_div is done)
		reg_next_shift_request(0) <= div_write;
		reg_next_shift_request(reg_shift_request'high downto 1) <= reg_shift_request(reg_shift_request'high - 1 downto 0);

		-- keep track of started enqueue operation if necessary
		if reg_enqueue_pending = '1' then
                        -- deassert reg_enqueue_pending only once done is asserted
                        reg_next_enqueue_pending <= not done;
		else
			reg_next_enqueue_pending <= enqueue and not capacity_available;
		end if;

		-- keep track of started dequeue operation if necessary
		if reg_dequeue_pending = '1' then
                        -- deassert reg_dequeue_pending only once done is asserted
                        reg_next_dequeue_pending <= not done;
		else
			reg_next_dequeue_pending <= dequeue and fifo_out.empty;
		end if;

		-- keep track of available capacity
		reg_next_queued <= reg_queued;
		if reg_queued > 0 and div_read = '1' then
			reg_next_queued <= reg_queued - 1;
		elsif reg_queued < CAPACITY and div_write = '1' then
			reg_next_queued <= reg_queued + 1;
		end if;

		-- extend dataa by 16 bits
		lpm_in.numer <= dataa & SHORT_ZERO;
		lpm_in.denom <= datab;
	end process;

end architecture;
