--------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: The arithmetic logic unit
--
-- 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;

use work.mips_types.all;

entity 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;
		start		: in  std_logic;
		s_un		: in  std_logic;
		hilo_sel	: in  std_logic;
		busy		: out std_logic;
		dout		: out word_t
	);
end muldiv;

architecture Behavioral of muldiv is

	subtype pprod_t is unsigned (39 downto 0);
	subtype prod_t is unsigned (63 downto 0);
	subtype byte_t is unsigned (7 downto 0);
	
	type pprod_array_t is array (natural range <>) of pprod_t;
	type word_array_t is array (natural range <>) of word_t;
	type byte_array_t is array (natural range <>) of byte_t;

	signal pprod : pprod_array_t(0 to 3);
	signal sign1 : std_logic;
	signal sign2 : std_logic;
	signal s1_r : std_logic;
	signal s2_r : std_logic;
	signal sr_r : std_logic;
	signal cy : unsigned (3 downto 0);
	signal mul_start : std_logic;
	signal mul_en : std_logic;
	signal sum_en : std_logic;
	signal pre_sum_vld : std_logic;
	signal pp_reg  : unsigned (63 downto 8);
	signal pre_const : word_array_t(0 to 1);
	signal result : prod_t;
	signal add_op1 : unsigned (39 downto 0);
	signal add_op2 : unsigned (39 downto 0);
	signal add_res : unsigned (39 downto 0);
	signal ppadd_op1 : word_array_t(0 to 3);
	signal ppadd_op2 : word_array_t(0 to 3);
	signal ppadd_res : word_array_t(0 to 3);
	signal ppadd_cyi : unsigned (3 downto 0);
	signal ppadd_cyo : unsigned (3 downto 0);

	signal pp_op1_r : word_t;
	signal pp_op1 : word_t;
	signal pp_op2 : word_t;
	signal pp_op2_r : byte_array_t(0 to 3);
	signal bsy : std_logic;

	signal div_add_res : unsigned (32 downto 0);
	signal div_add_op1 : unsigned (32 downto 0);
	signal div_add_op2 : unsigned (32 downto 0);
	signal div_add_cyi : std_logic;
	signal div_qbit : std_logic;
	signal div_start : std_logic;
	signal div_en : std_logic;

	signal div_A : unsigned (32 downto 0);
	signal div_M : unsigned (32 downto 0);
	signal div_M_n : unsigned (32 downto 0);
	signal div_Q : word_t;
	
	type md_state_t is (md_rdy, mul_pre1, mul_pre2, mul_pp, mul_sum, div_process, div_post1, div_post2, div_post3);
	signal s, sn : md_state_t;
	signal cycle_cnt : natural range 0 to 31;
	signal cycle_cnt_preset : natural range 0 to 31;
	signal cycle_cnt_load : std_logic;
	signal div_fin_en : unsigned (2 downto 0);
	
