diff --git a/README.MD b/README.MD index 6995e82..255e60f 100644 --- a/README.MD +++ b/README.MD @@ -5,7 +5,7 @@ Trashernet is a very trashy Ethernet stack for FPGAs written in VHDL aiming to cover all OSI layers: * Layer 1, Physical: `trashernet_phy` -* Layer 2, Data link: `trashernet_mac`, `trashernet_eth`, `trashernet_arp` +* Layer 2, Data link: `trashernet_mac` (`trashernet_raw_mac`), `trashernet_eth`, `trashernet_arp` * Layer 3, Network: `trashernet_ipv4`, `trashernet_ipv4prot`, `trashernet_icmp` When writing it, the following were the main design philosophies: diff --git a/trashernet/trashernet_pkg.vhd b/trashernet/trashernet_pkg.vhd index 9ed6ded..5d150ee 100644 --- a/trashernet/trashernet_pkg.vhd +++ b/trashernet/trashernet_pkg.vhd @@ -79,6 +79,21 @@ package trashernet_pkg is rx_mac_crc_error : std_logic; -- End of packet, CRC invalid end record mac_out_t; + -- Raw MAC interface + type raw_mac_in_t is record + tx_mac_data : byte; -- Payload + tx_mac_data_en : std_logic; -- Start (and keep) transmitting a frame + end record raw_mac_in_t; + type raw_mac_out_t is record + rx_mac_data : byte; -- Ethernet data (after Ethertype) + rx_mac_valid : std_logic; -- `rx_mac` values (headers + data) are valid + tx_mac_data_ack : std_logic; -- The byte on `tx_mac_data` has been latched. Update to next word. + tx_active : std_logic; -- Transmission in progress + + rx_mac_crc_ok : std_logic; -- End of packet, CRC OK (strobe independent from other rx_mac fields) + rx_mac_crc_error : std_logic; -- End of packet, CRC invalid + end record raw_mac_out_t; + -- MAC ETH interface type ethernet_ii_protocol_t is record ethertype : ethertype_t; diff --git a/trashernet/trashernet_raw_mac.vhd b/trashernet/trashernet_raw_mac.vhd new file mode 100644 index 0000000..fc60154 --- /dev/null +++ b/trashernet/trashernet_raw_mac.vhd @@ -0,0 +1,214 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- trashernet_raw_mac.vhd : Ethernet OSI Layer 2, Data Link +-- Implements packet handling for sync header + CRC. +-- -------------------------------------------------------------------------- -- +-- Author : Markus Koch +-- Contributors : None +-- License : Mozilla Public License (MPL) Version 2 +-- -------------------------------------------------------------------------- -- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.trashernet_pkg.all; + +entity trashernet_raw_mac is + port( + -- Global + clk : in std_logic; -- Global clock + rst : in std_logic; -- Asynchronous reset + + -- PHY signals + phy_out : in phy_out_t; -- PHY application IF (out of PHY) + phy_in : out phy_in_t; -- PHY application IF (into PHY) + + -- MAC application IF + mac_out : out raw_mac_out_t; -- MAC application IF (out of MAC) + mac_in : in raw_mac_in_t -- MAC application IF (into MAC) + ); +end entity trashernet_raw_mac; + +architecture rtl of trashernet_raw_mac is + constant ETH_POLYNOMIAL : std_logic_vector(31 downto 0) := x"04C11DB7"; + constant ETH_FRAME_MIN_LENGTH : integer := 64; + +begin + rx : block + constant CRC_LENGTH : integer := 4; + signal sr_head : byte_vector(0 to CRC_LENGTH - 1); + constant BYTE_COUNT_MAX : integer := CRC_LENGTH; + signal byte_count : integer range 0 to BYTE_COUNT_MAX; + + signal sr_payload : byte_vector(0 to 4); + + signal crc : std_logic_vector(ETH_POLYNOMIAL'range); + signal crc_ok : std_logic; + signal crc_clear : std_logic; + begin + crc_inst : entity work.crc + generic map( + POLYNOMIAL => ETH_POLYNOMIAL, + START => x"FFFFFFFF", + FINAL_XOR => x"FFFFFFFF", + REVERSE_OUT => true + ) + port map( + clk => clk, + rst => rst, + data => phy_out.rx_data, + data_valid => phy_out.rx_data_valid, + crc_clear => crc_clear, + crc_out => crc + ); + + crc_ok <= '1' when crc = x"2144DF1C" else '0'; + + main : process(clk, rst) is + begin + if rst then + byte_count <= 0; + mac_out.rx_mac_valid <= '0'; + mac_out.rx_mac_crc_error <= '0'; + mac_out.rx_mac_crc_ok <= '0'; + crc_clear <= '0'; + + elsif rising_edge(clk) then + mac_out.rx_mac_valid <= '0'; + mac_out.rx_mac_crc_error <= '0'; + mac_out.rx_mac_crc_ok <= '0'; + crc_clear <= '0'; + + if phy_out.rx_data_valid then + sr_payload <= sr_payload(sr_payload'low + 1 to sr_payload'high) & phy_out.rx_data; + if byte_count = CRC_LENGTH then + mac_out.rx_mac_valid <= '1'; + else + byte_count <= byte_count + 1; + end if; + end if; + if not crc_clear and not phy_out.rx_active then + mac_out.rx_mac_crc_ok <= crc_ok; + mac_out.rx_mac_crc_error <= not crc_ok; + end if; + if phy_out.rx_error then + mac_out.rx_mac_crc_error <= '1'; + end if; + + if (not phy_out.rx_active) or phy_out.rx_error then + byte_count <= 0; + crc_clear <= '1'; + end if; + end if; + end process main; + mac_out.rx_mac_data <= sr_payload(0); + + end block rx; + + tx : block + type tx_state_t is (IDLE, DATA, PAD, TXCRC); + signal tx_state : tx_state_t; + + constant SYNC_HEADER : byte_vector(0 to 7) := byte_vector'(0 to 6 => x"55", 7 => x"D5"); + signal sr : byte_vector(0 to SYNC_HEADER'length - 1); + constant SR_CNT_MAX : integer := sr'high; + signal sr_cnt : integer range 0 to SR_CNT_MAX; + constant BYTE_CNT_MAX : integer := ETH_FRAME_MIN_LENGTH - 4 + 1; -- Minus four because the CRC is *after* the PAD, plus one because the logic counts one of the header bytes + signal byte_cnt : integer range 0 to BYTE_CNT_MAX; + + signal crc : std_logic_vector(ETH_POLYNOMIAL'range); + signal crc_clear : std_logic; + signal crc_valid : std_logic; + begin + crc_inst : entity work.crc + generic map( + POLYNOMIAL => ETH_POLYNOMIAL, + START => x"FFFFFFFF", + FINAL_XOR => x"FFFFFFFF", + REVERSE_OUT => true + ) + port map( + clk => clk, + rst => rst, + data => sr(0), + data_valid => crc_valid, + crc_clear => crc_clear, + crc_out => crc + ); + + tx_main : process(clk, rst) is + variable more_data : std_logic; + begin + if rst then + mac_out.tx_mac_data_ack <= '0'; + tx_state <= IDLE; + sr_cnt <= 0; + byte_cnt <= 0; + + elsif rising_edge(clk) then + mac_out.tx_mac_data_ack <= '0'; + crc_valid <= '0'; + more_data := more_data and mac_in.tx_mac_data_en; -- Latch a short low-pulse on tx_en + + if phy_out.tx_data_ack then + sr <= sr(sr'low + 1 to sr'high) & x"00"; + if sr_cnt /= 0 then + sr_cnt <= sr_cnt - 1; + end if; + if byte_cnt /= 0 then + byte_cnt <= byte_cnt - 1; + end if; + end if; + if (sr_cnt /= 0) then + byte_cnt <= BYTE_CNT_MAX; + end if; + case tx_state is + when IDLE => -- Idle + prepare sync header + if not phy_out.tx_active and mac_in.tx_mac_data_en then + more_data := '1'; + sr(SYNC_HEADER'range) <= SYNC_HEADER; + sr_cnt <= 7; + tx_state <= DATA; + end if; + + when DATA => -- Wait for MAC header + transmit payload until tx_mac_data_en goes away + if (phy_out.tx_data_ack = '1') and (sr_cnt = 0) then -- MAC Header TX complete + if more_data then + sr(0) <= mac_in.tx_mac_data; + crc_valid <= '1'; + mac_out.tx_mac_data_ack <= '1'; + else + tx_state <= PAD; + if (byte_cnt /= 0) then -- TODO: just testing! + -- sr(0) will have been shifted to x"00" + crc_valid <= '1'; + end if; + end if; + end if; + + when PAD => -- Wait for PADs / last payload byte + prepare CRC + if phy_out.tx_data_ack then + crc_valid <= '1'; + elsif (byte_cnt = 0) then + sr(0 to 3) <= (crc(7 downto 0), crc(15 downto 8), crc(23 downto 16), crc(31 downto 24)); + sr_cnt <= 4 - 1; + tx_state <= TXCRC; + end if; + + when TXCRC => -- Wait for CRC + jump back to idle + if (phy_out.tx_data_ack = '1') and (sr_cnt = 0) then -- CRC TX complete + tx_state <= IDLE; + end if; + end case; + end if; + end process tx_main; + phy_in.tx_data_en <= '1' when tx_state /= IDLE else '0'; + crc_clear <= '1' when sr_cnt /= 0 else '0'; -- todo: this might be to early + phy_in.tx_data <= sr(sr'low); + + mac_out.tx_active <= phy_out.tx_active; + end block tx; + +end architecture rtl;