LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

ENTITY ac97_wb IS
	Generic
	(
		fifo_depth		: natural := 8
	);
	Port
	(
		-- J-Bus domain
		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;
		ADDR_I			: in  unsigned(31 downto 0);
		DAT_I			: in  unsigned(31 downto 0);
		DAT_O			: out unsigned(31 downto 0);
		INT_O			: out STD_LOGIC;
		
		-- AC97 domain
		ac97_sdata_in	: in std_logic;
		ac97_bit_clk	: in  std_logic;          
		ac97_reset_n	: out std_logic;
		ac97_sdata_out	: out std_logic;
		ac97_ssync		: out std_logic
	);
END ac97_wb;

ARCHITECTURE behavior OF ac97_wb IS

	constant ac_data_width			: natural := 20;
	constant pcm_data_width			: natural := 18;
	constant stat_data_width		: natural := 16;
	signal cmd_reg_we				: std_logic;
	signal cmd_pending				: std_logic;
	signal ac_cmd_data				: unsigned(ac_data_width-1 downto 0);
	signal ac_cmd_addr				: unsigned(ac_data_width-1 downto 0);
	signal ac_cmd_we				: std_logic;
	signal ac_tx_pcm_we				: std_logic;

	signal stat_reg					: unsigned(stat_data_width-1 downto 0);
	signal stat_reg_re				: std_logic;
	signal stat_valid				: std_logic;
	signal stat_pending				: std_logic;
	signal ac_stat_data				: unsigned(ac_data_width-1 downto 0);
	signal ac_stat_addr				: unsigned(ac_data_width-1 downto 0);
	signal ac_stat_read				: std_logic;
	signal ac_stat_valid			: std_logic;

	signal ac_tx_pcm_data			: unsigned(ac_data_width-1 downto 0);
	signal ac_tx_pcm_addr			: unsigned (3 downto 0);
	signal ac_rx_pcm_data			: unsigned(ac_data_width-1 downto 0);
	signal ac_rx_pcm_addr			: unsigned (3 downto 0);
	signal ac_sync_strobe			: unsigned(0 to 2);
	signal ac_slot_valid			: unsigned(0 to 12);
	signal ac_ready					: std_logic;
	signal rx_frame_valid			: std_logic;
	signal sync_frame				: std_logic;
	signal sync_tx					: std_logic;
	signal sync_status				: std_logic;
	signal request					: unsigned(3 to 12);

	signal tx_data_start			: std_logic;
	signal tx_fifo_din				: unsigned(pcm_data_width-1 downto 0);
	signal tx_fifo_dout				: unsigned(pcm_data_width-1 downto 0);
	signal tx_fifo_re				: std_logic;
	signal tx_fifo_we				: std_logic;
	signal tx_fifo_full				: std_logic;
	signal tx_fifo_empty			: std_logic;
	signal tx_fifo_almost_full		: std_logic;
	signal tx_fifo_almost_empty		: std_logic;

	signal rx_data_irq				: std_logic;
	signal rx_data_ack				: std_logic;

	signal ac97_stat				: unsigned(31 downto 0);
	signal addr_reg					: unsigned(31 downto 0);
	signal data_reg					: unsigned(31 downto 0);
	signal int_en_frame_sync		: std_logic;
	signal int_en_rx				: std_logic;
	signal int_en_tx				: std_logic;
	signal rx_en					: std_logic;
	signal tx_en					: std_logic;
	signal rx_fifo_rst				: std_logic;
	signal tx_fifo_rst				: std_logic;

	function GetRequest(slot1 : unsigned) return unsigned is
	variable res : unsigned(3 to 12);
	begin
		res := (others => '0');
		for i in res'range loop
			res(i) := not slot1(14-i);
		end loop;
		return res;	

	end GetRequest;

	type ac_state_t is (reset, idle, pcm_3, pcm_4);
	signal rx_state, rx_state_n : ac_state_t;

