-----------------------------------------------------------------------
-----------------------------------------------------------------------
-- PicoBlaze compatible MAC Serial Management Interface
--
-- 2-byte data register is shared by Rx (read) and Tx (write).
-- Address/status register contains basic SMI status information.
-- SMI speed depends on clock present on clk input
-- (MDC frequency is the half of the clk frequency)
-- clk frequency should not be higher than 16 MHz (results in 8 MHz MDC)
--
-- 32 idle MDC clock cycles must be present between each transmission
--
-- Interrupts are short positive pulses only (one clock cycle long)
-- and must be captured by an interrupt controller.
--
-- Status:
--  CODING COMPLETE     YES
--  SIMULATED           YES
--  TESTED              YES
--
----------------------------------------------------------------------------------
---------------------------------------------------------------------------
-- Addr register:
-- [0] A(0)
-- [1] A(1)
-- [2] A(2)
-- [3] A(3)
-- [4] A(4)
-- [5] - Unused -
-- [6] Write command        -- write 1 to initiate write; cleared automaticly when write is completed
-- [7] Read command         -- write 1 to initiate read; cleared automaticly when read is completed
----------------------------------------------------------------------
----------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
--library UNISIM;
--use UNISIM.VComponents.all;
----------------------------------------------------------------------------------
ENTITY SMI_Controler IS
    GENERIC(datL_port_ID: STD_LOGIC_VECTOR(7 DOWNTO 0):= "00000001";    -- data port lower byte
            datH_port_ID: STD_LOGIC_VECTOR(7 DOWNTO 0):= "00000010";    -- data port upper byte
            addr_port_ID: STD_LOGIC_VECTOR(7 DOWNTO 0):= "00000100";
            clk_div:      INTEGER                     :=  1000);        -- clock divider to slow-down data rate
                                                                        -- DataRate = ( freq(clk) / clk_div ) / 2
    PORT(
            clk:            IN  STD_LOGIC;      -- main SMI clock; use 16 MHz for 8 MHz SMI clock (maximum)
            rst:            IN  STD_LOGIC;      -- system reset; active high

            -- PicoBlaze interface
            Data_in:        IN  STD_LOGIC_VECTOR(7 DOWNTO 0);     -- Data IN (PicoBlaze OUT)
            MAC_SMI_datL:   OUT STD_LOGIC_VECTOR(7 DOWNTO 0);     -- Data L reg OUT (to PicoBlaze input MUX)
            MAC_SMI_datH:   OUT STD_LOGIC_VECTOR(7 DOWNTO 0);     -- Data H reg OUT (to PicoBlaze input MUX)
            MAC_SMI_addr:   OUT STD_LOGIC_VECTOR(7 DOWNTO 0);     -- Address reg OUT (to PicoBlaze input MUX)
            Address:        IN  STD_LOGIC_VECTOR(7 DOWNTO 0);     -- PicoBlaze Port ID
            PHY_addr:       IN  STD_LOGIC_VECTOR(4 DOWNTO 0);     -- PHY address on SMI bus
            read_strobe:    IN  STD_LOGIC;                        -- PicoBlaze read strobe
            write_strobe:   IN  STD_LOGIC;                        -- PicoBlaze write strobe
            Tx_ready_int:   OUT STD_LOGIC;                        -- Tx done interrupt (short pulse)
            Rx_new_int:     OUT STD_LOGIC;                        -- New Rx data interrupt (celared by reading the data reg)

            -- SMI physical interface
            MDC:            OUT STD_LOGIC;
            MDIO:           INOUT STD_LOGIC);
END SMI_Controler;
----------------------------------------------------------------------------------
ARCHITECTURE Behavioral OF SMI_Controler IS
----------------------------------------------------------------------------------
--CONSTANT PHY_addr:    STD_LOGIC_VECTOR(4 DOWNTO 0):= "00111";             -- PHY address set during power up

  SIGNAL data_reg:      STD_LOGIC_VECTOR(15 DOWNTO 0):= (OTHERS => '0');    -- data register (H & L)
  SIGNAL data_reg_out:  STD_LOGIC_VECTOR(15 DOWNTO 0):= (OTHERS => '0');     -- data register for reading
  SIGNAL addr_reg:      STD_LOGIC_VECTOR( 7 DOWNTO 0):= (OTHERS => '0');    -- address + status register

  SIGNAL cnt_clk_div:   STD_LOGIC_VECTOR(15 DOWNTO 0):= (OTHERS => '0');    -- clock divider for lowering data rate
  SIGNAL clk_EN:        STD_LOGIC := '0';                                   -- clock  enable for lowering data rate

  SIGNAL SMI_state:     INTEGER RANGE 0 TO 129 := 0;
  SIGNAL SMI_done:      STD_LOGIC := '0';               -- high in the last cycle of the SMI state machine
  SIGNAL SMI_wr_start:  STD_LOGIC := '0';               -- initiates SMI write cycle
  SIGNAL SMI_rd_start:  STD_LOGIC := '0';               -- initiates SMI read cycle
  SIGNAL MDC_i:         STD_LOGIC := '0';               -- internal SMI clock signal
