------------------------------------------------------------------------------
--  This file is a part of the GRLIB VHDL IP LIBRARY
--  Copyright (C) 2003 - 2008, Gaisler Research
--  Copyright (C) 2008 - 2014, Aeroflex Gaisler
--  Copyright (C) 2015 - 2017, Cobham Gaisler
--
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program; if not, write to the Free Software
--  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
----------------------------------------------------------------------------
-- Entity:   	phy
-- File:	phy.vhd
-- Description:	Simulation model of an Ethernet PHY
-- Author:      Marko Isomaki
------------------------------------------------------------------------------

-- pragma translate_off

library ieee;
library grlib;

use ieee.std_logic_1164.all;
use grlib.stdlib.all;

entity phy is
  generic(
    address       : integer range 0 to 31 := 0;
    extended_regs : integer range 0 to 1  := 1;
    aneg          : integer range 0 to 1  := 1;
    base100_t4    : integer range 0 to 1  := 0;
    base100_x_fd  : integer range 0 to 1  := 1;
    base100_x_hd  : integer range 0 to 1  := 1;
    fd_10         : integer range 0 to 1  := 1;
    hd_10         : integer range 0 to 1  := 1;
    base100_t2_fd : integer range 0 to 1  := 1;
    base100_t2_hd : integer range 0 to 1  := 1;
    base1000_x_fd : integer range 0 to 1  := 0;
    base1000_x_hd : integer range 0 to 1  := 0;
    base1000_t_fd : integer range 0 to 1  := 1;
    base1000_t_hd : integer range 0 to 1  := 1;
    rmii          : integer range 0 to 1  := 0;
    rgmii         : integer range 0 to 1  := 0;
    extrxclken    : integer range 0 to 1  := 0; -- align rx_clk with extrxclk (gmii only)
    gmii100       : integer range 0 to 1  := 0  -- force 10/100 to (x10/x100 repeating) gmii
    );
  port(
    rstn     : in std_logic;
    mdio     : inout std_logic;
    tx_clk   : out std_logic;
    rx_clk   : out std_logic; 
    rxd      : out std_logic_vector(7 downto 0);   
    rx_dv    : out std_logic; 
    rx_er    : out std_logic; 
    rx_col   : out std_logic;
    rx_crs   : out std_logic;
    txd      : in std_logic_vector(7 downto 0);   
    tx_en    : in std_logic; 
    tx_er    : in std_logic; 
    mdc      : in std_logic;
    gtx_clk  : in std_logic;
    extrxclk : in std_logic
  );
end;