begin

	SRDY_O			<= CYC_I;
	INT_O			<= (rx_data_irq and int_en_rx) or (not tx_fifo_full and int_en_tx) or (sync_frame and int_en_frame_sync) ;
	
	rx_frame_valid	<= ac_slot_valid(0);
	sync_frame		<= ac_sync_strobe(0);
	sync_status		<= ac_sync_strobe(1);
	sync_tx			<= ac_sync_strobe(2);
	request			<= GetRequest(ac_stat_addr);
	
	ac_stat_read	<= not addr_reg(9);
	ac_stat_valid	<= '1' when (sync_status = '1' and ac_slot_valid(0 to 2) = "111") else '0';
	ac97_stat		<= ac_ready & '0' & X"00000" & '0' & int_en_rx & int_en_tx & int_en_frame_sync & '0' & tx_fifo_full & rx_data_irq & tx_fifo_almost_full & stat_valid & cmd_pending;
	
command_logic:
	process (CLK_I)
	begin
	
		if rising_edge(CLK_I) then
			ac_cmd_we	<= '0';
			if RST_I = '1' then
				cmd_pending		<= '0';
			elsif cmd_reg_we = '1' and cmd_pending = '0' then
				cmd_pending		<= '1';
			elsif (rx_frame_valid and sync_tx) = '1' and cmd_pending = '1' then
				ac_cmd_we		<= '1';
				cmd_pending		<= '0';
			end if;	
		end if;	
	end process;

status_logic:
	process (CLK_I)
	begin
	
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				stat_pending	<= '0';
			elsif cmd_reg_we = '1' and cmd_pending = '0' then
				stat_pending	<= ac_stat_read;
			elsif ac_stat_valid = '1' then
				stat_pending	<= '0';
			end if;	
		end if;	
	end process;

command_register:
	process (CLK_I)
	begin
		if rising_edge(CLK_I) then
			if cmd_reg_we = '1' and cmd_pending = '0' then
				ac_cmd_addr		<= ac_stat_read & addr_reg(8 downto 2) & X"000";
				ac_cmd_data		<= data_reg(stat_data_width-1 downto 0) & X"0";
			end if;	
		end if;	
	end process;

status_register:
	process (CLK_I)
	begin
	
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				stat_valid		<= '0';
			elsif stat_pending = '1' and ac_stat_valid = '1' then
				stat_reg		<= ac_stat_data(ac_data_width-1 downto ac_data_width-stat_data_width);
				stat_valid		<= '1';
			elsif stat_reg_re = '1' then
				stat_valid		<= '0';
			end if;	
		end if;	
	end process;

	-- Instantiate synchronous FIFO
inst_tx_fifo: entity work.fifo_sync
	GENERIC MAP
	(
		addr_width			=> fifo_depth,
		data_width			=> pcm_data_width,
		almost_full_thresh	=> 90*(2**fifo_depth)/100,
		almost_empty_thresh	=> 10*(2**fifo_depth)/100
	)
	PORT MAP
	(
		rst			=>	tx_fifo_rst,
		clk			=>	CLK_I,
		we			=>	tx_fifo_we,
		re			=>	tx_fifo_re,
		fifo_full	=>	tx_fifo_full,
		fifo_empty	=>	tx_fifo_empty,
		fifo_afull	=>	tx_fifo_almost_full,
		fifo_aempty	=>	tx_fifo_almost_empty,
		data_w		=>  tx_fifo_din,
		data_r		=>  tx_fifo_dout
	);
	tx_fifo_rst		<= not tx_en or RST_I;
	