--------------------------------------------------------------------------
begin
-- 9A0CD0570B88D78	

	busy		<= bsy;

	sign1		<= din_hi(din_hi'left) and s_un;
	sign2		<= din_lo(din_lo'left) and s_un;
	pp_op1		<= not din_hi when sign1 = '1' else din_hi;
	pp_op2		<= not din_lo when sign2 = '1' else din_lo;
	
--------------------------------------------------------------------------
md_state:
	process(s, start, mul_divn, cycle_cnt)
	begin
		
		bsy					<= '1';
		cycle_cnt_load		<= '0';
		cycle_cnt_preset	<= 7;
	
		mul_en		<= '0';
		div_en		<= '0';
		sum_en		<= '0';
		pre_sum_vld	<= '0';
		mul_start	<= '0';
		div_start	<= '0';
		div_fin_en	<= "000";
		sn	<= s;
		case s is
		when md_rdy =>
			bsy	<= '0';
			if start = '1' then
				if mul_divn = '1' then
					sn	<= mul_pre1;
					cycle_cnt_load <= '1';
					cycle_cnt_preset <= 7;
					mul_start	<= '1';
				else
					sn	<= div_process;
					cycle_cnt_load <= '1';
					cycle_cnt_preset <= 31;
					div_start	<= '1';
				end if;
			end if;
		when mul_pre1 =>
			pre_sum_vld <= '1';
			mul_en	<= '1';
			sn	<= mul_pre2;
		when mul_pre2 =>
			pre_sum_vld <= '1';
			mul_en	<= '1';
			sn	<= mul_pp;
		when mul_pp =>
			mul_en	<= '1';
			if cycle_cnt = 0 then
				cycle_cnt_load <= '1';
				cycle_cnt_preset <= 3;
				sn	<= mul_sum;
			end if;
		when mul_sum =>
			sum_en	<= '1';
			if cycle_cnt = 0 then
				bsy	<= '0';
				sn	<= md_rdy;
			end if;
		when div_process =>
			div_en	<= '1';
			if cycle_cnt = 0 then
				sn	<= div_post1;
			end if;
		when div_post1 =>
			div_fin_en	<= "001";
			sn	<= div_post2;
		when div_post2 =>
			div_fin_en	<= "010";
			sn	<= div_post3;
		when div_post3 =>
			div_fin_en	<= "100";
			sn	<= md_rdy;
			bsy	<= '0';
		when others =>
			sn	<= md_rdy;
		end case;		
				
	end process;	

md_state_next:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				s	<= md_rdy;
			else
				s	<= sn;
			end if;
		end if;
	end process;	
	
md_cycle_cnt:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				cycle_cnt		<= 0;
			elsif cycle_cnt_load = '1' then
				cycle_cnt	<= cycle_cnt_preset;
			elsif cycle_cnt /= 0 then
				cycle_cnt		<= cycle_cnt - 1;
			end if;	
		end if;
	end process;	
	
proc_out_reg:
	process(hilo_sel, result)
	begin
		if hilo_sel = '1' then
			dout <= result(63 downto 32);
		else	
			dout <= result(31 downto 0);
		end if;
	end process;	
	
proc_in_reg:
	process(clk)
	variable pre_cond : unsigned(1 downto 0);
	begin
		if rising_edge(clk) then
			if mul_start = '1' or div_start = '1' then
				pp_op1_r		<= pp_op1;
				pre_const(0)	<= (others => '0');
				pre_const(1)	<= (others => '0');
				pre_cond		:= (din_hi(din_hi'left) and s_un) & (din_lo(din_lo'left) and s_un);
				s1_r			<= pre_cond(1);
				s2_r			<= pre_cond(0);
				sr_r			<= pre_cond(0) xor pre_cond(1);
				case pre_cond is
				when "01" =>
					pre_const(0)	<= din_hi;
					pre_const(1)	<= X"FFFF_FFFF";
				when "10" =>	
					pre_const(0) 	<= X"0000_0000";
					pre_const(1)	<= X"FFFF_FFFF";
				when "11" =>	
					pre_const(0)	<= not din_hi;
					pre_const(1)	<= X"0000_0001";
				when others => null;
				end case;
			elsif pre_sum_vld = '1' then
				pre_const(0) <= pre_const(1);
			end if;
		end if;
	end process;	
	
proc_mul_sr:
	process(clk)
	begin
		if rising_edge(clk) then
			if mul_start = '1' then
				for i in 0 to 3 loop
					cy(i) <= '0';
					pprod(i) <= ppadd_res(i) & X"00";
				end loop;	
			elsif mul_en = '1' then
				for i in 0 to 3 loop
					cy(i) <= ppadd_cyo(i);
					pprod(i) <= ppadd_res(i) & pprod(i)(8 downto 1);
				end loop;
			elsif sum_en = '1' then
				for i in 1 to 3 loop
					pprod(i-1) <= pprod(i);
				end loop;
			end if;
		end if;
	end process;	
		
gen_mul32x8pp:
	for i in 0 to 3 generate
	begin
	
	pp_adder_mux:
		process(mul_start, pp_op1, pp_op2, pp_op1_r, pp_op2_r(i), s1_r, sign1, pprod(i), cy(i))
		variable v2 : unsigned(7 downto 0);
		begin
			ppadd_cyi(i) <= '0';
			ppadd_op1(i) <= (others => '0');
			if mul_start = '1' then
				ppadd_op2(i) <= (others => '0');
				v2	:= pp_op2(8*(i+1)-1 downto 8*i);
				if v2(0) = '1' then
					ppadd_cyi(i) <= sign1;
					ppadd_op1(i) <= pp_op1;
				end if;
			else
				ppadd_op2(i) <= cy(i) & pprod(i)(39 downto 9);
				v2 := pp_op2_r(i);
				if v2(0) = '1' then
					ppadd_cyi(i) <= s1_r;
					ppadd_op1(i) <= pp_op1_r;
				end if;
			end if;	
		end process;	

	pp_adder:
		process(ppadd_op1(i), ppadd_op2(i), ppadd_cyi(i))
		variable sum_pp : unsigned(33 downto 0);
		begin
			sum_pp			:= ('0' & ppadd_op1(i) & ppadd_cyi(i)) + ('0' & ppadd_op2(i) & ppadd_cyi(i));
			ppadd_res(i)	<= sum_pp(sum_pp'left-1 downto 1);
			ppadd_cyo(i)	<= sum_pp(sum_pp'left);
		end process;	

	proc_mul_ctrl:
		process(clk)
		begin
			if rising_edge(clk) then
				if mul_start = '1' then
					pp_op2_r(i) <= '0' & pp_op2(8*(i+1)-1 downto 8*i+1);
				elsif mul_en = '1' then
					pp_op2_r(i) <= '0' & pp_op2_r(i)(7 downto 1);
				end if;
			end if;
		end process;	
		
	end generate;
	
comb_mul_adder:
	process(add_op1, add_op2)
	variable sum_mul : unsigned(39 downto 0);
	begin
		sum_mul		:= (add_op1) + (add_op2);
		add_res		<= sum_mul(sum_mul'left downto 0);
	end process;	

comb_mul_adder_mux:
	process(pre_const, pre_sum_vld, pp_reg, pprod, sr_r)
	variable s_ext : std_logic;
	begin
		if pre_sum_vld = '1' then
			add_op1 <= pp_reg(63 downto 24);
			add_op2	<= pre_const(0) & X"00";
		else
			s_ext := pp_reg(63) and sr_r;
			add_op1	<= (7 downto 0 => s_ext) & pp_reg(63 downto 32);
			add_op2	<= pprod(0);
		end if;	
	end process;	

comb_mul:
	process(clk)
	variable tmp : prod_t;
	begin
		if rising_edge(clk) then
			if mul_start = '1' then
				pp_reg	<= (others => '0');
			elsif pre_sum_vld = '1' then
				pp_reg	<= add_res & X"0000";
			elsif sum_en = '1' then
				tmp		:= add_res & pp_reg(31 downto 8);
				pp_reg	<= tmp(63 downto 8);
				result	<= tmp;
				if sr_r = '1' then
					result	<= not tmp;
				end if;
			elsif div_fin_en(1) = '1' then
				result(63 downto 32)		<= div_A(31 downto 0);
				if s1_r = '1' then
					result(63 downto 32)	<= div_add_res(31 downto 0);
				end if;	
			elsif div_fin_en(2) = '1' then
				result(31 downto 0)		<= div_Q;
				if sr_r = '1' then
					result(31 downto 0)	<= div_add_res(31 downto 0);
				end if;
			elsif hilo_we = '1' then
				if hilo_sel = '1' then
					result(63 downto 32) <= din_hi;
				else	
					result(31 downto 0) <= din_hi;
				end if;
			end if;
		end if;
	end process;	

div_adder:
	process(div_add_op1, div_add_op2, div_add_cyi)
	variable sum_div : unsigned(33 downto 0);
	begin
		sum_div			:= (div_add_op1 & div_add_cyi) + (div_add_op2 & div_add_cyi);
		div_add_res		<= sum_div(sum_div'left downto 1);
	end process;	

div_adder_mux:
	process(div_start, div_en, div_fin_en, div_A, div_Q, pp_op1, sign1, s1_r, s2_r, sr_r, div_qbit, div_M, div_M_n)
	begin
	
		div_add_cyi	<= s2_r xor div_qbit;
		div_add_op1	<= div_A(31 downto 0) & div_Q(31);
		div_add_op2	<= (others => '0');
		if div_start = '1' then
			div_add_op1	<= '0' & pp_op1;
			div_add_cyi	<= sign1;
		elsif div_en = '1' then
			div_add_op2	<= div_M;
			if div_qbit = '1' then
				div_add_op2	<= div_M_n;
			end if;
		elsif div_fin_en(0) = '1' then
			div_add_op2	<= div_M;
			div_add_cyi	<= s2_r;
			div_add_op1	<= '0' & div_A(31 downto 0);
		elsif div_fin_en(1) = '1' then
			div_add_cyi	<= s1_r;
			div_add_op1	<= '0' & not div_A(31 downto 0);
		elsif div_fin_en(2) = '1' then
			div_add_cyi	<= sr_r;
			div_add_op1	<= '0' & not div_Q;
		end if;	
	end process;	

divide:
	process(clk)
	variable quo : std_logic;
	begin
		if rising_edge(clk) then
			if div_start = '1' then
				div_qbit	<= '1';
				div_Q		<= div_add_res(31 downto 0);
				div_A		<= (others => '0');
				if sign2 = '1' then
					div_M_n		<= '1' & din_lo;
					div_M		<= '0' & not din_lo;
				else
					div_M_n		<= '1' & not din_lo;
					div_M		<= '0' & din_lo;
				end	if;
			elsif div_en = '1' then
				div_qbit	<= not div_add_res(32);
				div_A		<= div_add_res(32 downto 0);
				div_Q		<= div_Q(30 downto 0) & not div_add_res(32);
			elsif div_fin_en(0) = '1' then
				if div_A(32) = '1' then
					div_A		<= '0' & div_add_res(31 downto 0);
				end if;
			end if;
		end if;
	end process;	

--------------------------------------------------------------------------
end Behavioral;
