-----------------------------------------------------------------------
-- $Header: /tmp/cvsroot/VHDL/lib/misc/async_port_wb.vhd,v 1.5 2009-02-08 17:34:18 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
	(
		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);
		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_rd, read, leadin_wr, write, leadout_rd, leadout_wr, 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 15;
	signal cycle_reload	: natural range 0 to 15;
	signal rdy			: std_logic;
	signal rdyo			: std_logic;
	signal en			: std_logic;
	signal data_reg		: unsigned(data_width-1 downto 0);
	signal byte_en		: unsigned(byte_sel_width-1 downto 0);
	
	------------------------------------------------------------------
	begin

	SRDY_O	<= CYC_I and rdyo;
	en		<= CYC_I and STB_I;
	
	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)
	begin
		
		cycle_reload	<= async_timespec.ncyc_pulse_rst;
		cc_rst			<= '0';
		as.rst			<= '0';
		as.cs			<= '0';
		as.wr			<= '0';
		as.rd			<= '0';
		as.drive_d		<= '0';
		ack				<= '0';
		rdy				<= '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';
			if en = '1' then
				as.cs			<= '1';
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_access-1;
				if WE_I = '0' then
					sn				<= leadin_rd;
				else
					sn				<= leadin_wr;
				end if;
			end if;

		when leadin_rd =>
			as.cs		<= '1';
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_pulse_rd-1;
				as.rd			<= '1';
				sn				<= read;
			end if;

		when leadin_wr =>
			as.cs		<= '1';
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_pulse_wr-1;
				as.wr			<= '1';
				as.drive_d		<= '1';
				sn				<= write;
			end if;
	
		when read =>
			as.cs		<= '1';
			as.rd		<= '1';
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_cs_hold-1;
--				as.rd			<= '0';
				sn				<= leadout_rd;
				ack				<= '1';
			end if;
				
		when write =>
			as.drive_d	<= '1';
			as.cs		<= '1';
			as.wr		<= '1';
			if cycle_cnt = 0 then
				as.wr			<= '0';
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_cs_hold-1;
				sn				<= leadout_wr;
--				ack				<= '1';
			end if;

		when leadout_rd =>
			as.cs		<= '1';
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_release-1;
				sn				<= release;
				as.cs			<= '0';
			end if;

		when leadout_wr =>
			as.cs		<= '1';
			as.drive_d	<= '1';
			if cycle_cnt = 0 then
				cc_rst			<= '1';
				cycle_reload	<= async_timespec.ncyc_release-1;
				sn				<= release;
				as.cs			<= '0';
			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_be	<= (byte_sel_width-1 downto 0 => not async_timespec.pol_be) xor ((byte_sel_width-1 downto 0 => as.cs) and byte_en);
			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');
				byte_en		<= (others => '0');
			elsif en = '1' and rdy = '1' then
				async_a 	<= ADDR_I(addr_width-1 downto 0);
				byte_en		<= SEL_I(byte_sel_width-1 downto 0);
			end if;	
		end if;
	end process;

	------------------------------------------------------------------
	data_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if en = '1' and rdy = '1' then
				data_reg 	<= DAT_I(data_width-1 downto 0);
			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 	<= data_reg;
			end if;	
		end if;
	end process;

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

end Behavioral;
