----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date:    10:44:47 03/08/2016 
-- Design Name: 
-- Module Name:    phy_controller - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

library UNISIM;
use UNISIM.VComponents.all;


entity phy_controller is
	port (
		clk									: in    std_logic;
		phy_reset_done						: in    std_logic;
		phy_cmd_available					: in    std_logic;
		phy_command							: in    std_logic_vector(31 downto 0);
		phy_cmd_busy						: out   std_logic;
		phy_reply							: out   std_logic_vector(31 downto 0);
      PhyMdc								: out   std_logic;
      PhyMdio								: inout std_logic
	);
end phy_controller;


architecture Behavioral of phy_controller is
	
	type   phy_smi_clk_type is (idle, waitClockHigh, waitClockLow);
	signal phy_smi_clk_state						: phy_smi_clk_type := idle;
	signal phy_smi_clk_ctr							: unsigned(4 downto 0) := (others => '0');
	signal phy_smi_clk								: std_logic := '0';

	signal phy_mdio_is_input						: std_logic := '0';
	signal phy_mdio_out								: std_logic := '0';
	signal phy_mdio_in								: std_logic;
	
	type   phy_smi_type is (idle, preamble, preamble2,
										frameStart, frameStart2, frameStart3, frameStart4,
											opCode, opCode2, opCode3, opCode4,
												phyAddr, phyAddr2, regAddr, regAddr2,
													turnAround, turnAround2, turnAround3, turnAround4,
														readReg, readReg2, writeReg, writeReg2);
	signal phy_smi_state								: phy_smi_type := idle;
	signal phy_smi_busy								: std_logic;
	signal phy_smi_write								: std_logic := '0';
	signal phy_smi_write_reg						: std_logic_vector(15 downto 0) := (others => '0');
	signal phy_smi_reg_addr							: std_logic_vector(4 downto 0) := (others => '0');
	signal phy_smi_phy_addr							: std_logic_vector(4 downto 0) := (others => '0');
	signal phy_smi_ctr								: unsigned(5 downto 0) := (others => '0');
	signal phy_smi_read_reg							: std_logic_vector(15 downto 0) := (others => '0');

	signal fifo_target								: std_logic_vector(3 downto 0);
	signal fifo_cmd									: std_logic_vector(7 downto 0);
	signal fifo_data									: std_logic_vector(19 downto 0);
		
	type   phy_cmd_type is (idle, cmdSel, cmdErr, setPhySmiWriteRegister, doPhySmiCmd, doPhySmiCmd2);
	signal phy_cmd_state								: phy_cmd_type := idle;
	signal phy_reply_i								: std_logic_vector(31 downto 0) := (others => '0');
	signal cmd_phy_smi_write_reg					: std_logic_vector(15 downto 0) := (others => '0');
	signal cmd_do_phy_smi							: std_logic;
	signal cmd_phy_smi_reg_addr					: std_logic_vector(4 downto 0) := (others => '0');
	signal cmd_phy_smi_phy_addr					: std_logic_vector(4 downto 0) := (others => '0');
	signal cmd_phy_smi_write						: std_logic := '0';
	
	signal phy_ready_ctr								: unsigned(11 downto 0) := (others => '0');
	
	type   phy_startup_type is (idle, waitResetDone, waitPHYReady, readBCR, readBCR2, checkDuplex,
											readANA, readANA2, setDuplex, setDuplex2,
												reNegotiate, reNegotiate2);
	signal phy_startup_state						: phy_startup_type := idle;
	signal inc_phy_ready_ctr						: std_logic;
	signal sup_phy_smi_write_reg					: std_logic_vector(15 downto 0) := (others => '0');
	signal sup_do_phy_smi							: std_logic;
	type   sup_phy_smi_reg_type is (BCR, ANA);
	signal sup_phy_smi_reg							: sup_phy_smi_reg_type := BCR;
--	signal sup_phy_smi_phy_addr					: std_logic_vector(4 downto 0) := (others => '0');
	signal sup_phy_smi_write						: std_logic := '0';
	signal sup_phy_bcr_hi							: std_logic_vector(5 downto 0) := (others => '0');
	signal sup_phy_bcr_lo							: std_logic_vector(8 downto 0) := (others => '0');

