LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
use std.textio.all; --  Imports the standard textio package.

use work.emac_types.all;
use work.utils_pkg.all;

ENTITY emac_tx IS
	Generic
	(
		f_sysclk		: real		:= 100.0;
		RAM_SIZE		: natural	:= 2048
	);
	Port
	(
		clk				: in  STD_LOGIC;
		rst				: in  STD_LOGIC;
		din_vld			: in  STD_LOGIC;
		din				: in  unsigned(31 downto 0);
		ctrl_in			: in  tx_ctrl_in_t;
		ctrl_out		: out tx_ctrl_out_t;
		mii_tx_clk		: in  STD_LOGIC;
		mii_tx_en		: out STD_LOGIC;
		mii_tx_er		: out STD_LOGIC;
		mii_tx			: out unsigned(7 downto 0)
		
	);
END emac_tx;

ARCHITECTURE behavior OF emac_tx IS

	constant RAM_ADDR_WIDTH	: natural := NextExpBaseTwo(RAM_SIZE);
	
	subtype word_ptr_t is unsigned(RAM_ADDR_WIDTH-1 downto 0);
	
	signal cmd_fifo_din		: unsigned(2*word_ptr_t'length-1 downto 0);
	signal cmd_fifo_dout	: unsigned(2*word_ptr_t'length-1 downto 0);
	signal cmd_fifo_we		: std_logic;
	signal cmd_fifo_re		: std_logic;
	signal cmd_fifo_empty	: std_logic;
	signal cmd_fifo_full	: std_logic;

	signal reset_en			: std_logic;

	signal fill_base		: word_ptr_t;
	signal fill_size		: word_ptr_t;
	signal fill_cnt			: word_ptr_t;
	signal fill_ptr			: word_ptr_t;
	signal fill_pre_rdy		: std_logic;
	signal fill_bsy			: std_logic;
	signal fill_remain		: unsigned(1 downto 0);
	signal fill_set			: std_logic;
	signal fill_en			: std_logic;
	signal fill_cnt_en		: std_logic;

	signal alloc_OK			: std_logic;
	signal alloc_req		: std_logic;
	signal alloc_ack		: std_logic;
	signal alloc_size		: word_ptr_t;
	signal alloc_remain		: unsigned(1 downto 0);
	
	signal commit_en		: std_logic;
	signal uncommit_en		: std_logic;
	signal mem_alloc_en		: std_logic;
	signal mem_free_en		: std_logic;
	signal nwords_free		: word_ptr_t;
	signal mem_free_req		: std_logic;
	signal mem_free_ack		: unsigned(1 downto 0);

	signal xfer_size		: word_ptr_t;
	signal xfer_ptr			: word_ptr_t;
	signal xfer_cnt			: word_ptr_t;
	signal xfer_set			: std_logic;
	signal xfer_en			: std_logic;
	signal xfer_cnt_en		: std_logic;
	signal xfer_bsy			: std_logic;
	signal xfer_free_en		: std_logic;

	signal ram_we_a			: std_logic;
	signal ram_addr_a		: word_ptr_t;
	signal ram_din_a		: unsigned(35 downto 0);
	signal ram_addr_b		: word_ptr_t;
	signal ram_dout_b		: unsigned(35 downto 0);

	signal piso32_din		: unsigned(31 downto 0);
	signal piso32_din_be		: unsigned(3 downto 0);
	signal piso32_din_re	: std_logic;
	signal piso32_din_vld	: std_logic;

	signal piso32_dout		: unsigned(7 downto 0);
	signal piso32_dout_vld	: std_logic;
	signal piso32_shift_en	: std_logic;
	
	signal piso8_dout_vld	: std_logic;
	signal piso8_din_re	: std_logic;
	signal piso8_din_vld	: std_logic;
	signal piso8_dout		: unsigned(3 downto 0);
	signal piso8_din		: unsigned(7 downto 0);
	
	signal reset_pipe		: unsigned(31 downto 0);
	signal Gbps_en			: std_logic;
	signal fcs_gen_en		: std_logic;
	
	signal fcs_inject_en	: std_logic;
	signal fcs_rst			: std_logic;
	signal fcs_en			: unsigned(7 downto 0);
	signal fcs_vld			: std_logic;
	signal fcs_din_vld		: std_logic;
	signal fcs				: unsigned(31 downto 0);
	signal fcs_rev_endian	: unsigned(31 downto 0);
	
	signal mii_fifo_we		: std_logic;
	signal mii_fifo_re		: std_logic;
	signal mii_fifo_full	: std_logic;
	signal mii_fifo_empty	: std_logic;
	signal mii_fifo_re_dly	: unsigned(23 downto 0);

	signal mii_bsy			: std_logic;

	type host_state_t is (host_init, host_idle, host_alloc, host_setup, host_arm, host_fill, host_commit);
	signal host_s, host_sn : host_state_t;
	
	type xfer_state_t is (xfer_init, xfer_idle, xfer_start, xfer_preamble0, xfer_preamble1, xfer_active, xfer_crc, xfer_stop, xfer_finish);
	signal xfer_s, xfer_sn : xfer_state_t;

	type piso_byte_mask_array_t is array (0 to 3) of unsigned (3 downto 0);
	constant piso_byte_mask_rom : piso_byte_mask_array_t :=
	(
		"1111",
		"1000",
		"1100",
		"1110"
	);
	
	signal preamble_en		: std_logic;
	signal preamble_addr	: natural range 0 to 1;
	type preamble_t is array (0 to 1) of unsigned(31 downto 0);
	constant preamble : preamble_t :=
	(
		X"55555555",
		X"555555D5"
	);
	

begin

	ctrl_out.pkt_alloc_req	<= alloc_req;
	ctrl_out.pkt_done		<= cmd_fifo_empty;
	ctrl_out.reset_busy		<= reset_en;
	
	ram_we_a				<= din_vld and fill_bsy;
	ram_din_a(31 downto 0)	<= din;
	ram_din_a(35 downto 32)	<= piso_byte_mask_rom(to_integer(fill_remain)) when fill_pre_rdy = '1' else "1111";
	ram_addr_a				<= fill_ptr;
	
	piso32_din				<= preamble(preamble_addr) when preamble_en = '1' else fcs_rev_endian when fcs_inject_en = '1' else ram_dout_b(31 downto 0);
	piso32_din_be			<= "1111" when (preamble_en = '1' or fcs_inject_en = '1') else ram_dout_b(35 downto 32);

	cmd_fifo_we				<= commit_en;
	cmd_fifo_re				<= uncommit_en;
	cmd_fifo_din			<= fill_size & fill_base;
	ram_addr_b				<= xfer_ptr;
	
	reset_en				<= reset_pipe(reset_pipe'left);
	
	xfer_cnt_en				<= xfer_bsy and piso32_din_re;
	fill_cnt_en				<= fill_bsy and din_vld;
	mem_free_en				<= not mem_free_ack(0) and mem_free_ack(1);
	
	fcs_rev_endian			<= fcs(7 downto 0) & fcs(15 downto 8) & fcs(23 downto 16) & fcs(31 downto 24);
	
------------------------------------------------------------------
reset_gen:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' or ctrl_in.reset = '1' then
				reset_pipe	<= (others => '1');
			else
				reset_pipe	<= reset_pipe(reset_pipe'left-1 downto 0) & '0';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
host_state_next:
	process(clk)
	begin
		if rising_edge(clk) then
			if reset_en = '1' then
				host_s	<= host_init;
			else
				host_s	<= host_sn;
			end if;
		end if;
	end process;	

host_state:
	process(host_s, alloc_OK, fill_bsy, ctrl_in.pkt_commit_en, alloc_req, mem_free_ack)
	begin
	
		fill_set			<= '0';
		fill_en				<= '0';
		commit_en			<= '0';
		alloc_ack			<= '0';
		mem_alloc_en		<= '0';
		
		host_sn				<= host_s;
		
		case host_s is
		
		when host_init =>
			host_sn 		<= host_idle;
		
		when host_idle =>
			if alloc_req = '1' then
				host_sn		<= host_alloc;
			end if;

		when host_alloc =>
			if alloc_OK = '1' then
				host_sn		<= host_setup;
			else
				host_sn 	<= host_idle;
				alloc_ack	<= '1';
			end if;

		when host_setup =>
			fill_set		<= '1';
			host_sn			<= host_arm;

		when host_arm =>
			fill_en			<= '1';
			alloc_ack		<= '1';
			host_sn			<= host_fill;

		when host_fill =>
			fill_en			<= '1';
			if alloc_req = '1' then
				host_sn		<= host_alloc;
			elsif ctrl_in.pkt_commit_en = '1' then
				host_sn		<= host_commit;
			end if;
		
		when host_commit =>
			if mem_free_ack = "00" then
				commit_en		<= '1';
				mem_alloc_en	<= '1';
				host_sn			<= host_idle;
			end if;

		when others => 
			host_sn			<= host_idle;
		end case;

	end process;

------------------------------------------------------------------
-- Allocation stuff
------------------------------------------------------------------
alloc_request_logic:
	process(clk)
	begin
		if rising_edge(clk) then
			if reset_en = '1' then
				alloc_req		<= '0';
			elsif ctrl_in.pkt_alloc_en = '1' and alloc_req = '0' then
				alloc_req		<= '1';
				alloc_remain	<= ctrl_in.tx_size(1 downto 0);
				alloc_size		<= ctrl_in.tx_size(word_ptr_t'left+2 downto 2);
				if ctrl_in.tx_size(1 downto 0) /= "00" then
					alloc_size	<= ctrl_in.tx_size(word_ptr_t'left+2 downto 2) + 1;
				end if;
				ctrl_out.tx_size	<= resize(ctrl_in.tx_size(word_ptr_t'left downto 0), 16);
			elsif alloc_ack = '1' then
				alloc_req				<= '0';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
host_alloc_armed_register:
	process(clk)
	begin
		if rising_edge(clk) then
			if reset_en = '1' or commit_en = '1' then
				ctrl_out.pkt_armed		<= '0';
			elsif alloc_ack = '1' then
				ctrl_out.pkt_armed		<= alloc_OK;
			end if;
		end if;
	end process;	

------------------------------------------------------------------
alloc_eval_logic:
	process(clk)
	begin
		if rising_edge(clk) then
			alloc_OK	<= '0';
			if alloc_size < nwords_free then
				alloc_OK	<= '1';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
fill_base_logic:
	process(clk)
	begin
		if rising_edge(clk) then
			if reset_en = '1' then
				fill_base		<= (others => '0');
			elsif commit_en = '1' then
				fill_base		<= fill_ptr;
			end if;
		end if;
	end process;	

------------------------------------------------------------------
mem_free_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if reset_en = '1' then
				nwords_free		<= (others => '1');
			elsif mem_alloc_en = '1' then
				nwords_free		<= nwords_free - fill_size;
			elsif mem_free_en = '1' then
				nwords_free		<= nwords_free + xfer_size;
			end if;
		end if;
	end process;	

------------------------------------------------------------------
mem_free_mii:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			if reset_en = '1' then
				mem_free_req	<= '0';
			elsif mem_free_ack(0) = '0' then
				if xfer_free_en = '1' then
					mem_free_req	<= '1';
				end if;
			else
				mem_free_req	<= '0';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
mem_free_host:
	process(clk)
	begin
		if rising_edge(clk) then
			mem_free_ack(1)	<= mem_free_ack(0);
			if reset_en = '1' then
				mem_free_ack	<= "00";
			elsif mem_free_req = '1' then
				if mem_alloc_en = '0' then
					mem_free_ack(0)	<= '1';
				end if;	
			else
				mem_free_ack(0)	<= '0';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
-- Fill stuff
------------------------------------------------------------------
fill_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if fill_set = '1' then	
				fill_bsy		<= '0';
				fill_pre_rdy	<= '0';
				fill_cnt		<= alloc_size;
				fill_size		<= alloc_size;
				fill_remain		<= alloc_remain;
			elsif fill_en = '1' then
				fill_bsy	<= '1';
				if fill_cnt_en = '1' or fill_bsy = '0' then
					if fill_cnt /= 1 then
						fill_cnt	<= fill_cnt - 1;
					else
						fill_pre_rdy	<= '1';
					end if;
					if fill_pre_rdy = '1' then
						fill_bsy	<= '0';
					end if;
				end if;
			end if;
		end if;
	end process;	

------------------------------------------------------------------
fill_pointer:
	process(clk)
	begin
		if rising_edge(clk) then
			if fill_set = '1' then
				fill_ptr		<= fill_base;
			elsif fill_cnt_en = '1' then
				fill_ptr		<= fill_ptr + 1;
			end if;
		end if;
	end process;	

------------------------------------------------------------------
-- Transfer stuff
------------------------------------------------------------------
host2xfer_sync_register:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			Gbps_en			<= ctrl_in.Gbps_en;
			fcs_gen_en		<= ctrl_in.fcs_gen_en;
		end if;	
	end process;	

------------------------------------------------------------------
xfer_counter:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			xfer_bsy		<= '0';
			if xfer_set = '1' then
				xfer_cnt 		<= cmd_fifo_dout(cmd_fifo_dout'left downto word_ptr_t'length);
				xfer_size		<= (others => '0');
			elsif xfer_en = '1' then
				xfer_bsy	<= '1';
				if xfer_cnt_en = '1' or xfer_bsy = '0' then
					if xfer_cnt /= 0 then
						xfer_cnt	<= xfer_cnt - 1;
						xfer_size	<= xfer_size + 1;
					else
						xfer_bsy	<= '0';
					end if;
				end if;
			end if;
		end if;	
			
	end process;	
	
------------------------------------------------------------------
xfer_pointer:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			if xfer_set = '1' then
				xfer_ptr		<= cmd_fifo_dout(xfer_ptr'left downto 0);
			elsif xfer_en = '1' then
				if xfer_bsy = '0' or xfer_cnt_en = '1' then
					xfer_ptr		<= xfer_ptr + 1;
				end if;
			end if;
		end if;	
			
	end process;	
	
------------------------------------------------------------------
xfer_state_next:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			if reset_en = '1' then
				xfer_s	<= xfer_init;
			else
				xfer_s	<= xfer_sn;
			end if;
		end if;
	end process;	

xfer_state:
	process(xfer_s, mii_bsy, cmd_fifo_empty, piso32_din_re, piso32_dout_vld, xfer_bsy, fcs_gen_en)
	begin
		
		piso32_din_vld		<= '0';
		preamble_en			<= '0';
		preamble_addr		<= 0;
		xfer_set			<= '0';
 		xfer_en				<= '0';
		xfer_free_en		<= '0';
 		fcs_rst				<= '0';
 		fcs_inject_en		<= '0';
 		uncommit_en			<= '0';
		
		xfer_sn			<= xfer_s;
		
		case xfer_s is
		
		when xfer_init =>
			xfer_sn 		<= xfer_idle;
			
		when xfer_idle =>
			if cmd_fifo_empty = '0' and mii_bsy = '0' then
				xfer_sn 		<= xfer_start;
			end if;
			
		when xfer_start =>
			if cmd_fifo_empty = '0' then
				xfer_sn 		<= xfer_preamble0;
			end if;

		when xfer_preamble0 =>
			xfer_set		<= '1';
 			fcs_rst			<= '1';
			preamble_addr	<= 0;
			preamble_en		<= '1';
			piso32_din_vld	<= '1';
			if piso32_din_re = '1' then
				xfer_sn 		<= xfer_preamble1;
			end if;

		when xfer_preamble1 =>
			preamble_addr	<= 1;
			preamble_en		<= '1';
			piso32_din_vld	<= '1';
			if piso32_din_re = '1' then
				xfer_sn 		<= xfer_active;
 				xfer_en			<= '1';
			end if;

		when xfer_active =>
			piso32_din_vld	<= xfer_bsy;
			xfer_en			<= '1';
			if xfer_bsy = '0' then
				xfer_sn 		<= xfer_stop;
			end if;

		when xfer_stop =>
			if piso32_dout_vld = '0' then
				xfer_sn 		<= xfer_finish;
				if fcs_gen_en = '1' then
					xfer_sn 		<= xfer_crc;
				end if;
			end if;

		when xfer_crc =>
			fcs_inject_en	<= piso32_din_re;
			piso32_din_vld	<= '1';
			if piso32_din_re = '1' then
				xfer_sn 		<= xfer_finish;
			end if;

		when xfer_finish =>
			uncommit_en		<= '1';
			xfer_free_en	<= '1';
			xfer_sn		<= xfer_idle;

		when others => 
			xfer_sn		<= xfer_idle;
		end case;

	end process;

------------------------------------------------------------------
inst_ram : entity work.dpram_1w1r2c_ro
	GENERIC MAP 
	(
		addr_width	=> RAM_ADDR_WIDTH,
		data_width	=> ram_din_a'length
	)
	PORT MAP 
	(
		clk_a		=> clk,
		clk_b		=> mii_tx_clk,
		we_a		=> ram_we_a,
		re_b		=> piso32_din_re,
		addr_a		=> ram_addr_a,
		addr_b		=> ram_addr_b,
		din_a		=> ram_din_a,
		dout_b		=> ram_dout_b
	);

------------------------------------------------------------------
-- Instantiate synchronous FIFO
inst_cmd_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width			=> NextExpBaseTwo(RAM_SIZE) - 4,	-- RAMSIZE(words)/MIN_PACKET_LEN(words)
		data_width			=> cmd_fifo_din'length,
		do_last_read_update	=> true
	)
	PORT MAP
	(
		rst			=>	reset_en,
		clk_w		=>	clk,
		clk_r		=>	mii_tx_clk,
		we			=>	cmd_fifo_we,
		re			=>	cmd_fifo_re,
		fifo_full	=>	cmd_fifo_full,
		fifo_empty	=>	cmd_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  cmd_fifo_din,
		data_r		=>  cmd_fifo_dout
	);

------------------------------------------------------------------
inst_piso32 : entity work.piso
	GENERIC MAP
	(
		data_width_in	=> 32,
		data_width_out	=> 8,
		msb_first		=> true
	)
	PORT MAP
	(
		rst			=> reset_en,
		clk			=> mii_tx_clk,
		din_vld		=> piso32_din_vld,
		din_re		=> piso32_din_re,
		din_be		=> piso32_din_be,
		din			=> piso32_din,
		dout_vld	=> piso32_dout_vld,
		shift_en		=> piso32_shift_en,
		dout		=> piso32_dout
		
	);

	piso32_shift_en	<= not mii_fifo_full; --'1' when Gbps_en = '1' else piso8_din_re;
	
------------------------------------------------------------------
fcs_enable_gen:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			if fcs_rst = '1' then
				fcs_en	<= (others => '0');
			elsif piso32_dout_vld = '1' then
				fcs_en	<= fcs_en(fcs_en'left-1 downto 0) & '1';
			end if;
		end if;
	end process;	

------------------------------------------------------------------
inst_fcs: entity work.crc32
	GENERIC MAP
	(
		crc32_init		=> X"00000000"
	)
	PORT MAP
	(
		rst				=> fcs_rst,
		clk				=> mii_tx_clk,
		din_vld			=> fcs_din_vld,
		din				=> piso32_dout,
		crc32_vld		=> fcs_vld,
		crc32_out		=> fcs
		
	);

	fcs_din_vld	<= fcs_en(fcs_en'left) and piso32_dout_vld and piso32_shift_en;

------------------------------------------------------------------
-- ensures continous MII-data stream and also acts as Inter Frame Gap delay
mii_fifo_re_dly_gen:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			if mii_fifo_empty = '1' then
				if Gbps_en = '0' then
					mii_fifo_re_dly	<= X"000000";	-- 100 mbps
				else
					mii_fifo_re_dly	<= X"000FFF";	-- Gigabit
				end if;
			else
				mii_fifo_re_dly	<= mii_fifo_re_dly(mii_fifo_re_dly'left-1 downto 0) & not mii_fifo_empty;
			end if;
		end if;
	end process;	

	mii_bsy			<= mii_fifo_re_dly(mii_fifo_re_dly'left);
	
------------------------------------------------------------------
-- Instantiate synchronous FIFO
inst_mii_fifo: entity work.fifo_sync
	GENERIC MAP
	(
		addr_width			=> 4,	
		data_width			=> piso32_dout'length,
		do_last_read_update	=> true
	)
	PORT MAP
	(
		rst			=>	reset_en,
		clk			=>  mii_tx_clk,
		we			=>	mii_fifo_we,
		re			=>	mii_fifo_re,
		fifo_full	=>	mii_fifo_full,
		fifo_empty	=>	mii_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  piso32_dout,
		data_r		=>  piso8_din
	);
	
	mii_fifo_re		<= mii_bsy when Gbps_en = '1' else (piso8_din_re and mii_bsy);
	mii_fifo_we		<= piso32_dout_vld;

------------------------------------------------------------------
inst_piso8 : entity work.piso
	GENERIC MAP
	(
		data_width_in	=> 8,
		data_width_out	=> 4,
		msb_first		=> false
	)
	PORT MAP
	(
		rst			=> reset_en,
		clk			=> mii_tx_clk,
		din_vld		=> piso8_din_vld,
		din_re		=> piso8_din_re,
		din_be		=> "11",
		din			=> piso8_din,
		dout_vld	=> piso8_dout_vld,
		shift_en	=> '1',
		dout		=> piso8_dout
		
	);

	piso8_din_vld	<= not mii_fifo_empty and mii_bsy;
	
------------------------------------------------------------------
mii_output_register:
	process(mii_tx_clk)
	begin
		if rising_edge(mii_tx_clk) then
			mii_tx_er		<= '0';
			if Gbps_en = '1' then
				mii_tx_en		<= piso8_din_vld;
				mii_tx			<= piso8_din;
			else
				mii_tx_en		<= piso8_dout_vld;
				mii_tx			<= "0000" & piso8_dout;
			end if;			
		end if;	
	end process;	

------------------------------------------------------------------
end behavior;
