458 lines
14 KiB
VHDL
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;
|