begin
	
   PhyMdio_IOBUF : IOBUF
   generic map (
      DRIVE			=> 12,
      IOSTANDARD	=> "LVCMOS33",
      SLEW			=> "SLOW"
	)
   port map (
      O	=> phy_mdio_in,
      IO	=> PhyMdio,
      I	=> phy_mdio_out,
      T	=> phy_mdio_is_input
   );
	
	
	PhyMdc <= phy_smi_clk;
	
	phy_smi_clk_proc : process(clk)
	begin
		if (rising_edge(clk)) then
			case phy_smi_clk_state is
				when idle =>
					phy_smi_clk <= '0';
					if (phy_smi_busy = '1') then
						phy_smi_clk_state <= waitClockHigh;
					else
						phy_smi_clk_state <= idle;
					end if;
				
				when waitClockHigh =>
					if (phy_smi_clk_ctr = to_unsigned(0, 5)) then
						phy_smi_clk <= '1';
						phy_smi_clk_ctr <= to_unsigned(21, 5);
						phy_smi_clk_state <= waitClockLow;
					else
						phy_smi_clk_ctr <= phy_smi_clk_ctr - 1;
						phy_smi_clk_state <= waitClockHigh;
					end if;
				
				when waitClockLow =>
					if (phy_smi_clk_ctr = to_unsigned(0, 5)) then
						phy_smi_clk <= '0';
						if (phy_smi_busy = '0') then
							phy_smi_clk_state <= idle;
						else
							phy_smi_clk_ctr <= to_unsigned(21, 5);
							phy_smi_clk_state <= waitClockHigh;
						end if;
					else
						phy_smi_clk_ctr <= phy_smi_clk_ctr - 1;
						phy_smi_clk_state <= waitClockLow;
					end if;
			end case;
		end if;
	end process phy_smi_clk_proc;

	
	phy_smi_proc : process(clk)
	begin
		if (rising_edge(clk)) then
			case phy_smi_state is
				when idle =>
					phy_mdio_out <= '1';
					phy_mdio_is_input <= '0';
					if (cmd_do_phy_smi = '1') then
						phy_smi_busy <= '1';
						phy_smi_write <= cmd_phy_smi_write;
						phy_smi_write_reg <= cmd_phy_smi_write_reg;
						phy_smi_reg_addr <= cmd_phy_smi_reg_addr;
						phy_smi_phy_addr <= cmd_phy_smi_phy_addr;
						phy_smi_ctr <= to_unsigned(63, 6);
						phy_smi_read_reg <= (others => '0');
						phy_smi_state <= preamble;
					elsif (sup_do_phy_smi = '1') then
						phy_smi_busy <= '1';
						phy_smi_write <= sup_phy_smi_write;
						phy_smi_write_reg <= sup_phy_smi_write_reg;
						case sup_phy_smi_reg is
							when BCR =>
								phy_smi_reg_addr <= "00000";
							when ANA =>
								phy_smi_reg_addr <= "00100";
						end case;
						phy_smi_phy_addr <= "00000";
						phy_smi_ctr <= to_unsigned(63, 6);
						phy_smi_read_reg <= (others => '0');
						phy_smi_state <= preamble;
					else
						phy_smi_busy <= '0';
						phy_smi_state <= idle;
					end if;
				
				when preamble =>
					if (phy_smi_clk = '1') then
						phy_smi_state <= preamble2;
					else
						phy_smi_state <= preamble;
					end if;
				
				when preamble2 =>
					if (phy_smi_clk = '0') then
						if (phy_smi_ctr = to_unsigned(0, 6)) then
							phy_smi_state <= frameStart;
						else
							phy_smi_ctr <= phy_smi_ctr - 1;
							phy_smi_state <= preamble;
						end if;
					else
						phy_smi_state <= preamble2;
					end if;
				
				when frameStart =>
					if (phy_smi_clk = '1') then
						phy_mdio_out <= '0';
						phy_smi_state <= frameStart2;
					else
						phy_smi_state <= frameStart;
					end if;
				
				when frameStart2 =>
					if (phy_smi_clk = '0') then
						phy_smi_state <= frameStart3;
					else
						phy_smi_state <= frameStart2;
					end if;
				
				when frameStart3 =>
					if (phy_smi_clk = '1') then
						phy_mdio_out <= '1';
						phy_smi_state <= frameStart4;
					else
						phy_smi_state <= frameStart3;
					end if;
				
				when frameStart4 =>
					if (phy_smi_clk = '0') then
						phy_smi_state <= opCode;
					else
						phy_smi_state <= frameStart4;
					end if;
				
				when opCode =>
					if (phy_smi_clk = '1') then
						phy_mdio_out <= not phy_smi_write;
						phy_smi_state <= opCode2;
					else
						phy_smi_state <= opCode;
					end if;
				
				when opCode2 =>
					if (phy_smi_clk = '0') then
						phy_smi_state <= opCode3;
					else
						phy_smi_state <= opCode2;
					end if;
				
				when opCode3 =>
					if (phy_smi_clk = '1') then
						phy_mdio_out <= phy_smi_write;
						phy_smi_state <= opCode4;
					else
						phy_smi_state <= opCode3;
					end if;
				
				when opCode4 =>
					if (phy_smi_clk = '0') then
						phy_smi_ctr <= to_unsigned(4, 6);
						phy_smi_state <= phyAddr;
					else
						phy_smi_state <= opCode4;
					end if;
						
				when phyAddr =>
					if (phy_smi_clk = '1') then
						case phy_smi_ctr(2 downto 0) is
							when "000" =>
								phy_mdio_out <= phy_smi_phy_addr(0);
							when "001" =>
								phy_mdio_out <= phy_smi_phy_addr(1);
							when "010" =>
								phy_mdio_out <= phy_smi_phy_addr(2);
							when "011" =>
								phy_mdio_out <= phy_smi_phy_addr(3);
							when others =>
								phy_mdio_out <= phy_smi_phy_addr(4);
						end case;
						phy_smi_state <= phyAddr2;
					else
						phy_smi_state <= phyAddr;
					end if;
				
				when phyAddr2 =>
					if (phy_smi_clk = '0') then
						if (phy_smi_ctr = to_unsigned(0, 6)) then
							phy_smi_ctr <= to_unsigned(4, 6);
							phy_smi_state <= regAddr;
						else
							phy_smi_ctr <= phy_smi_ctr - 1;
							phy_smi_state <= phyAddr;
						end if;
					else
						phy_smi_state <= phyAddr2;
					end if;
						
				when regAddr =>
					if (phy_smi_clk = '1') then
						case phy_smi_ctr(2 downto 0) is
							when "000" =>
								phy_mdio_out <= phy_smi_reg_addr(0);
							when "001" =>
								phy_mdio_out <= phy_smi_reg_addr(1);
							when "010" =>
								phy_mdio_out <= phy_smi_reg_addr(2);
							when "011" =>
								phy_mdio_out <= phy_smi_reg_addr(3);
							when others =>
								phy_mdio_out <= phy_smi_reg_addr(4);
						end case;
						phy_smi_state <= regAddr2;
					else
						phy_smi_state <= regAddr;
					end if;
				
				when regAddr2 =>
					if (phy_smi_clk = '0') then
						if (phy_smi_ctr = to_unsigned(0, 6)) then
							phy_smi_state <= turnAround;
						else
							phy_smi_ctr <= phy_smi_ctr - 1;
							phy_smi_state <= regAddr;
						end if;
					else
						phy_smi_state <= regAddr2;
					end if;
				
				when turnAround =>
					if (phy_smi_clk = '1') then
						phy_smi_state <= turnAround2;
					else
						phy_smi_state <= turnAround;
					end if;
				
				when turnAround2 =>
					if (phy_smi_clk = '0') then
						phy_smi_state <= turnAround3;
					else
						phy_smi_state <= turnAround2;
					end if;
				
				when turnAround3 =>
					if (phy_smi_clk = '1') then
						phy_mdio_is_input <= not phy_smi_write;
						phy_smi_state <= turnAround4;
					else
						phy_smi_state <= turnAround3;
					end if;
				
				when turnAround4 =>
					if (phy_smi_clk = '0') then
						phy_smi_ctr <= to_unsigned(15, 6);
						if (phy_smi_write = '1') then
							phy_smi_state <= writeReg;
						else
							phy_smi_state <= readReg;
						end if;
					else
						phy_smi_state <= turnAround4;
					end if;
				
				when readReg =>
					if (phy_smi_clk = '1') then
						phy_smi_state <= readReg2;
					else
						phy_smi_state <= readReg;
					end if;
				
				when readReg2 =>
					if (phy_smi_clk = '0') then
						phy_smi_read_reg <= phy_smi_read_reg(14 downto 0) & phy_mdio_in;
						if (phy_smi_ctr = to_unsigned(0, 6)) then
							phy_smi_state <= idle;
						else
							phy_smi_ctr <= phy_smi_ctr - 1;
							phy_smi_state <= readReg;
						end if;
					else
						phy_smi_state <= readReg2;
					end if;
				
				when writeReg =>
					if (phy_smi_clk = '1') then
						case phy_smi_ctr(3 downto 0) is
							when "0000" =>
								phy_mdio_out <= phy_smi_write_reg(0);
							when "0001" =>
								phy_mdio_out <= phy_smi_write_reg(1);
							when "0010" =>
								phy_mdio_out <= phy_smi_write_reg(2);
							when "0011" =>
								phy_mdio_out <= phy_smi_write_reg(3);
							when "0100" =>
								phy_mdio_out <= phy_smi_write_reg(4);
							when "0101" =>
								phy_mdio_out <= phy_smi_write_reg(5);
							when "0110" =>
								phy_mdio_out <= phy_smi_write_reg(6);
							when "0111" =>
								phy_mdio_out <= phy_smi_write_reg(7);
							when "1000" =>
								phy_mdio_out <= phy_smi_write_reg(8);
							when "1001" =>
								phy_mdio_out <= phy_smi_write_reg(9);
							when "1010" =>
								phy_mdio_out <= phy_smi_write_reg(10);
							when "1011" =>
								phy_mdio_out <= phy_smi_write_reg(11);
							when "1100" =>
								phy_mdio_out <= phy_smi_write_reg(12);
							when "1101" =>
								phy_mdio_out <= phy_smi_write_reg(13);
							when "1110" =>
								phy_mdio_out <= phy_smi_write_reg(14);
							when others =>
								phy_mdio_out <= phy_smi_write_reg(15);
						end case;
						phy_smi_state <= writeReg2;
					else
						phy_smi_state <= writeReg;
					end if;
				
				when writeReg2 =>
					if (phy_smi_clk = '0') then
						if (phy_smi_ctr = to_unsigned(0, 6)) then
							phy_smi_state <= idle;
						else
							phy_smi_ctr <= phy_smi_ctr - 1;
							phy_smi_state <= writeReg;
						end if;
					else
						phy_smi_state <= writeReg2;
					end if;
					
			end case;
		end if;
	end process phy_smi_proc;


	fifo_target <= phy_command(31 downto 28);
	fifo_cmd <= phy_command(27 downto 20);
	fifo_data <= phy_command(19 downto 0);
	
	phy_reply <= phy_reply_i;

	phy_control_proc : process(clk)
	begin
		if (rising_edge(clk)) then
			cmd_do_phy_smi <= '0';
					
			case phy_cmd_state is
				when idle =>
					if (phy_cmd_available = '1') then
						phy_cmd_state <= cmdSel;
						phy_cmd_busy <= '1';
					else
						phy_cmd_state <= idle;
						phy_cmd_busy <= '0';
					end if;
				
				
				when cmdSel =>
					case fifo_cmd is
						when x"01" =>
							phy_cmd_state <= setPhySmiWriteRegister;
						
						when x"02" =>
							phy_cmd_state <= doPhySmiCmd;
						
						when others =>
							phy_cmd_state <= cmdErr;
					end case;
				
				
				when setPhySmiWriteRegister =>
					cmd_phy_smi_write_reg <= fifo_data(15 downto 0);
					phy_reply_i <= fifo_target & fifo_cmd & fifo_data;
					phy_cmd_state <= idle;
				
				
				when doPhySmiCmd =>
					cmd_phy_smi_reg_addr <= fifo_data(4 downto 0);
					cmd_phy_smi_phy_addr <= fifo_data(9 downto 5);
					cmd_phy_smi_write <= fifo_data(10);
					cmd_do_phy_smi <= '1';
					if (phy_smi_busy = '0') then
						phy_cmd_state <= doPhySmiCmd;
					else
						phy_cmd_state <= doPhySmiCmd2;
					end if;
				
				when doPhySmiCmd2 =>
					if (phy_smi_busy = '1') then
						phy_cmd_state <= doPhySmiCmd2;
					else
						phy_reply_i <= fifo_target & fifo_cmd & fifo_data(19 downto 16) & phy_smi_read_reg;
						phy_cmd_state <= idle;
					end if;
				
				
				when cmdErr =>
					phy_reply_i <= "1" & fifo_target(2 downto 0) & fifo_cmd & fifo_data;
					phy_cmd_state <= idle;
			end case;
		end if;
	end process phy_control_proc;
	
	
	phy_ready_ctr_proc : process (clk)
	begin
		if rising_edge(clk) then
			if (inc_phy_ready_ctr = '1') then
				phy_ready_ctr <= phy_ready_ctr + 1;
			end if;
		end if;
	end process phy_ready_ctr_proc;
	
	
	phy_startup_proc : process (clk)
	begin
		if rising_edge(clk) then
			sup_do_phy_smi <= '0';
			inc_phy_ready_ctr <= '0';
			
			case phy_startup_state is
				when idle =>
					if (phy_reset_done = '0') then
						phy_startup_state <= waitResetDone;
					else
						phy_startup_state <= idle;
					end if;
				
				when waitResetDone =>
					if (phy_reset_done = '1') then
						phy_startup_state <= waitPHYReady;
					else
						phy_startup_state <= waitResetDone;
					end if;
				
				when waitPHYReady =>
					inc_phy_ready_ctr <= '1';
					if (phy_ready_ctr = to_unsigned(4095, 12)) then
						phy_startup_state <= readBCR;
					else
						phy_startup_state <= waitPHYReady;
					end if;
				
				when readBCR =>
