301 lines
11 KiB
VHDL
301 lines
11 KiB
VHDL
|
|
-- -------------------------------------------------------------------------- --
|
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
|
-- -------------------------------------------------------------------------- --
|
|
-- trashernet_rmii.vhd : Ethernet OSI Layer 1, Physical
|
|
-- Implements interface to an RMII PHY.
|
|
-- -------------------------------------------------------------------------- --
|
|
-- Author : Markus Koch <markus@notsyncing.net>
|
|
-- Contributors : None
|
|
-- License : Mozilla Public License (MPL) Version 2
|
|
-- -------------------------------------------------------------------------- --
|
|
|
|
library ieee;
|
|
use ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
use ieee.math_real.all;
|
|
|
|
use work.trashernet_pkg.all;
|
|
|
|
entity trashernet_rmii is
|
|
generic(
|
|
SYSCLK_IS_REFCLK : boolean := false -- Do not generate synchronizers between the RMII and system clock domains
|
|
);
|
|
port(
|
|
-- Global
|
|
clk : in std_logic; -- Global clock (must not be slower than rmii_ref_clk)
|
|
rst : in std_logic; -- Asynchronous reset
|
|
|
|
-- PHY application interface
|
|
phy_out : out phy_out_t; -- PHY application IF (out)
|
|
phy_in : in phy_in_t; -- PHY application IF (in)
|
|
|
|
-- RMII physical signals
|
|
rmii_ref_clk : in std_logic; -- Synchronous clock reference for receive, transmit and control interface
|
|
rmii_crs_dv : in std_logic; -- Carrier Sense/Receive Data Valid
|
|
rmii_rxd : in std_logic_vector(1 downto 0); -- Receive Data
|
|
rmii_tx_en : out std_logic; -- Transmit Enable
|
|
rmii_txd : out std_logic_vector(1 downto 0) -- Transmit Data
|
|
);
|
|
end entity trashernet_rmii;
|
|
|
|
architecture rtl of trashernet_rmii is
|
|
signal rmii_rst : std_logic;
|
|
signal rmii_crs_dv_sync : std_logic;
|
|
begin
|
|
synchronizer_inst : entity work.synchronizer
|
|
generic map(
|
|
SIZE => 2
|
|
)
|
|
port map(
|
|
clk => clk,
|
|
rst => rst,
|
|
data_in => rmii_crs_dv,
|
|
data_out => rmii_crs_dv_sync
|
|
);
|
|
|
|
rmii_reset_gen : if SYSCLK_IS_REFCLK generate
|
|
rmii_rst <= rst;
|
|
|
|
else generate
|
|
rmii_reset_gen_p : process(rmii_ref_clk, rst) is
|
|
begin
|
|
if rst then
|
|
rmii_rst <= '1';
|
|
elsif rising_edge(rmii_ref_clk) then
|
|
rmii_rst <= '0';
|
|
end if;
|
|
end process rmii_reset_gen_p;
|
|
end generate rmii_reset_gen;
|
|
|
|
receive : block
|
|
type state_t is (IDLE, DATA);
|
|
signal state : state_t;
|
|
signal sr : byte;
|
|
signal dibit_cnt : integer range 0 to 3;
|
|
|
|
signal temp_rx_data : byte;
|
|
signal temp_rx_active : std_logic;
|
|
signal temp_rx_data_valid : std_logic;
|
|
|
|
begin
|
|
rxp : process(rmii_rst, rmii_ref_clk) is
|
|
begin
|
|
if rmii_rst then
|
|
state <= IDLE;
|
|
|
|
elsif rising_edge(rmii_ref_clk) then
|
|
-- Shift in data bytes
|
|
sr <= rmii_rxd & sr(sr'high downto 2);
|
|
if dibit_cnt = 3 then
|
|
dibit_cnt <= 0;
|
|
else
|
|
dibit_cnt <= dibit_cnt + 1;
|
|
end if;
|
|
|
|
-- Sync header
|
|
case state is
|
|
when IDLE =>
|
|
if sr = x"D5" then -- Sync header
|
|
state <= DATA;
|
|
dibit_cnt <= 1;
|
|
end if;
|
|
|
|
when DATA =>
|
|
null;
|
|
end case;
|
|
|
|
-- Stop receiving whenever CRSDV disappears
|
|
if rmii_crs_dv_sync = '0' then
|
|
state <= IDLE;
|
|
end if;
|
|
end if;
|
|
end process rxp;
|
|
|
|
temp_rx_data <= sr;
|
|
temp_rx_active <= '1' when (state = DATA) else '0'; -- TODO: We might want to delay this by one cycle
|
|
temp_rx_data_valid <= '1' when (state = DATA) and (dibit_cnt = 0) else '0';
|
|
|
|
phy_out.rx_error <= '0'; -- We don't implement RXER
|
|
phy_out.carrier_detect <= '1'; -- TODO: Not yet implemented.
|
|
|
|
cdc_or_register : if SYSCLK_IS_REFCLK generate
|
|
register_out : process(rmii_rst, rmii_ref_clk) is
|
|
begin
|
|
if rmii_rst then
|
|
phy_out.rx_data <= (others => '0');
|
|
phy_out.rx_active <= '0';
|
|
phy_out.rx_data_valid <= '0';
|
|
|
|
elsif rising_edge(rmii_ref_clk) then
|
|
phy_out.rx_data <= temp_rx_data;
|
|
phy_out.rx_active <= temp_rx_active;
|
|
phy_out.rx_data_valid <= temp_rx_data_valid;
|
|
end if;
|
|
end process register_out;
|
|
|
|
else generate
|
|
-- CDC
|
|
synchronizer_inst : entity work.synchronizer
|
|
generic map(
|
|
SIZE => 2
|
|
)
|
|
port map(
|
|
clk => clk,
|
|
rst => rst,
|
|
data_in => temp_rx_active,
|
|
data_out => phy_out.rx_active
|
|
);
|
|
|
|
cdc_strobe_inst : entity work.cdc_strobe
|
|
generic map(
|
|
SYNCHRONIZERS => 2
|
|
)
|
|
port map(
|
|
a_clk => rmii_ref_clk,
|
|
a_rst => rmii_rst,
|
|
a_in => temp_rx_data_valid,
|
|
b_clk => clk,
|
|
b_rst => rst,
|
|
b_out => phy_out.rx_data_valid
|
|
);
|
|
|
|
-- Not a synchronizer, just a byte latch in the rmii_ref_clk domain
|
|
rx_data_reg : process(rmii_ref_clk) is
|
|
begin
|
|
if rising_edge(rmii_ref_clk) then
|
|
if temp_rx_data_valid then
|
|
phy_out.rx_data <= temp_rx_data; -- TODO: We are assuming that the system clock domains runs at least x times faster than the PHY clock domain
|
|
end if;
|
|
end if;
|
|
end process rx_data_reg;
|
|
|
|
end generate cdc_or_register;
|
|
|
|
end block receive;
|
|
|
|
transmitter : block
|
|
constant SYNC_HEADER_SIZE_BYTES : natural := 8 - 1; -- Sync header 0x55 bytes ()
|
|
constant IPG_SIZE_BYTES : natural := 96 / 8;
|
|
|
|
signal temp_tx_data : byte;
|
|
signal temp_tx_data_en : std_logic;
|
|
signal temp_tx_data_ack : std_logic;
|
|
|
|
type state_t is (IDLE, HEADER, DATA, IPG);
|
|
signal state : state_t;
|
|
signal sr : byte;
|
|
signal dibit_cnt : integer range 0 to 3;
|
|
signal byte_cnt : integer range 0 to maximum(SYNC_HEADER_SIZE_BYTES, IPG_SIZE_BYTES) - 1;
|
|
signal byte_done : std_logic;
|
|
signal block_done : std_logic;
|
|
|
|
begin
|
|
cdc_or_register : if SYSCLK_IS_REFCLK generate
|
|
temp_tx_data_en <= phy_in.tx_data_en;
|
|
temp_tx_data <= phy_in.tx_data;
|
|
|
|
-- -----------------------------------------------------------------
|
|
|
|
phy_out_reg : process(rst, clk) is
|
|
begin
|
|
if rst then
|
|
phy_out.tx_data_ack <= '0';
|
|
elsif rising_edge(clk) then
|
|
phy_out.tx_data_ack <= temp_tx_data_ack;
|
|
end if;
|
|
end process phy_out_reg;
|
|
|
|
else generate
|
|
synchronizer_txdv_inst : entity work.synchronizer
|
|
generic map(
|
|
SIZE => 2
|
|
)
|
|
port map(
|
|
clk => rmii_ref_clk,
|
|
rst => rmii_rst,
|
|
data_in => phy_in.tx_data_en,
|
|
data_out => temp_tx_data_en
|
|
);
|
|
temp_tx_data <= phy_in.tx_data; -- TODO: Again, we are making assumptions about the clock relationship here
|
|
|
|
-- -----------------------------------------------------------------
|
|
|
|
cdc_strobe_inst : entity work.cdc_strobe
|
|
generic map(
|
|
SYNCHRONIZERS => 2
|
|
)
|
|
port map(
|
|
a_clk => rmii_ref_clk,
|
|
a_rst => rmii_rst,
|
|
a_in => temp_tx_data_ack,
|
|
b_clk => clk,
|
|
b_rst => rst,
|
|
b_out => phy_out.tx_data_ack
|
|
);
|
|
end generate cdc_or_register;
|
|
|
|
txp : process(rmii_ref_clk, rmii_rst) is
|
|
begin
|
|
if rmii_rst then
|
|
state <= IDLE;
|
|
|
|
elsif rising_edge(rmii_ref_clk) then
|
|
sr <= "00" & sr(sr'high downto 2);
|
|
if dibit_cnt = 3 then
|
|
dibit_cnt <= 0;
|
|
else
|
|
dibit_cnt <= dibit_cnt + 1;
|
|
end if;
|
|
if byte_done then
|
|
if byte_cnt > 0 then
|
|
byte_cnt <= byte_cnt - 1;
|
|
else
|
|
end if;
|
|
end if;
|
|
|
|
case state is
|
|
when IDLE =>
|
|
sr <= x"55";
|
|
byte_cnt <= SYNC_HEADER_SIZE_BYTES - 1;
|
|
dibit_cnt <= 0;
|
|
if temp_tx_data_en then
|
|
state <= HEADER;
|
|
end if;
|
|
|
|
when HEADER =>
|
|
sr <= x"55";
|
|
if not temp_tx_data_en then
|
|
state <= IDLE;
|
|
elsif block_done then
|
|
sr <= x"D5";
|
|
state <= DATA;
|
|
end if;
|
|
|
|
when DATA =>
|
|
if byte_done then
|
|
if temp_tx_data_en then
|
|
sr <= temp_tx_data;
|
|
else
|
|
state <= IPG;
|
|
byte_cnt <= IPG_SIZE_BYTES - 1;
|
|
end if;
|
|
end if;
|
|
when IPG =>
|
|
if block_done then
|
|
state <= IDLE;
|
|
end if;
|
|
end case;
|
|
end if;
|
|
end process txp;
|
|
byte_done <= '1' when dibit_cnt = 3 else '0';
|
|
block_done <= '1' when (byte_cnt = 0) and (byte_done = '1') else '0';
|
|
temp_tx_data_ack <= '1' when (state = DATA) and (byte_done = '1') and (temp_tx_data_en = '1') else '0';
|
|
phy_out.tx_active <= '1' when (state = HEADER) or (state = DATA) else '0';
|
|
|
|
rmii_txd <= sr(1 downto 0);
|
|
rmii_tx_en <= '1' when (state = HEADER) or (state = DATA) else '0';
|
|
end block transmitter;
|
|
end architecture rtl;
|
|
|