-----------------------------------------------------------------------
-- $Header: D:\usr\cvsroot/VHDL/lib/misc/async_port_wb.vhd,v 1.16 2010/02/09 09:56:37 Jens Exp $
-----------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.async_types.all;

	------------------------------------------------------------------
entity async_port_wb is
	Generic
	(
		f_sysclk		: real		:= 100.0;
		addr_width		: natural	:= 32;
		data_width		: natural	:= 32;
		byte_sel_width	: natural	:= 4;
		async_timespec	: async_timespec_t
	);
	Port
	(
		CLK_I			: in  STD_LOGIC;
		RST_I			: in  STD_LOGIC;
		CYC_I			: in  STD_LOGIC;
		STB_I			: in  STD_LOGIC;
		WE_I			: in  STD_LOGIC;
		ACK_O			: out STD_LOGIC;
		SRDY_O			: out STD_LOGIC;
		MRDY_I			: in  STD_LOGIC;
		SEL_I			: in  unsigned(3 downto 0);
		ADDR_I			: in  unsigned(31 downto 0);
		DAT_I			: in  unsigned(31 downto 0);
		DAT_O			: out unsigned(31 downto 0);
		page_mode_en	: in  STD_LOGIC;
		async_a			: out unsigned(addr_width-1 downto 0);
		async_d			: inout unsigned(data_width-1 downto 0);
		async_cs		: out std_logic;
		async_wr		: out std_logic;
		async_rd		: out std_logic;
		async_be		: out unsigned(byte_sel_width-1 downto 0);
		async_rst		: out std_logic
	);
end async_port_wb;