architecture behavioral of phy is
  type mdio_state_type is (idle, start_of_frame, start_of_frame2, op, phyad, regad,
    ta, rdata, wdata);
           
  type ctrl_reg_type is record
    reset       : std_ulogic;
    loopback    : std_ulogic;
    speedsel    : std_logic_vector(1 downto 0);
    anegen      : std_ulogic;
    powerdown   : std_ulogic;
    isolate     : std_ulogic;
    restartaneg : std_ulogic;
    duplexmode  : std_ulogic;
    coltest     : std_ulogic;
  end record;

  type status_reg_type is record
    base100_t4    : std_ulogic;
    base100_x_fd  : std_ulogic;
    base100_x_hd  : std_ulogic;
    fd_10         : std_ulogic;
    hd_10         : std_ulogic;
    base100_t2_fd : std_ulogic;
    base100_t2_hd : std_ulogic;
    extstat       : std_ulogic;
    mfpreamblesup : std_ulogic;
    anegcmpt      : std_ulogic;
    remfault      : std_ulogic;
    anegability   : std_ulogic;
    linkstat      : std_ulogic;
    jabdetect     : std_ulogic;
    extcap        : std_ulogic;
  end record;

  type aneg_ab_type is record
    next_page     : std_ulogic;
    remote_fault  : std_ulogic;
    tech_ability  : std_logic_vector(7 downto 0);
    selector      : std_logic_vector(4 downto 0);
  end record;

  type aneg_exp_type is record
    par_detct_flt : std_ulogic;
    lp_np_able    : std_ulogic;
    np_able       : std_ulogic;
    page_rx       : std_ulogic;
    lp_aneg_able  : std_ulogic;
  end record;

  type aneg_nextpage_type is record
    next_page     : std_ulogic;
    message_page  : std_ulogic;
    ack2          : std_ulogic;
    toggle        : std_ulogic;
    message       : std_logic_vector(10 downto 0);
  end record;

  type mst_slv_ctrl_type is record
    tmode         : std_logic_vector(2 downto 0);
    manualcfgen   : std_ulogic;
    cfgval        : std_ulogic;
    porttype      : std_ulogic;
    base1000_t_fd : std_ulogic;
    base1000_t_hd : std_ulogic;
  end record;

  type mst_slv_status_type is record
    cfgfault        : std_ulogic;
    cfgres          : std_ulogic;
    locrxstate      : std_ulogic;
    remrxstate      : std_ulogic;
    lpbase1000_t_fd : std_ulogic;
    lpbase1000_t_hd : std_ulogic;
    idlerrcnt       : std_logic_vector(7 downto 0);
  end record;

  type extended_status_reg_type is record
    base1000_x_fd : std_ulogic;
    base1000_x_hd : std_ulogic;
    base1000_t_fd : std_ulogic;
    base1000_t_hd : std_ulogic;
  end record;
  
  type reg_type is record
    state         : mdio_state_type;
    cnt           : integer;
    op            : std_logic_vector(1 downto 0);
    phyad         : std_logic_vector(4 downto 0);
    regad         : std_logic_vector(4 downto 0);
    wr            : std_ulogic;
    regtmp        : std_logic_vector(15 downto 0);
    -- MII management registers
    ctrl          : ctrl_reg_type;
    status        : status_reg_type;
    anegadv       : aneg_ab_type;
    aneglp        : aneg_ab_type;
    anegexp       : aneg_exp_type;
    anegnptx      : aneg_nextpage_type;
    anegnplp      : aneg_nextpage_type;
    mstslvctrl    : mst_slv_ctrl_type;
    mstslvstat    : mst_slv_status_type;
    extstatus     : extended_status_reg_type;
    rstcnt        : integer;
    anegcnt       : integer;
  end record;

  signal r, rin   : reg_type;
  signal int_clk  : std_ulogic := '0';
  signal clkslow  : std_ulogic := '0';
  signal rcnt     : integer;
  signal anegact  : std_ulogic;

  constant qlength: integer := 4;
  signal txdataq: std_logic_vector(0 to qlength*10-1);
  signal txqpos,rxqpos: integer range 0 to qlength-1 := 0;

  type phy_interface_mode_type is (mode_mii,mode_gmii,mode_rmii,mode_rgmii);
  signal ifmode: phy_interface_mode_type;

  signal lb_rxd: std_logic_vector(7 downto 0);
  signal lb_rxdv,lb_rxer: std_ulogic;

  signal erxclkdel : std_ulogic;