--					sup_phy_smi_phy_addr <= (others => '0');
--					sup_phy_smi_reg_addr <= (others => '0');
					sup_phy_smi_reg <= BCR;
					sup_phy_smi_write <= '0';
					sup_do_phy_smi <= '1';
					if (phy_smi_busy = '0') then
						phy_startup_state <= readBCR;
					else
						phy_startup_state <= readBCR2;
					end if;
				
				when readBCR2 =>
					if (phy_smi_busy = '1') then
						phy_startup_state <= readBCR2;
					else
						phy_startup_state <= checkDuplex;
					end if;
				
				when checkDuplex =>
					sup_phy_bcr_hi <= phy_smi_read_reg(15 downto 10);
					sup_phy_bcr_lo <= phy_smi_read_reg(8 downto 0);
					if (phy_smi_read_reg(8) = '0') then
						phy_startup_state <= readANA;
					else
						phy_startup_state <= idle;
					end if;
				
				when readANA =>
					sup_phy_smi_reg <= ANA;
--					sup_phy_smi_reg_addr <= "00100";
					sup_do_phy_smi <= '1';
					if (phy_smi_busy = '0') then
						phy_startup_state <= readANA;
					else
						phy_startup_state <= readANA2;
					end if;
				
				when readANA2 =>
					if (phy_smi_busy = '1') then
						phy_startup_state <= readANA2;
					else
						phy_startup_state <= setDuplex;
					end if;
				
				when setDuplex =>
					sup_do_phy_smi <= '1';
					sup_phy_smi_write <= '1';
					sup_phy_smi_write_reg <= phy_smi_read_reg(15 downto 9) & '1' & phy_smi_read_reg(7 downto 0);
					if (phy_smi_busy = '0') then
						phy_startup_state <= setDuplex;
					else
						phy_startup_state <= setDuplex2;
					end if;
				
				when setDuplex2 =>
					if (phy_smi_busy = '1') then
						phy_startup_state <= setDuplex2;
					else
						phy_startup_state <= reNegotiate;
					end if;
				
				when reNegotiate =>
					sup_do_phy_smi <= '1';
					sup_phy_smi_write <= '0';
					sup_phy_smi_write_reg <= sup_phy_bcr_hi & '1' & sup_phy_bcr_lo;
					if (phy_smi_busy = '0') then
						phy_startup_state <= reNegotiate;
					else
						phy_startup_state <= reNegotiate2;
					end if;
				
				when reNegotiate2 =>
					if (phy_smi_busy = '1') then
						phy_startup_state <= reNegotiate2;
					else
						phy_startup_state <= idle;
					end if;
					
			end case;
		end if;
	end process phy_startup_proc;

end Behavioral;

