408 lines
12 KiB
VHDL
408 lines
12 KiB
VHDL
-- -------------------------------------------------------------------------- --
|
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
|
-- -------------------------------------------------------------------------- --
|
|
-- trashernet_phy.vhd : Ethernet OSI Layer 1, Physical
|
|
-- Implements low-level bit encoding and timing and frame synchronization.
|
|
-- -------------------------------------------------------------------------- --
|
|
-- 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_phy is
|
|
generic(
|
|
F_CLK : in integer -- Clock frequency
|
|
);
|
|
port(
|
|
-- Global
|
|
clk : in std_logic; -- Global clock
|
|
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)
|
|
|
|
-- Ethernet physical signals
|
|
rx_p : in std_logic;
|
|
tx_p : out std_logic;
|
|
tx_n : out std_logic
|
|
);
|
|
end entity trashernet_phy;
|
|
|
|
architecture rtl of trashernet_phy is
|
|
constant F_ETH : integer := 10000000;
|
|
|
|
begin
|
|
|
|
receiver : block
|
|
-- Signal conditioning
|
|
signal rx : std_logic;
|
|
signal rx_last : std_logic;
|
|
signal rx_last_static : std_logic;
|
|
signal rx_edge : std_logic;
|
|
|
|
-- Bit recovery
|
|
signal bit_value : std_logic;
|
|
signal bit_stb : std_logic;
|
|
|
|
-- Bytizer
|
|
signal bit_cnt : integer range 0 to 7;
|
|
|
|
-- NLP supervision
|
|
constant NLP_TIMEOUT_CNT_MAX : integer := integer(round(real(F_CLK) * 16.0 * 10.0 ** (-3))) - 1; -- Every 16 ms
|
|
signal nlp_timeout_cnt : integer range 0 to NLP_TIMEOUT_CNT_MAX;
|
|
begin
|
|
-- Synchronize RX input
|
|
synchronizer_rxp_inst : entity work.synchronizer
|
|
port map(
|
|
clk => clk,
|
|
rst => rst,
|
|
data_in => rx_p,
|
|
data_out => rx
|
|
);
|
|
|
|
-- Edge detector for RX (+glitch filter)
|
|
edgedet : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
rx_last <= '0';
|
|
rx_last_static <= '0';
|
|
rx_edge <= '0';
|
|
|
|
elsif rising_edge(clk) then
|
|
rx_edge <= '0';
|
|
if (rx_last = rx) then
|
|
rx_edge <= rx_last_static xor rx;
|
|
rx_last_static <= rx;
|
|
end if;
|
|
rx_last <= rx;
|
|
end if;
|
|
end process edgedet;
|
|
|
|
demanchestizer : block
|
|
-- Transition detector
|
|
constant BIT_LENGTH_LONG : integer := F_CLK / F_ETH - 1;
|
|
constant BIT_LENGTH_SHORT : integer := F_CLK / (F_ETH * 2) - 1;
|
|
constant BIT_LENGTH_TOLERANCE : integer := (F_CLK / F_ETH) / 5;
|
|
constant BIT_LENGTH_TIMEOUT : integer := BIT_LENGTH_LONG + BIT_LENGTH_TOLERANCE + 1;
|
|
signal sample_count : integer range 0 to BIT_LENGTH_TIMEOUT;
|
|
type transition_duration_t is (SHORT, LONG);
|
|
signal transition_duration : transition_duration_t;
|
|
signal transition_stb : std_logic;
|
|
signal transition_activity : std_logic;
|
|
|
|
-- Transition analysis
|
|
signal last_transition : transition_duration_t;
|
|
type bit_ev_t is (NONE, TOGGLE, KEEP, ERROR);
|
|
signal bit_ev : bit_ev_t;
|
|
|
|
-- Bit recovery
|
|
type demanchestization_state_t is (SYNC, DATA, ERROR);
|
|
signal demanchestization_state : demanchestization_state_t;
|
|
begin
|
|
-- Detects spacing of transitions
|
|
transition_detector : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
transition_stb <= '0';
|
|
sample_count <= 0;
|
|
|
|
elsif rising_edge(clk) then
|
|
transition_stb <= '0';
|
|
|
|
if rx_edge then
|
|
if (sample_count = BIT_LENGTH_TIMEOUT) then -- First edge, ignore this transition
|
|
sample_count <= 0;
|
|
else
|
|
if ((sample_count >= BIT_LENGTH_SHORT - BIT_LENGTH_TOLERANCE) and (sample_count <= BIT_LENGTH_SHORT + BIT_LENGTH_TOLERANCE)) then
|
|
transition_duration <= SHORT;
|
|
transition_stb <= '1';
|
|
sample_count <= 0;
|
|
elsif ((sample_count >= BIT_LENGTH_LONG - BIT_LENGTH_TOLERANCE) and (sample_count <= BIT_LENGTH_LONG + BIT_LENGTH_TOLERANCE)) then
|
|
transition_duration <= LONG;
|
|
transition_stb <= '1';
|
|
sample_count <= 0;
|
|
end if;
|
|
end if;
|
|
else
|
|
if (sample_count /= BIT_LENGTH_TIMEOUT) then
|
|
sample_count <= sample_count + 1;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process transition_detector;
|
|
transition_activity <= '1' when sample_count /= BIT_LENGTH_TIMEOUT else '0';
|
|
|
|
-- Converts the spacing of transitions into a toggle-no-toggle stream
|
|
transition_analyzer : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
last_transition <= LONG;
|
|
bit_ev <= NONE;
|
|
|
|
elsif rising_edge(clk) then
|
|
bit_ev <= NONE;
|
|
|
|
if transition_stb then
|
|
case last_transition is
|
|
when LONG => -- @suppress: Exit condition through indirect assignment
|
|
if transition_duration = LONG then
|
|
bit_ev <= TOGGLE;
|
|
end if;
|
|
last_transition <= transition_duration;
|
|
when SHORT =>
|
|
if (transition_duration = SHORT) then
|
|
bit_ev <= KEEP;
|
|
else
|
|
bit_ev <= ERROR;
|
|
end if;
|
|
last_transition <= LONG;
|
|
end case;
|
|
end if;
|
|
|
|
if (not transition_activity) then
|
|
last_transition <= LONG;
|
|
end if;
|
|
end if;
|
|
end process transition_analyzer;
|
|
|
|
-- Synchronizes the Manchester level to the Ethernet header which is TOGGLE TOGGLE .. TOGGLE NO-TOGGLE for ..1010101011
|
|
bit_recovery : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
demanchestization_state <= SYNC;
|
|
bit_stb <= '0';
|
|
phy_out.rx_active <= '0';
|
|
phy_out.rx_error <= '0';
|
|
elsif rising_edge(clk) then
|
|
bit_stb <= '0';
|
|
phy_out.rx_error <= '0';
|
|
|
|
if (bit_ev /= NONE) then
|
|
case demanchestization_state is
|
|
when SYNC =>
|
|
if (bit_ev = KEEP) then
|
|
bit_value <= '1';
|
|
demanchestization_state <= DATA;
|
|
phy_out.rx_active <= '1';
|
|
end if;
|
|
when DATA => -- @suppress: Condition outside of case allows to exit this state
|
|
bit_value <= not bit_value when bit_ev = TOGGLE else bit_value;
|
|
bit_stb <= '1';
|
|
when ERROR => -- @suppress: Condition outside of case allows to exit this state
|
|
null;
|
|
end case;
|
|
end if;
|
|
|
|
if (bit_ev = ERROR) then
|
|
phy_out.rx_error <= '1';
|
|
demanchestization_state <= ERROR;
|
|
end if;
|
|
|
|
if (not transition_activity) then
|
|
demanchestization_state <= SYNC;
|
|
phy_out.rx_active <= '0';
|
|
end if;
|
|
end if;
|
|
end process bit_recovery;
|
|
end block demanchestizer;
|
|
|
|
-- Latches data into an 8-bit vector
|
|
bytizer : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
phy_out.rx_data_valid <= '0';
|
|
|
|
elsif rising_edge(clk) then
|
|
phy_out.rx_data_valid <= '0';
|
|
|
|
if phy_out.rx_active then
|
|
if (bit_stb) then
|
|
phy_out.rx_data <= bit_value & phy_out.rx_data(phy_out.rx_data'high downto phy_out.rx_data'low + 1);
|
|
if (bit_cnt = 7) then
|
|
phy_out.rx_data_valid <= '1';
|
|
bit_cnt <= 0;
|
|
else
|
|
bit_cnt <= bit_cnt + 1;
|
|
end if;
|
|
end if;
|
|
else
|
|
bit_cnt <= 0;
|
|
end if;
|
|
end if;
|
|
end process bytizer;
|
|
|
|
-- Supervises for NLP or data presence
|
|
nlp_timeout_p : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
nlp_timeout_cnt <= 0;
|
|
elsif rising_edge(clk) then
|
|
if rx_edge then -- Technically, we should use only the rising edge here, but a project called `trashernet` probably won't mind ;)
|
|
nlp_timeout_cnt <= NLP_TIMEOUT_CNT_MAX;
|
|
else
|
|
if (nlp_timeout_cnt /= 0) then
|
|
nlp_timeout_cnt <= nlp_timeout_cnt - 1;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process nlp_timeout_p;
|
|
phy_out.carrier_detect <= '1' when nlp_timeout_cnt /= 0 else '0';
|
|
end block receiver;
|
|
|
|
-- -------------------------------------------------------------------------
|
|
|
|
transmitter : block
|
|
constant TX_STB_CNT_MAX : integer := integer(round(real(F_CLK) / real((F_ETH * 2)))) - 1;
|
|
signal tx_stb_cnt : integer range 0 to TX_STB_CNT_MAX;
|
|
|
|
type tx_state_t is (IDLE, NLP, TX, IPG);
|
|
signal tx_state : tx_state_t;
|
|
signal sr : std_logic_vector(phy_in.tx_data'range);
|
|
signal bit_stage : std_logic;
|
|
|
|
constant BIT_CNT_MAX_NLP : integer := 16000000 / 100; -- 16 ms (timebase 100 ns)
|
|
constant BIT_CNT_MAX_IPG : integer := 96;
|
|
constant BIT_CNT_MAX_DATA : integer := sr'length - 1;
|
|
signal bit_cnt : integer range 0 to maximum(maximum(BIT_CNT_MAX_NLP, BIT_CNT_MAX_DATA), BIT_CNT_MAX_IPG);
|
|
|
|
type tx_mode_t is (OFF, NLP, ACTIVE);
|
|
signal tx_mode : tx_mode_t;
|
|
begin
|
|
tx_main : process(clk, rst) is
|
|
procedure transmit_byte is
|
|
begin
|
|
tx_state <= TX;
|
|
tx_mode <= ACTIVE;
|
|
|
|
sr <= phy_in.tx_data;
|
|
bit_stage <= '0';
|
|
bit_cnt <= BIT_CNT_MAX_DATA;
|
|
phy_out.tx_data_ack <= '1';
|
|
end procedure transmit_byte;
|
|
|
|
procedure transmit_ipg is
|
|
begin
|
|
tx_state <= IPG;
|
|
tx_mode <= OFF;
|
|
|
|
bit_cnt <= BIT_CNT_MAX_IPG;
|
|
bit_stage <= '0';
|
|
phy_out.tx_data_ack <= '1'; -- Notify that the end-of-packet has been accepted
|
|
end procedure transmit_ipg;
|
|
|
|
procedure transmit_nlp is
|
|
begin
|
|
tx_state <= NLP;
|
|
tx_mode <= NLP;
|
|
end procedure transmit_nlp;
|
|
|
|
procedure go_idle is
|
|
begin
|
|
tx_state <= IDLE;
|
|
tx_mode <= OFF;
|
|
|
|
bit_cnt <= BIT_CNT_MAX_NLP;
|
|
bit_stage <= '0';
|
|
end procedure go_idle;
|
|
|
|
variable tx_stb : std_logic; -- Strobe every 50 ns (20 MHz)
|
|
|
|
begin
|
|
if rst then
|
|
phy_out.tx_data_ack <= '0';
|
|
tx_stb_cnt <= TX_STB_CNT_MAX;
|
|
tx_mode <= OFF;
|
|
tx_state <= IDLE;
|
|
bit_cnt <= 0;
|
|
go_idle;
|
|
|
|
elsif rising_edge(clk) then
|
|
phy_out.tx_data_ack <= '0';
|
|
|
|
if tx_stb_cnt = 0 then
|
|
tx_stb_cnt <= TX_STB_CNT_MAX;
|
|
else
|
|
tx_stb_cnt <= tx_stb_cnt - 1;
|
|
end if;
|
|
tx_stb := '1' when tx_stb_cnt = 0 else '0';
|
|
|
|
if tx_stb then
|
|
bit_stage <= not bit_stage;
|
|
end if;
|
|
if tx_stb and bit_stage then -- 100 ns (1 bit)
|
|
if (bit_cnt /= 0) then
|
|
bit_cnt <= bit_cnt - 1;
|
|
end if;
|
|
end if;
|
|
|
|
case tx_state is
|
|
when IDLE =>
|
|
if phy_in.tx_data_en then -- New packet to TX
|
|
transmit_byte;
|
|
bit_stage <= '1';
|
|
tx_stb_cnt <= TX_STB_CNT_MAX; -- resync
|
|
|
|
elsif bit_cnt = 0 then -- NLP timeout
|
|
transmit_nlp;
|
|
|
|
--bit_cnt <= 1; Let's save some resources here...
|
|
bit_stage <= '0';
|
|
tx_stb_cnt <= TX_STB_CNT_MAX; -- resync
|
|
end if;
|
|
when NLP =>
|
|
if tx_stb and bit_stage then -- 100 ns duration
|
|
go_idle;
|
|
end if;
|
|
when TX =>
|
|
if tx_stb then
|
|
if (bit_stage = '1') then
|
|
sr <= '0' & sr(sr'high downto sr'low + 1);
|
|
if bit_cnt = 0 then
|
|
if phy_in.tx_data_en then
|
|
transmit_byte;
|
|
else
|
|
transmit_ipg;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
when IPG =>
|
|
if bit_cnt = 0 then
|
|
go_idle;
|
|
end if;
|
|
end case;
|
|
end if;
|
|
end process tx_main;
|
|
phy_out.tx_active <= '1' when (tx_state /= IDLE and tx_state /= NLP) else '0';
|
|
|
|
driver : process(clk, rst) is
|
|
begin
|
|
if rst then
|
|
tx_p <= '0';
|
|
tx_n <= '0';
|
|
elsif rising_edge(clk) then
|
|
case tx_mode is
|
|
when OFF =>
|
|
tx_p <= '0';
|
|
tx_n <= '0';
|
|
when NLP =>
|
|
tx_p <= '1';
|
|
tx_n <= '0';
|
|
when ACTIVE =>
|
|
tx_p <= sr(sr'low) xnor bit_stage;
|
|
tx_n <= not (sr(sr'low) xnor bit_stage);
|
|
end case;
|
|
end if;
|
|
end process driver;
|
|
|
|
end block transmitter;
|
|
end architecture rtl;
|