begin

  -- Selects which interface standard should be implemented by the PHY
  -- Note we assume selection between GMII and MII depending on speed setting
  --   in MDIO reg
  ifmode <= mode_rgmii when rgmii=1 else
            mode_rmii when rmii=1 else
            mode_gmii when (r.ctrl.speedsel = "01" or gmii100=1) else
            mode_mii;

  erxclkdel <= transport extrxclk after (8 ns - 2.5 ns);

  int_clk <= erxclkdel when extrxclken=1 and ifmode=mode_gmii else
             not int_clk after 10 ns  when ifmode=mode_rmii else
             not int_clk after 4 ns   when ifmode=mode_rgmii and r.ctrl.speedsel = "01" else
             not int_clk after 20 ns  when ifmode=mode_rgmii and r.ctrl.speedsel = "10" else
             not int_clk after 200 ns when ifmode=mode_rgmii and r.ctrl.speedsel = "00" else
             not int_clk after 4 ns   when ifmode=mode_gmii else
             not int_clk after 20 ns  when ifmode=mode_mii   and r.ctrl.speedsel = "10" else
             not int_clk after 200 ns when ifmode=mode_mii   and r.ctrl.speedsel = "00";

  clkslow <= not clkslow after 20 ns when r.ctrl.speedsel = "10" else
             not clkslow after 200 ns;

  rx_clk <= int_clk after 2.5 ns when ifmode=mode_gmii else
            int_clk after 10 ns when ifmode=mode_mii else
            int_clk;
  tx_clk <= clkslow when ifmode/=mode_gmii else '0';

  anegproc : process is
  begin
    loop
      anegact <= '0';
      while rstn /= '1' loop
        wait on rstn;
      end loop;
      while rstn = '1' loop
        if r.ctrl.anegen = '0' then
          anegact <= '0';
          wait on rstn, r.ctrl.anegen, r.ctrl.restartaneg;
        else
          if r.ctrl.restartaneg = '1' then
            anegact <= '1';
            wait on rstn, r.ctrl.restartaneg, r.ctrl.anegen for 2 us;
            anegact <= '0';
            wait on rstn, r.ctrl.anegen until r.ctrl.restartaneg = '0';
            if (rstn and r.ctrl.anegen) = '1' then
              wait on rstn, r.ctrl.anegen, r.ctrl.restartaneg;
            end if;
          else
            anegact <= '0';
            wait on rstn, r.ctrl.restartaneg, r.ctrl.anegen;
          end if;
        end if;
      end loop;
    end loop;
  end process; 

  mdiocomb : process(rstn, r, anegact, mdio) is
    variable v : reg_type;
  begin
    v := r; 
    if anegact = '0' then
      v.ctrl.restartaneg := '0';
    end if;
    case r.state is
      when idle =>
        mdio <= 'Z';
        if to_X01(mdio) = '1' then
          v.cnt := v.cnt + 1;
          if v.cnt = 31 then
            v.state := start_of_frame; v.cnt := 0;
          end if;
        else
          v.cnt := 0;
        end if;
      when start_of_frame =>
        if to_X01(mdio) = '0' then
          v.state := start_of_frame2;
        elsif to_X01(mdio) /= '1' then
          v.state := idle;
        end if;
      when start_of_frame2 =>
        if to_X01(mdio) = '1' then
          v.state := op;
        else
          v.state := idle;
        end if;
      when op =>
        v.cnt := v.cnt + 1;
        v.op := r.op(0) & to_X01(mdio);
        if r.cnt = 1 then
          if (v.op = "01") or (v.op = "10") then 
            v.state := phyad; v.cnt := 0;
          else
            v.state := idle; v.cnt := 0;
          end if;
        end if;
      when phyad =>
        v.phyad := r.phyad(3 downto 0) & to_X01(mdio);
        v.cnt := v.cnt + 1;
        if r.cnt = 4 then
          v.state := regad; v.cnt := 0;
        end if;
      when regad =>
        v.regad := r.regad(3 downto 0) & to_X01(mdio);
        v.cnt := v.cnt + 1;
        if r.cnt = 4 then
          v.cnt := 0;
          if conv_integer(r.phyad) = address then
            v.state := ta; 
          else
            v.state := idle;
          end if;
        end if;
      when ta =>
        v.cnt := r.cnt + 1;
        if r.cnt = 0 then
          if (r.op = "01") and to_X01(mdio) /= '1' then
            v.cnt := 0; v.state := idle;
          end if;
        else
          if r.op = "10" then
            mdio <= '0'; v.cnt := 0; v.state := rdata;
            case r.regad is
              when "00000" => --ctrl (basic)
                v.regtmp := r.ctrl.reset & r.ctrl.loopback &
                  r.ctrl.speedsel(1) & r.ctrl.anegen & r.ctrl.powerdown &
                  r.ctrl.isolate & r.ctrl.restartaneg & r.ctrl.duplexmode &
                  r.ctrl.coltest & r.ctrl.speedsel(0) & "000000";
              when "00001" => --statuc (basic)
                v.regtmp := r.status.base100_t4 & r.status.base100_x_fd & 
                  r.status.base100_x_hd & r.status.fd_10 & r.status.hd_10 & 
                  r.status.base100_t2_fd & r.status.base100_t2_hd & 
                  r.status.extstat & '0' & r.status.mfpreamblesup & 
                  r.status.anegcmpt & r.status.remfault & r.status.anegability &
                  r.status.linkstat & r.status.jabdetect & r.status.extcap;
              when "00010" => --PHY ID (extended)
                if extended_regs = 1 then
                  v.regtmp := X"BBCD";
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "00011" => --PHY ID (extended)
                if extended_regs = 1 then
                  v.regtmp := X"9C83";
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "00100" => --Auto-neg adv. (extended)
                if extended_regs = 1 then
                  v.regtmp := r.anegadv.next_page & '0' & r.anegadv.remote_fault & 
                    r.anegadv.tech_ability & r.anegadv.selector;
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "00101" => --Auto-neg link partner ability (extended)
                if extended_regs = 1 then
                  v.regtmp := r.aneglp.next_page & '0' & r.aneglp.remote_fault & 
                    r.aneglp.tech_ability & r.aneglp.selector;
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "00110" => --Auto-neg expansion (extended)
                if extended_regs = 1 then
                  v.regtmp := "00000000000" & r.anegexp.par_detct_flt & 
                  r.anegexp.lp_np_able &  r.anegexp.np_able & r.anegexp.page_rx &
                  r.anegexp.lp_aneg_able;
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "00111" => --Auto-neg next page (extended)
                if extended_regs = 1 then
                  v.regtmp := r.anegnptx.next_page & '0' & r.anegnptx.message_page &
                  r.anegnptx.ack2 & r.anegnptx.toggle & r.anegnptx.message;
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "01000" => --Auto-neg link partner received next page (extended)
                if extended_regs = 1 then
                  v.regtmp := r.anegnplp.next_page & '0' & r.anegnplp.message_page &
                  r.anegnplp.ack2 & r.anegnplp.toggle & r.anegnplp.message;
                else
                  v.cnt := 0; v.state := idle; 
                end if;
              when "01001" => --Master-slave control (extended)
                if extended_regs = 1 then
                  v.regtmp := r.mstslvctrl.tmode & r.mstslvctrl.manualcfgen & 
                  r.mstslvctrl.cfgval & r.mstslvctrl.porttype &
                  r.mstslvctrl.base1000_t_fd &  r.mstslvctrl.base1000_t_hd &
                  "00000000";
                else
                  v.cnt := 0; v.state := idle;
                end if;
              when "01010" => --Master-slave status (extended)
                if extended_regs = 1 then
                  v.regtmp := r.mstslvstat.cfgfault & r.mstslvstat.cfgres &
                  r.mstslvstat.locrxstate & r.mstslvstat.remrxstate & 
                  r.mstslvstat.lpbase1000_t_fd & r.mstslvstat.lpbase1000_t_hd &
                  "00" & r.mstslvstat.idlerrcnt;
                else
                  v.cnt := 0; v.state := idle;
                end if;
              when "01111" =>
                if (base1000_x_fd = 1) or (base1000_x_hd = 1) or
                   (base1000_t_fd = 1) or (base1000_t_hd = 1) then
                  v.regtmp := r.extstatus.base1000_x_fd &
                              r.extstatus.base1000_x_hd &
                              r.extstatus.base1000_t_fd &
                              r.extstatus.base1000_t_hd & X"000";
                else
                  v.regtmp := (others => '0');
                end if;
              when others =>
                --PHY shall not drive MDIO when unimplemented registers
                --are accessed
                v.cnt := 0; v.state := idle;
                v.regtmp := (others => '0');
            end case;
            if r.ctrl.reset = '1' then
              if r.regad = "00000" then
                v.regtmp := X"8000";
              else
                v.regtmp := X"0000";
              end if;
            end if;
          else
            if to_X01(mdio) /= '0'then
              v.cnt := 0; v.state := idle; 
            else
              v.cnt := 0; v.state := wdata;
            end if; 
          end if;
        end if;
      when rdata =>
        v.cnt := r.cnt + 1;
        mdio <= r.regtmp(15-r.cnt);
        if r.cnt = 15 then
          v.state := idle; v.cnt := 0;
        end if;
      when wdata =>
        v.cnt := r.cnt + 1;
        v.regtmp := r.regtmp(14 downto 0) & to_X01(mdio);
        if r.cnt = 15 then
          v.state := idle; v.cnt := 0;
          if r.ctrl.reset = '0' then
            case r.regad is
              when "00000" =>
                v.ctrl.reset := v.regtmp(15);
                v.ctrl.loopback := v.regtmp(14);
                v.ctrl.speedsel(1) := v.regtmp(13);
                v.ctrl.anegen := v.regtmp(12);
                v.ctrl.powerdown := v.regtmp(11);
                v.ctrl.isolate := v.regtmp(10);
                v.ctrl.restartaneg := v.regtmp(9);
                v.ctrl.duplexmode := v.regtmp(8);
                v.ctrl.coltest := v.regtmp(7);
                v.ctrl.speedsel(0) := v.regtmp(6);
              when "00100" =>
                if extended_regs = 1 then
                  v.anegadv.remote_fault := r.regtmp(13);
                  v.anegadv.tech_ability := r.regtmp(12 downto 5);
                  v.anegadv.selector := r.regtmp(4 downto 0);
                end if;
              when "00111" =>
                if extended_regs = 1 then
                  v.anegnptx.next_page     := r.regtmp(15);
                  v.anegnptx.message_page  := r.regtmp(13);
                  v.anegnptx.ack2          := r.regtmp(12);
                  v.anegnptx.message       := r.regtmp(10 downto 0);
                end if;
              when "01001" =>
                if extended_regs = 1 then
                  v.mstslvctrl.tmode         := r.regtmp(15 downto 13);
                  v.mstslvctrl.manualcfgen   := r.regtmp(12);
                  v.mstslvctrl.cfgval        := r.regtmp(11);
                  v.mstslvctrl.porttype      := r.regtmp(10);
                  v.mstslvctrl.base1000_t_fd := r.regtmp(9);
                  v.mstslvctrl.base1000_t_hd := r.regtmp(8);
                end if;
              when others =>  --no writable bits for other regs
                null;
            end case;
          end if;
        end if;
      when others =>
        null;
    end case;
    if r.rstcnt > 19 then
      v.ctrl.reset := '0'; v.rstcnt := 0;
    else
      v.rstcnt := r.rstcnt + 1; 
    end if;
    if (v.ctrl.reset and not r.ctrl.reset) = '1' then
      v.rstcnt := 0; 
    end if;
    if r.ctrl.anegen = '1' then
      if r.anegcnt < 10 then
        v.anegcnt := r.anegcnt + 1;
      else
        v.status.anegcmpt := '1';
        if (base1000_x_fd = 1) or (base1000_x_hd = 1) or
           (r.mstslvctrl.base1000_t_fd = '1') or
           (r.mstslvctrl.base1000_t_hd = '1') then
          v.ctrl.speedsel(1 downto 0) := "01";
        elsif (r.anegadv.tech_ability(4) = '1') or
              (r.anegadv.tech_ability(3) = '1') or
              (r.anegadv.tech_ability(2) = '1') or
              (base100_t2_fd = 1) or (base100_t2_hd = 1) then
          v.ctrl.speedsel(1 downto 0) := "10";
        else
          v.ctrl.speedsel(1 downto 0) := "00";
        end if;
        if ((base1000_x_fd = 1) or (r.mstslvctrl.base1000_t_fd = '1')) or
           (((base100_t2_fd = 1) or (r.anegadv.tech_ability(3) = '1')) and
           (r.mstslvctrl.base1000_t_hd = '0') and (base1000_x_hd = 0)) or
           ((r.anegadv.tech_ability(1) = '1') and (base100_t2_hd = 0) and
           (r.anegadv.tech_ability(4) = '0') and
           (r.anegadv.tech_ability(2) = '0')) then
          v.ctrl.duplexmode := '1';
        else
          v.ctrl.duplexmode := '0';
        end if;
      end if;
    end if;
    if r.ctrl.restartaneg = '1' then
      v.anegcnt := 0;
      v.status.anegcmpt := '0';
      v.ctrl.restartaneg := '0';
    end if;
    rin <= v;
  end process;

  reg : process(rstn, mdc) is
  begin
    if rising_edge(mdc) then
      r <= rin;
    end if;