tx_logic:
	process (CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' or tx_en = '0' then
				ac_tx_pcm_addr 	<= "0011";
				tx_data_start	<= '0';
			elsif sync_tx = '1' and (request(3) and request(4)) = '1' then
				ac_tx_pcm_addr 	<= "0011";
				tx_data_start	<= '1';
			elsif ac_tx_pcm_addr /= "0100" then
				ac_tx_pcm_addr	<= ac_tx_pcm_addr + 1;
			else
				tx_data_start	<= '0';
			end if;	
		end if;	
	end process;

	ac_tx_pcm_we	<= not tx_fifo_empty and tx_data_start;
	tx_fifo_re		<= ac_tx_pcm_we;
	
	tx_fifo_din		<= data_reg(pcm_data_width-1 downto 0);
	
	ac_rx_pcm_addr	<= addr_reg(5 downto 2);

rx_logic:
	process (CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				rx_data_irq	<= '0';
			elsif rx_frame_valid = '1' and sync_frame = '1' then 
				if ac_slot_valid(3) = '1' and ac_slot_valid(4) = '1' then
					rx_data_irq	<= rx_en;
				end if;
			elsif rx_data_ack = '1' then
				rx_data_irq	<= '0';
			end if;
		end if;
	end process;	

inst_ac_io : entity work.ac_io
    PORT MAP
    (
		rst				=> RST_I,
		clk				=> CLK_I,
		ready			=> ac_ready,
		sync_strobe		=> ac_sync_strobe,
		slot_valid		=> ac_slot_valid,
		stat_addr		=> ac_stat_addr,
		stat_data		=> ac_stat_data,
		rx_pcm_addr		=> ac_rx_pcm_addr,
		rx_pcm_data		=> ac_rx_pcm_data,
		cmd_addr		=> ac_cmd_addr,
		cmd_data		=> ac_cmd_data,
		cmd_we			=> ac_cmd_we,
		tx_pcm_addr		=> ac_tx_pcm_addr,
		tx_pcm_data		=> ac_tx_pcm_data,
		tx_pcm_we		=> ac_tx_pcm_we,
		ac_sdata_in		=> ac97_sdata_in,
		ac_bit_clk		=> ac97_bit_clk,          
		ac_reset_n		=> ac97_reset_n,
		ac_sdata_out	=> ac97_sdata_out,
		ac_ssync		=> ac97_ssync
	);

	ac_tx_pcm_data		<= tx_fifo_dout & (ac_data_width-pcm_data_width-1 downto 0 => '0');

slave_data_out:
	process	(CLK_I)
	begin
		
		if rising_edge(CLK_I) then
			tx_fifo_we		<= '0';
			stat_reg_re		<= '0';
			cmd_reg_we		<= '0';
			rx_data_ack		<= '0';
			ACK_O			<= '0';
			if RST_I = '1' then
				addr_reg			<= (others => '0');
				int_en_frame_sync	<= '0';
				int_en_rx			<= '0';
				int_en_tx			<= '0';
				tx_en				<= '0';
				rx_en				<= '0';
			else
--				DAT_O			<= (others => '-');

				if (STB_I and CYC_I) = '1' then
					ACK_O		<= not WE_I;

					case ADDR_I(11 downto 10) is 

					when "00" =>
						DAT_O				<= ac97_stat;
						if WE_I = '1' then
							int_en_frame_sync	<= DAT_I(0);
							int_en_tx			<= DAT_I(1);
							int_en_rx			<= DAT_I(2);
							tx_en				<= DAT_I(4);
							rx_en				<= DAT_I(5);
						end if;
					when "01" =>
						addr_reg			<= ADDR_I;
						data_reg			<= DAT_I;
						stat_reg_re			<= not WE_I;
						cmd_reg_we			<= WE_I;
						DAT_O				<= (31 downto stat_data_width => '0') & stat_reg;

					when "10" =>
						rx_data_ack			<= not WE_I;
						addr_reg			<= ADDR_I;
						data_reg			<= DAT_I;
						tx_fifo_we			<= WE_I;
						DAT_O				<= (31 downto pcm_data_width => ac_rx_pcm_data(ac_data_width-1)) & ac_rx_pcm_data(ac_data_width-1 downto ac_data_width-pcm_data_width);

					when others => null;

					end case;
				end if;
			end if;
		end if;
		
	end process;
	
end behavior;
