trashernet/trashernet/trashernet_phy.vhd

458 lines
14 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;
signal tick_ms : std_logic; -- 1 ms tick (for NLP)
begin
common : block
constant TICK_MS_CNT_MAX : integer := (F_CLK / 1000) - 1;
signal tick_ms_count : integer range 0 to TICK_MS_CNT_MAX;
begin
mstick : process(clk, rst) is
begin
if rst then
tick_ms_count <= TICK_MS_CNT_MAX;
tick_ms <= '0';
elsif rising_edge(clk) then
tick_ms <= '0';
if tick_ms_count = 0 then
tick_ms <= '1';
tick_ms_count <= TICK_MS_CNT_MAX;
else
tick_ms_count <= tick_ms_count - 1;
end if;
end if;
end process mstick;
end block common;
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 := (16 + 8) - 1; -- Every 16 ms (timebase 1 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;
constant DEMANCHESTIZATION_MIN_SYNC_CNT_MAX : integer := (4 * 8) - 1; -- 4 good sync bytes
signal demanchestization_min_sync_cnt : integer range 0 to DEMANCHESTIZATION_MIN_SYNC_CNT_MAX;
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';
demanchestization_min_sync_cnt <= DEMANCHESTIZATION_MIN_SYNC_CNT_MAX;
elsif rising_edge(clk) then
bit_stb <= '0';
phy_out.rx_error <= '0';
if (bit_ev = TOGGLE) and (demanchestization_min_sync_cnt /= 0) then
demanchestization_min_sync_cnt <= demanchestization_min_sync_cnt - 1;
elsif (bit_ev = KEEP) or (bit_ev = ERROR) then
demanchestization_min_sync_cnt <= DEMANCHESTIZATION_MIN_SYNC_CNT_MAX;
end if;
if (bit_ev /= NONE) then
case demanchestization_state is
when SYNC =>
if (bit_ev = KEEP) then
if demanchestization_min_sync_cnt = 0 then
bit_value <= '1';
demanchestization_state <= DATA;
phy_out.rx_active <= '1';
end if;
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' when (demanchestization_state = DATA) else '0';
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';
phy_out.rx_data <= (others => '0'); -- Needed for yosys to compile
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 tick_ms then
if nlp_timeout_cnt /= 0 then
nlp_timeout_cnt <= nlp_timeout_cnt - 1;
end if;
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_IDEAL : real := real(F_CLK) / real((F_ETH * 2));
constant TX_STB_SKIP_ERROR : real := abs (round(TX_STB_CNT_IDEAL) - TX_STB_CNT_IDEAL);
constant TX_STB_CNT_MAX : integer := integer(round(TX_STB_CNT_IDEAL + 0.25)) - 1; -- Round up starting for error > 0.25
constant TX_STB_SKIP_SECOND : boolean := TX_STB_SKIP_ERROR >= 0.25; -- Skip one clock cycle every other symbol to hit clock rate in between when the divider is close to x.5
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_IPG : integer := 96;
constant BIT_CNT_MAX_DATA : integer := sr'length - 1;
signal bit_cnt : integer range 0 to maximum(BIT_CNT_MAX_IPG, BIT_CNT_MAX_DATA);
constant NLP_CNT_MAX : integer := 15; -- specced 16 ms, but there's margin, so let's choose 15 to save a bit (timebase 1 ms)
signal nlp_cnt : integer range 0 to NLP_CNT_MAX;
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';
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;
nlp_cnt <= NLP_CNT_MAX;
bit_stage <= '0';
end procedure go_idle;
variable tx_stb : std_logic; -- Strobe every 50 ns (20 MHz)
variable more_data : std_logic; -- tx_data_en low-latched
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';
more_data := more_data and phy_in.tx_data_en; -- Latch a short low-pulse on tx_en
if tx_stb_cnt = 0 then
tx_stb_cnt <= TX_STB_CNT_MAX;
if TX_STB_SKIP_SECOND and bit_stage = '1' then
tx_stb_cnt <= TX_STB_CNT_MAX - 1;
end if;
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;
if tick_ms then
if (nlp_cnt /= 0) then
nlp_cnt <= nlp_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;
more_data := '1';
bit_stage <= '1';
tx_stb_cnt <= TX_STB_CNT_MAX; -- resync
elsif nlp_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 more_data 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;