--     -- RESET DELAY
--     if rstd = '1' then
--       r.ctrl.reset <= '1';
--     else
--       r.ctrl.reset <= '0';
--     end if;

    -- RESET
    if (r.ctrl.reset or not rstn) = '1' then
      r.ctrl.loopback <= '1'; r.anegcnt <= 0;
      if (base1000_x_hd = 1) or (base1000_x_fd = 1) or (base1000_t_hd = 1) or
         (base1000_t_fd = 1) then
        r.ctrl.speedsel <= "01";
      elsif (base100_x_hd = 1) or (base100_t2_hd = 1) or (base100_x_fd = 1) or
            (base100_t2_fd = 1) or (base100_t4 = 1) then
        r.ctrl.speedsel <= "10";
      else
        r.ctrl.speedsel <= "00";
      end if;
        
      r.ctrl.anegen <= conv_std_logic(aneg = 1);
      r.ctrl.powerdown <= '0';
      r.ctrl.isolate <= '0';
      r.ctrl.restartaneg <= '0';
      if (base100_x_hd = 0) and (hd_10 = 0) and (base100_t2_hd = 0) and
         (base1000_x_hd = 0) and (base1000_t_hd = 0) then
        r.ctrl.duplexmode <= '1';
      else
        r.ctrl.duplexmode <= '0';
      end if;
      r.ctrl.coltest <= '0';
      
      r.status.base100_t4 <= conv_std_logic(base100_t4 = 1);
      r.status.base100_x_fd <= conv_std_logic(base100_x_fd = 1);
      r.status.base100_x_hd <= conv_std_logic(base100_x_hd = 1);  
      r.status.fd_10 <= conv_std_logic(fd_10 = 1);        
      r.status.hd_10 <= conv_std_logic(hd_10 = 1);        
      r.status.base100_t2_fd <= conv_std_logic(base100_t2_fd = 1);
      r.status.base100_t2_hd <= conv_std_logic(base100_t2_hd = 1);
      r.status.extstat <= conv_std_logic((base1000_x_fd = 1) or
                                         (base1000_x_hd = 1) or
                                         (base1000_t_fd = 1) or
                                         (base1000_t_hd = 1)); 
      r.status.mfpreamblesup <= '0';
      r.status.anegcmpt <= '0';      
      r.status.remfault <= '0';     
      r.status.anegability <= conv_std_logic(aneg = 1); 
      r.status.linkstat <= '0';     
      r.status.jabdetect <= '0';    
      r.status.extcap <= conv_std_logic(extended_regs = 1);

      r.anegadv.next_page <= '0';
      r.anegadv.remote_fault <= '0';
      r.anegadv.tech_ability <= "000" & conv_std_logic(base100_t4 = 1) &
        conv_std_logic(base100_x_fd = 1) & conv_std_logic(base100_x_hd = 1) &
        conv_std_logic(fd_10 = 1) & conv_std_logic(hd_10 = 1);
      r.anegadv.selector <= "00001";

      r.aneglp.next_page <= '0';
      r.aneglp.remote_fault <= '0';
      r.aneglp.tech_ability <= "000" & conv_std_logic(base100_t4 = 1) &
        conv_std_logic(base100_x_fd = 1) & conv_std_logic(base100_x_hd = 1) &
        conv_std_logic(fd_10 = 1) & conv_std_logic(hd_10 = 1);
      r.aneglp.selector <= "00001";

      r.anegexp.par_detct_flt <= '0';
      r.anegexp.lp_np_able    <= '0';
      r.anegexp.np_able       <= '0';
      r.anegexp.page_rx       <= '0';
      r.anegexp.lp_aneg_able  <= '0';

      r.anegnptx.next_page     <= '0';
      r.anegnptx.message_page  <= '1';
      r.anegnptx.ack2          <= '0';
      r.anegnptx.toggle        <= '0';
      r.anegnptx.message       <= "00000000001";

      r.anegnplp.next_page     <= '0';
      r.anegnplp.message_page  <= '1';
      r.anegnplp.ack2          <= '0';
      r.anegnplp.toggle        <= '0';
      r.anegnplp.message       <= "00000000001";

      r.mstslvctrl.tmode         <= (others => '0');
      r.mstslvctrl.manualcfgen   <= '0';
      r.mstslvctrl.cfgval        <= '0';
      r.mstslvctrl.porttype      <= '0';
      r.mstslvctrl.base1000_t_fd <= conv_std_logic(base1000_t_fd = 1);
      r.mstslvctrl.base1000_t_hd <= conv_std_logic(base1000_t_fd = 1);

      r.mstslvstat.cfgfault         <= '0';
      r.mstslvstat.cfgres           <= '1';
      r.mstslvstat.locrxstate       <= '1';
      r.mstslvstat.remrxstate       <= '1';
      r.mstslvstat.lpbase1000_t_fd  <= conv_std_logic(base1000_t_fd = 1);
      r.mstslvstat.lpbase1000_t_hd  <= conv_std_logic(base1000_t_fd = 1);
      r.mstslvstat.idlerrcnt        <= (others => '0');
      
      r.extstatus.base1000_x_fd <= conv_std_logic(base1000_x_fd = 1);
      r.extstatus.base1000_x_hd <= conv_std_logic(base1000_x_hd = 1);
      r.extstatus.base1000_t_fd <= conv_std_logic(base1000_t_fd = 1);
      r.extstatus.base1000_t_hd <= conv_std_logic(base1000_t_hd = 1);
      
    end if;
    
    if rstn = '0' then
      r.cnt <= 0; r.state <= idle; r.rstcnt <= 0;
      r.ctrl.reset <= '1'; 
    end if;
  end process;

  loopback_sel : process(r, gtx_clk, txd, tx_en, tx_er, lb_rxd, lb_rxer, lb_rxdv) is
  begin
    rx_col <= '0'; rx_crs <= '1';
    rxd <= (others => '0'); rx_dv <= '0'; rx_er <= '0';
    if r.ctrl.loopback = '1' then
      case ifmode is
        when mode_mii | mode_gmii =>
          -- Use TX ring buffer
          rxd <= lb_rxd;
          rx_dv <= lb_rxdv;
          rx_er <= lb_rxer;
        when mode_rmii =>
          rx_dv <= '1'; rx_er <= '1'; --unused should not affect anything
          rx_col <= '0'; rx_crs <= tx_en;
          if tx_en = '1' then
            rxd(1 downto 0) <= txd(1 downto 0);
          end if;
        when mode_rgmii =>
          if (gtx_clk = '1' and tx_en = '0') then
            rxd(3 downto 0) <= r.ctrl.duplexmode & r.ctrl.speedsel & r.status.linkstat;
          end if;
      end case;
    end if;
  end process;


  txsamp: process(txd,tx_en,tx_er,gtx_clk,int_clk,clkslow)

    variable lasttxtr, lastclk: time;
    variable lastclk_valid: boolean := false;
    constant tSU_GMII : time := 2.5 ns;
    constant tH_GMII  : time := 0.5 ns;
    constant tCTOmax_MII : time := 25.0 ns;

    procedure sample_data(tSU: time) is
    begin
      txdataq(txqpos*10 to txqpos*10+9) <= (txd & tx_en & tx_er);
      txqpos <= (txqpos+1) mod qlength;
      assert (now-lasttxtr) >= tSU
        report "Setup violation on txd/tx_en/tx_er" severity warning;
      lastclk := now;
      lastclk_valid := true;
    end procedure;

    procedure holdcheck(tH: time) is
    begin
      assert (not lastclk_valid) or (lastclk >= lasttxtr) or (lasttxtr-lastclk >= tH)
        report "Hold violation on txd/tx_en/tx_er" severity warning;
    end procedure;

  begin
    if txd'event or tx_en'event or tx_er'event then lasttxtr:=now; end if;
    case ifmode is
      when mode_gmii =>
        if r.ctrl.speedsel="01" or gmii100=1 then
          if rising_edge(gtx_clk) then sample_data(2.5 ns); end if;
          holdcheck(0.5 ns);
        else
          if rising_edge(clkslow) then sample_data(15.0 ns); end if;
        end if;
      when mode_mii =>
        if rising_edge(int_clk) then sample_data(15.0 ns); end if;
      when others =>
    end case;
  end process;

  rxloopback: process(int_clk)
    variable twin: time;
  begin
    if rising_edge(int_clk) then
      if r.ctrl.loopback='0' then
        rxqpos <= (txqpos + (qlength/2)) mod qlength;
      else
        rxqpos <= (rxqpos+1) mod qlength;
      end if;
      case ifmode is
        when mode_mii => twin := 20.0 ns;
        when mode_gmii => twin := 3.0 ns;
        when others => twin := 1.0 ns;
      end case;
      lb_rxd <= txdataq(rxqpos*10 to rxqpos*10+7), (others => '0') after twin;
      lb_rxdv <= txdataq(rxqpos*10+8), '0' after twin;
      lb_rxer <= txdataq(rxqpos*10+9), '0' after twin;
    end if;
  end process;

end;
-- pragma translate_on