architecture Behavioral of async_port_wb is

	type async_t is record
		cs		: std_logic;
		wr		: std_logic;
		rd		: std_logic;
		rst		: std_logic;
		drive_d	: std_logic;
	end record;
	
	type async_state_t is (start, reset, idle, leadin, pulse, leadout, release);
	
	signal s, sn			: async_state_t;
	signal as				: async_t;
	signal ack				: std_logic;
	signal cc_rst			: std_logic;
	signal cycle_cnt		: natural range 0 to 31;
	signal cycle_reload		: natural range 0 to 31;
	signal rdy				: std_logic;
	signal rdyo				: std_logic;
	signal en				: std_logic;
	signal is_idle			: std_logic;
	signal DAT_I_r			: unsigned(data_width-1 downto 0);
	signal SEL_I_r			: unsigned(byte_sel_width-1 downto 0);
	signal WE_I_r			: std_logic;
	signal ADDR_I_r			: unsigned(addr_width-1 downto 0);
	signal page_mode_en_r	: std_logic;
	signal do_page_read		: std_logic;
	
	------------------------------------------------------------------
	begin
	
	ASSERT to_cycles(async_timespec.T_pulse_rd, f_sysclk) > 0 report "Read pulse length must be greater than zero!" severity failure;
	ASSERT to_cycles(async_timespec.T_pulse_wr, f_sysclk) > 0 report "Write pulse length must be greater than zero!" severity failure;
	ASSERT (not async_timespec.can_page_rd OR (to_cycles(async_timespec.T_pulse_page_rd, f_sysclk) > 1)) report "Read page pulse length must be greater than one!" severity failure;

	SRDY_O	<= CYC_I and rdyo;
	en		<= CYC_I and STB_I;
	
	do_page_read	<= '1' when (async_timespec.can_page_rd and (WE_I = '0') and (WE_I_r = '0') and (ADDR_I(addr_width-1 downto async_timespec.nbits_page_rd) = ADDR_I_r(addr_width-1 downto async_timespec.nbits_page_rd))) else '0';
	
	proc_cycle_counter:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if cc_rst = '1' then
				cycle_cnt <= cycle_reload;
			elsif cycle_cnt /= 0 then
				cycle_cnt <= cycle_cnt - 1;
			end if;
		end if;
	end process;
	
	------------------------------------------------------------------
	proc_state_next:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				s <= start;
			else
				s <= sn;
			end if;
		end if;
	end process;
	
	proc_state:
	process(s, cycle_cnt, en, WE_I, WE_I_r, do_page_read)
	begin
		
		cycle_reload	<= to_cycles(async_timespec.T_pulse_rst, f_sysclk);
		cc_rst			<= '0';
		as.rst			<= '0';
		as.cs			<= '0';
		as.wr			<= '0';
		as.rd			<= '0';
		as.drive_d		<= '0';
		ack				<= '0';
		rdy				<= '0';
		is_idle			<= '0';
		
		sn	<= s;
		case s is
		
		when start =>
			cc_rst	<= '1';
			sn		<= reset;
		when reset =>
			as.rst	<= '1';
			if cycle_cnt = 0 then
				sn	<= idle;
			end if;
		when idle =>
			rdy			<= '1';
			is_idle		<= '1';
			if en = '1' then
				as.cs			<= '1';
				cc_rst			<= '1';
				if to_cycles(async_timespec.T_leadin, f_sysclk) = 0 then
					sn			<= pulse;
					if WE_I = '1' then
						cycle_reload	<= 0;
						sn				<= leadin;	-- always lead-in for writes
					else
						cycle_reload	<= to_cycles(async_timespec.T_pulse_rd, f_sysclk) - 1;
						as.rd			<= '1';
					end if;
				else
					cycle_reload	<= to_cycles(async_timespec.T_leadin, f_sysclk) - 1;
					sn				<= leadin;
				end if;	
			end if;

		when leadin =>
			as.cs		<= '1';
			as.rd		<= not WE_I_r;
			as.drive_d	<= WE_I_r;
			as.wr		<= WE_I_r;
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				sn				<= pulse;
				if WE_I_r = '1' then
					cycle_reload	<= to_cycles(async_timespec.T_pulse_wr, f_sysclk) - 1;
				else
					cycle_reload	<= to_cycles(async_timespec.T_pulse_rd, f_sysclk) - 1;
				end if;
			end if;

		when pulse =>
			as.cs		<= '1';
			as.rd		<= not WE_I_r;
			as.drive_d	<= WE_I_r;
			as.wr		<= WE_I_r;
			if cycle_cnt = 0 then
				as.wr			<= '0';
				cc_rst			<= '1';
				ack				<= not WE_I_r;
				rdy				<= do_page_read;
				if en = '1' and do_page_read = '1' then
					cycle_reload	<= to_cycles(async_timespec.T_pulse_page_rd, f_sysclk) - 1;
					sn				<= pulse;
				elsif to_cycles(async_timespec.T_leadout, f_sysclk) = 0 then
					if to_cycles(async_timespec.T_release, f_sysclk) = 0 then
						sn				<= idle;
					else
						cycle_reload	<= to_cycles(async_timespec.T_release, f_sysclk) - 1;
						sn				<= release;
					end if;
				else
					cycle_reload	<= to_cycles(async_timespec.T_leadout, f_sysclk) - 1;
					sn				<= leadout;
				end if;
			end if;
				
		when leadout =>
			as.cs		<= '1';
			as.drive_d	<= WE_I_r;
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				if to_cycles(async_timespec.T_release, f_sysclk) = 0 then
					sn				<= idle;
				else
					cycle_reload	<= to_cycles(async_timespec.T_release, f_sysclk) - 1;
					sn				<= release;
				end if;
			end if;

		when release =>
			if cycle_cnt = 0 then
				sn				<= idle;
			end if;

		when others =>
			sn <= idle;
			
		end case;		
	end process;

	------------------------------------------------------------------
	output_ctrl:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			async_cs	<= (not async_timespec.pol_cs) xor as.cs;
			async_wr	<= (not async_timespec.pol_we) xor as.wr;
			async_rd	<= (not async_timespec.pol_oe) xor as.rd;
			async_rst	<= (not async_timespec.pol_rst) xor as.rst;
		end if;
	end process;

	------------------------------------------------------------------
	din_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if as.rd = '1' then
				DAT_O(data_width-1 downto 0) 	<= async_d;
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	output_addr:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				async_a		<= (others => '0');
				SEL_I_r		<= (others => '0');
			elsif en = '1' and rdy = '1' then
				async_a 	<= ADDR_I(addr_width-1 downto 0);
				async_be	<= (byte_sel_width-1 downto 0 => not async_timespec.pol_be) xor ((byte_sel_width-1 downto 0 => as.cs) and SEL_I(byte_sel_width-1 downto 0));
				SEL_I_r		<= SEL_I(byte_sel_width-1 downto 0);
				if is_idle = '1' then
					ADDR_I_r 	<= ADDR_I(addr_width-1 downto 0);
				end if;	
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	data_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			page_mode_en_r	<= page_mode_en;
			if en = '1' and rdy = '1' then
				DAT_I_r 		<= DAT_I(data_width-1 downto 0);
				WE_I_r			<= WE_I;
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	output_SRDY:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				rdyo 	<= '1';
			elsif en = '1' then
				rdyo 	<= rdy and not rdyo;
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	output_ACK:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				ACK_O 	<= '0';
			else
				ACK_O 	<= ack;
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	output_data:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			async_d 	<= (others => 'Z');
			if as.drive_d = '1' then
				async_d 	<= DAT_I_r;
			end if;	
		end if;
	end process;

	------------------------------------------------------------------

end Behavioral;