----------------------------------------------------------------------------------
BEGIN
----------------------------------------------------------------------------------

  MDC <= MDC_i;

  --------------------------------------------------------------------------------
  -- clock divider
  PROCESS(clk) BEGIN
    IF rising_edge(clk) THEN
      IF cnt_clk_div = CONV_STD_LOGIC_VECTOR(clk_div,16) THEN
        cnt_clk_div <= (OTHERS => '0');
        clk_EN <= '1';
      ELSE
        cnt_clk_div <= cnt_clk_div + 1;
        clk_EN <= '0';
      END IF;
    END IF;
  END PROCESS;

  --------------------------------------------------------------------------------
  -- PicoBlaze interface
  --------------------------------------------------------------------------------

  -- Processor write to controller internal registers
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN
      IF write_strobe = '1' THEN
        CASE Address IS
          WHEN datL_port_ID => data_reg( 7 DOWNTO 0) <= Data_in;
          WHEN datH_port_ID => data_reg(15 DOWNTO 8) <= Data_in;
          WHEN addr_port_ID => addr_reg( 4 DOWNTO 0) <= Data_in(4 DOWNTO 0);
          WHEN OTHERS       => NULL;
        END CASE;
      END IF;
    END IF;
  END PROCESS;
  --------------------------------------------------------------------------------
  -- Write or Read cycle start
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN

      -- clear busy flags when transmission is completed
      IF SMI_done = '1' THEN
        addr_reg(7 DOWNTO 6) <= "00";

      -- if processor writes to the controll register
      ELSIF write_strobe = '1' AND Address = addr_port_ID THEN

        -- if SMI is idle
        IF addr_reg(7 DOWNTO 6) = "00" THEN

          -- write command
          IF Data_in(6) = '1' THEN
            addr_reg(6) <= '1';
            SMI_wr_start <= '1';
            SMI_rd_start <= '0';

          -- read command
          ELSIF Data_in(7) = '1' THEN
            addr_reg(7) <= '1';
            SMI_rd_start <= '1';
            SMI_wr_start <= '0';
          END IF;
        END IF;
      ELSIF SMI_state = 1 THEN
        SMI_rd_start <= '0';
        SMI_wr_start <= '0';
      END IF;
    END IF;
  END PROCESS;

  --------------------------------------------------------------------------------
  -- Processor read of controller internal registers
  MAC_SMI_datL <= data_reg_out( 7 DOWNTO 0);
  MAC_SMI_datH <= data_reg_out(15 DOWNTO 8);
  MAC_SMI_addr <= addr_reg;
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
-- SMI state machine
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
  -- SMI SCLK generator
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN
      CASE SMI_state IS
        WHEN    0   => MDC_i <= '1';
        WHEN OTHERS => IF clk_EN = '1' THEN MDC_i <= NOT MDC_i; END IF;
      END CASE;
    END IF;
  END PROCESS;
  --------------------------------------------------------------------------------
  -- SMI state signal
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN
      IF clk_EN = '1' THEN
        CASE SMI_state IS
          WHEN     0  => IF SMI_wr_start = '1' OR SMI_rd_start = '1' THEN
                           SMI_state <= SMI_state + 1;
                         ELSE
                           SMI_state <= 129;
                         END IF;
          WHEN   129  => SMI_state <= 0;
          WHEN OTHERS => SMI_state <= SMI_state + 1;
        END CASE;
      END IF;
    END IF;
  END PROCESS;
  --------------------------------------------------------------------------------
  -- SMI MDIO output
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN

      -- read cycle
      IF addr_reg(7) = '1' THEN
        CASE SMI_state IS
        -- start
          WHEN  1| 2 => MDIO <= '0';
          WHEN  3| 4 => MDIO <= '1';
        -- read/write command
          WHEN  5| 6 => MDIO <= addr_reg(7);
          WHEN  7| 8 => MDIO <= NOT addr_reg(7);
        -- PHY Addr(4:0)
          WHEN  9|10 => MDIO <= PHY_addr(4);
          WHEN 11|12 => MDIO <= PHY_addr(3);
          WHEN 13|14 => MDIO <= PHY_addr(2);
          WHEN 15|16 => MDIO <= PHY_addr(1);
          WHEN 17|18 => MDIO <= PHY_addr(0);
        -- REG Addr(4:0)
          WHEN 19|20 => MDIO <= addr_reg(4);
          WHEN 21|22 => MDIO <= addr_reg(3);
          WHEN 23|24 => MDIO <= addr_reg(2);
          WHEN 25|26 => MDIO <= addr_reg(1);
          WHEN 27|28 => MDIO <= addr_reg(0);
          WHEN OTHERS => MDIO <= 'Z';
        END CASE;

      -- write cycle
      ELSE
        CASE SMI_state IS
        -- start
          WHEN  1| 2 => MDIO <= '0';
          WHEN  3| 4 => MDIO <= '1';
        -- read/write command
          WHEN  5| 6 => MDIO <= addr_reg(7);
          WHEN  7| 8 => MDIO <= NOT addr_reg(7);
        -- PHY Addr(4:0)
          WHEN  9|10 => MDIO <= PHY_addr(4);
          WHEN 11|12 => MDIO <= PHY_addr(3);
          WHEN 13|14 => MDIO <= PHY_addr(2);
          WHEN 15|16 => MDIO <= PHY_addr(1);
          WHEN 17|18 => MDIO <= PHY_addr(0);
        -- REG Addr(4:0)
          WHEN 19|20 => MDIO <= addr_reg(4);
          WHEN 21|22 => MDIO <= addr_reg(3);
          WHEN 23|24 => MDIO <= addr_reg(2);
          WHEN 25|26 => MDIO <= addr_reg(1);
          WHEN 27|28 => MDIO <= addr_reg(0);
        -- Turnaround
          WHEN 29|30 => MDIO <= '1';
          WHEN 31|32 => MDIO <= '0';
        -- Data(15:0)
          WHEN 33|34 => MDIO <= data_reg(15);
          WHEN 35|36 => MDIO <= data_reg(14);
          WHEN 37|38 => MDIO <= data_reg(13);
          WHEN 39|40 => MDIO <= data_reg(12);
          WHEN 41|42 => MDIO <= data_reg(11);
          WHEN 43|44 => MDIO <= data_reg(10);
          WHEN 45|46 => MDIO <= data_reg(9);
          WHEN 47|48 => MDIO <= data_reg(8);
          WHEN 49|50 => MDIO <= data_reg(7);
          WHEN 51|52 => MDIO <= data_reg(6);
          WHEN 53|54 => MDIO <= data_reg(5);
          WHEN 55|56 => MDIO <= data_reg(4);
          WHEN 57|58 => MDIO <= data_reg(3);
          WHEN 59|60 => MDIO <= data_reg(2);
          WHEN 61|62 => MDIO <= data_reg(1);
          WHEN 63|64 => MDIO <= data_reg(0);
          WHEN OTHERS => MDIO <= 'Z';
        END CASE;
      END IF;

    END IF;
  END PROCESS;
  --------------------------------------------------------------------------------
  -- reading from MDIO to register
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN
      -- read cycle
      IF addr_reg(7) = '1' THEN
        CASE SMI_state IS
        -- Data(15:0)
          WHEN 34 => data_reg_out(15) <= MDIO;
          WHEN 36 => data_reg_out(14) <= MDIO;
          WHEN 38 => data_reg_out(13) <= MDIO;
          WHEN 40 => data_reg_out(12) <= MDIO;
          WHEN 42 => data_reg_out(11) <= MDIO;
          WHEN 44 => data_reg_out(10) <= MDIO;
          WHEN 46 => data_reg_out(9)  <= MDIO;
          WHEN 48 => data_reg_out(8)  <= MDIO;
          WHEN 50 => data_reg_out(7)  <= MDIO;
          WHEN 52 => data_reg_out(6)  <= MDIO;
          WHEN 54 => data_reg_out(5)  <= MDIO;
          WHEN 56 => data_reg_out(4)  <= MDIO;
          WHEN 58 => data_reg_out(3)  <= MDIO;
          WHEN 60 => data_reg_out(2)  <= MDIO;
          WHEN 62 => data_reg_out(1)  <= MDIO;
          WHEN 64 => data_reg_out(0)  <= MDIO;
          WHEN OTHERS => NULL;
        END CASE;
      END IF;
    END IF;
  END PROCESS;
  --------------------------------------------------------------------------------
  -- Interrupts, "done" flag
  PROCESS(clk) BEGIN
    IF RISING_EDGE(clk) THEN
      CASE SMI_state IS
        WHEN   128  => SMI_done <= '1';
                       IF addr_reg(7) = '1' THEN Rx_new_int <= '1';
                                            ELSE Tx_ready_int <= '1'; END IF;
        WHEN OTHERS => SMI_done <= '0'; Rx_new_int <= '0'; Tx_ready_int <= '0';
      END CASE;
    END IF;
  END PROCESS;
----------------------------------------------------------------------------------
END Behavioral;
----------------------------------------------------------------------------------
