trashernet/trashernet/trashernet_phy.vhd

414 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_types.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
-- Eth
rx_data : out byte; -- RX Data
rx_data_valid : out std_logic; -- RX Data valid
rx_active : out std_logic; -- RX of packet in progress
tx_data : in byte; -- TX Data
tx_data_en : in std_logic; -- Transmitter enable
tx_data_ack : out std_logic; -- Latched data_tx
tx_active : out std_logic; -- Transmission in progress
carrier_detect : out std_logic; -- Carrier detected
rx_error : out std_logic; -- Receive error
-- 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))); -- Every 16 ms
signal nlp_timeout_cnt : integer range 0 to NLP_TIMEOUT_CNT_MAX;
begin
-- Synchronize RX input
synchronizer_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';
rx_active <= '0';
rx_error <= '0';
elsif rising_edge(clk) then
bit_stb <= '0';
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;
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
rx_error <= '1';
demanchestization_state <= ERROR;
end if;
if (not transition_activity) then
demanchestization_state <= SYNC;
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
rx_data_valid <= '0';
elsif rising_edge(clk) then
rx_data_valid <= '0';
if rx_active then
if (bit_stb) then
rx_data <= bit_value & rx_data(rx_data'high downto rx_data'low + 1);
if (bit_cnt = 7) then
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;
carrier_detect <= '1' when nlp_timeout_cnt /= 0 else '0';
end block receiver;
-- -------------------------------------------------------------------------
transmitter : block
constant TX_STB_CNT_MAX : integer := F_CLK / (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(tx_data'range);
signal bit_stage : std_logic;
constant BIT_CNT_MAX_NLP : integer := 16000 / 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 <= tx_data;
bit_stage <= '0';
bit_cnt <= BIT_CNT_MAX_DATA;
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;
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
tx_data_ack <= '0';
tx_stb_cnt <= TX_STB_CNT_MAX;
tx_mode <= OFF;
go_idle;
elsif rising_edge(clk) then
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 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 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;
tx_active <= '1' when tx_state /= IDLE 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) xor bit_stage;
tx_n <= not (sr(sr'low) xor bit_stage);
end case;
end if;
end process driver;
end block transmitter;
end architecture rtl;