Compare commits

...

2 Commits

Author SHA1 Message Date
3e1a53abaf Move header fields into records 2025-09-11 13:00:09 +02:00
c9f6560091 Add RMII PHY 2025-09-11 10:54:28 +02:00
10 changed files with 655 additions and 92 deletions

View File

@ -4,7 +4,7 @@
Trashernet is a very trashy Ethernet stack for FPGAs written in VHDL aiming to cover all OSI layers: Trashernet is a very trashy Ethernet stack for FPGAs written in VHDL aiming to cover all OSI layers:
* Layer 1, Physical: `trashernet_phy` * Layer 1, Physical: `trashernet_phy`, (`trashernet_rmii`)
* Layer 2, Data link: `trashernet_mac`, `trashernet_eth`, `trashernet_arp` * Layer 2, Data link: `trashernet_mac`, `trashernet_eth`, `trashernet_arp`
* Layer 3, Network: `trashernet_ipv4`, `trashernet_ipv4prot`, `trashernet_icmp` * Layer 3, Network: `trashernet_ipv4`, `trashernet_ipv4prot`, `trashernet_icmp`
@ -16,6 +16,8 @@ When writing it, the following were the main design philosophies:
**IMPORTANT:** This code (and HW suggestions) violate the Ethernet standard in many ways. Do not use in proper (especially commercial) products. **IMPORTANT:** This code (and HW suggestions) violate the Ethernet standard in many ways. Do not use in proper (especially commercial) products.
Note: The `trashernet_rmii` component uses a standard RMII Ethernet PHY instead of the hardware suggestions below. If you are looking for the true Trashernet experience, choose the `trashernet_phy` instead.
## Hardware ## Hardware
![Circuit Diagram](doc/circuit.svg) ![Circuit Diagram](doc/circuit.svg)

View File

@ -0,0 +1,218 @@
-- -------------------------------------------------------------------------- --
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
-- -------------------------------------------------------------------------- --
-- bench_trashernet_mac.vhd : Stimulus-only test bench for the MAC+PHY parts
-- Tests TX path through the RX path. Not great, but whatever.
-- -------------------------------------------------------------------------- --
-- 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 work.bench_pkg.all;
library trashernet;
use trashernet.trashernet_pkg.all;
library vunit_lib;
context vunit_lib.vunit_context;
entity bench_trashernet_rmii is
generic(
runner_cfg : string
);
end entity bench_trashernet_rmii;
architecture bench of bench_trashernet_rmii is
signal clk : std_logic;
signal rst : std_logic;
signal phy_out : phy_out_t;
signal phy_in : phy_in_t;
signal rmii_ref_clk : std_logic;
signal rmii_crs_dv : std_logic;
signal rmii_rxd : std_logic_vector(1 downto 0);
signal rmii_tx_en : std_logic;
signal rmii_txd : std_logic_vector(1 downto 0);
signal rmii_tx_start : std_logic := '0';
signal trashernet_tx_start : std_logic := '0';
begin
trashernet_rmii_inst : entity trashernet.trashernet_rmii
generic map(
SYSCLK_IS_REFCLK => false
)
port map(
clk => clk,
rst => rst,
phy_out => phy_out,
phy_in => phy_in,
rmii_ref_clk => rmii_ref_clk,
rmii_crs_dv => rmii_crs_dv,
rmii_rxd => rmii_rxd,
rmii_tx_en => rmii_tx_en,
rmii_txd => rmii_txd
);
clockDriver : process
constant period : time := 10 ns;
begin
clk <= '0';
wait for period / 2;
clk <= '1';
wait for period / 2;
end process clockDriver;
test : process is
begin
rst <= '1';
test_runner_setup(runner, runner_cfg);
wait for 10 ns;
rst <= '0';
wait for 10 ns;
while test_suite loop
if run("rx") then
rmii_tx_start <= not rmii_tx_start;
wait until phy_out.rx_active = '1' for 1 ms;
assert phy_out.rx_active'event report "Timeout for reception to start";
wait until phy_out.rx_active = '0' for 1 ms;
assert phy_out.rx_active'event report "Timeout for reception to end";
wait for 1 us;
elsif run("tx") then
trashernet_tx_start <= not trashernet_tx_start;
wait until phy_out.tx_active = '1' for 1 ms;
assert phy_out.tx_active'event report "Timeout for transmission to start";
wait until phy_out.tx_active = '0' for 1 ms;
assert phy_out.tx_active'event report "Timeout for transmission to stop";
wait until phy_out.tx_active = '1' for 1 ms;
assert phy_out.tx_active'event report "Timeout for transmission to start";
wait until phy_out.tx_active = '0' for 1 ms;
assert phy_out.tx_active'event report "Timeout for transmission to stop";
end if;
end loop;
wait for 1 ns;
test_runner_cleanup(runner);
end process test;
phy_rx : process(clk) is
begin
if rising_edge(clk) then
if phy_out.rx_data_valid = '1' then
report "RX data: " & to_hstring(phy_out.rx_data);
end if;
end if;
end process phy_rx;
rmiirefclk : process
constant period : time := 20 ns;
begin
rmii_ref_clk <= '0';
wait for period / 2;
rmii_ref_clk <= '1';
wait for period / 2;
end process rmiirefclk;
rmii_tx_gen : process is
procedure send_frame(data : byte_vector) is
variable sr : byte;
begin
wait until rising_edge(rmii_ref_clk);
rmii_rxd <= "01";
wait for 2.5 ns;
rmii_crs_dv <= '1';
for i in 0 to 10 loop
wait until rmii_ref_clk;
end loop;
rmii_rxd <= "11";
wait until rmii_ref_clk;
for i in data'range loop
sr := data(i);
for j in 0 to 3 loop
rmii_rxd <= sr(1 downto 0);
wait until rising_edge(rmii_ref_clk);
sr := "XX" & sr(sr'high downto 2);
end loop;
end loop;
rmii_crs_dv <= '0';
wait for 1 us; -- IPG
end procedure send_frame;
begin
rmii_crs_dv <= '0';
rmii_rxd <= (others => '0');
wait on rmii_tx_start;
send_frame(byte_vector'(x"c6", x"b8", x"c1", x"db", x"b1", x"1d", x"00", x"ff", x"ff", x"11", x"22", x"33", x"08", x"00", x"45", x"00", x"00", x"29", x"00", x"00", x"00", x"00", x"40", x"11", x"f5", x"70", x"c0", x"a8", x"02", x"02", x"c0", x"a8", x"02", x"01", x"ab", x"cd", x"00", x"ff", x"00", x"15", x"00", x"00", x"48", x"65", x"6c", x"6c", x"6f", x"20", x"57", x"6f", x"72", x"6c", x"64", x"21", x"0a", x"00", x"00", x"00", x"00", x"00", x"64", x"90", x"a9", x"ea"));
wait;
end process rmii_tx_gen;
trashernet_tx_gen : process is
procedure send_frame(data : byte_vector) is
begin
for i in data'range loop
phy_in.tx_data_en <= '1';
phy_in.tx_data <= data(i);
wait until rising_edge(clk);
while not phy_out.tx_data_ack loop
wait until rising_edge(clk);
end loop;
end loop;
phy_in.tx_data_en <= '0';
wait until rising_edge(clk);
end procedure send_frame;
begin
phy_in.tx_data_en <= '0';
wait on trashernet_tx_start;
wait until rising_edge(clk);
send_frame(byte_vector'(x"c6", x"b8", x"c1", x"db", x"b1", x"1d", x"00", x"ff", x"ff", x"11", x"22", x"33", x"08", x"00", x"45", x"00", x"00", x"29", x"00", x"00", x"00", x"00", x"40", x"11", x"f5", x"70", x"c0", x"a8", x"02", x"02", x"c0", x"a8", x"02", x"01", x"ab", x"cd", x"00", x"ff", x"00", x"15", x"00", x"00", x"48", x"65", x"6c", x"6c", x"6f", x"20", x"57", x"6f", x"72", x"6c", x"64", x"21", x"0a", x"00", x"00", x"00", x"00", x"00", x"64", x"90", x"a9", x"ea"));
-- Currently, there's a bug in the CDC preventing single-cycle deassertions from deasserting the transmit enable
wait until rising_edge(clk);
wait until rising_edge(clk);
wait until rising_edge(clk);
send_frame(byte_vector'(x"c6", x"b8", x"c1", x"db", x"b1", x"1d", x"00", x"ff", x"ff", x"11", x"22", x"33", x"08", x"00", x"45", x"00", x"00", x"29", x"00", x"00", x"00", x"00", x"40", x"11", x"f5", x"70", x"c0", x"a8", x"02", x"02", x"c0", x"a8", x"02", x"01", x"ab", x"cd", x"00", x"ff", x"00", x"15", x"00", x"00", x"48", x"65", x"6c", x"6c", x"6f", x"20", x"57", x"6f", x"72", x"6c", x"64", x"21", x"0a", x"00", x"00", x"00", x"00", x"00", x"64", x"90", x"a9", x"ea"));
end process trashernet_tx_gen;
rmii_receiver : process(rmii_ref_clk) is
variable sr : byte;
variable cnt : integer range 0 to 3;
variable active : boolean := false;
begin
if rising_edge(rmii_ref_clk) then
if rmii_tx_en then
if not active then
report "RMII RX start";
end if;
sr := rmii_txd & sr(sr'high downto 2);
if cnt = 3 then
cnt := 0;
report "RMII RX: " & to_hstring(sr);
else
cnt := cnt + 1;
end if;
active := true;
else
if active then
report "RMII RX stop";
end if;
cnt := 0;
active := false;
end if;
end if;
end process rmii_receiver;
end architecture bench;

View File

@ -360,7 +360,7 @@ begin
elsif rising_edge(clk) then elsif rising_edge(clk) then
if ethernet_i_out.rx_header_rcv then if ethernet_i_out.rx_header_rcv then
report "RX ETH-I: " & to_string(to_integer(ethernet_i_out.rx_length)); report "RX ETH-I: " & to_string(to_integer(ethernet_i_out.rx_header.length));
end if; end if;
if (ethernet_ii_out(PROT_ARP).rx_header_rcv) then if (ethernet_ii_out(PROT_ARP).rx_header_rcv) then
report "RX ARP"; report "RX ARP";
@ -373,7 +373,7 @@ begin
report "RX IP error"; report "RX IP error";
end if; end if;
if ipv4_out.rx_header_rcv then if ipv4_out.rx_header_rcv then
report "RX IP start. PROT=" & to_hstring(ipv4_out.rx_protocol) & ", FROM=" & ip_to_string(ipv4_out.rx_ip_address); report "RX IP start. PROT=" & to_hstring(ipv4_out.rx_header.protocol) & ", FROM=" & ip_to_string(ipv4_out.rx_header.ip_address);
end if; end if;
end if; end if;
end process receiver; end process receiver;
@ -455,8 +455,8 @@ begin
elsif rising_edge(clk) then elsif rising_edge(clk) then
if udp_out.rx_header_rcv then if udp_out.rx_header_rcv then
report "UDP: RX on port " & -- report "UDP: RX on port " & --
to_string(to_integer(udp_out.rx_destination_port)) & " from port " & -- to_string(to_integer(udp_out.rx_header.destination_port)) & " from port " & --
to_string(to_integer(udp_out.rx_source_port)); to_string(to_integer(udp_out.rx_header.source_port));
end if; end if;
if udp_out.rx_data_valid then if udp_out.rx_data_valid then
report "UDP: RX: " & to_hstring(udp_out.rx_data); report "UDP: RX: " & to_hstring(udp_out.rx_data);

View File

@ -87,7 +87,7 @@ begin
-- Shared fields in the Ethernet II application interface -- Shared fields in the Ethernet II application interface
shared_fields : for i in ethernet_ii_out'range generate shared_fields : for i in ethernet_ii_out'range generate
ethernet_ii_out(i).rx_mac_address <= mac_out.rx_header.mac_source; ethernet_ii_out(i).rx_header.mac_header <= mac_out.rx_header;
ethernet_ii_out(i).rx_data <= mac_out.rx_mac_data; ethernet_ii_out(i).rx_data <= mac_out.rx_mac_data;
ethernet_ii_out(i).rx_data_valid <= mac_out.rx_mac_valid when sel = i else '0'; ethernet_ii_out(i).rx_data_valid <= mac_out.rx_mac_valid when sel = i else '0';
ethernet_ii_out(i).rx_crc_error <= mac_out.rx_mac_crc_error when sel = i else '0'; ethernet_ii_out(i).rx_crc_error <= mac_out.rx_mac_crc_error when sel = i else '0';
@ -101,8 +101,8 @@ begin
ethernet_i_out.rx_crc_error <= mac_out.rx_mac_crc_error when sel = SEL_ETH_I else '0'; ethernet_i_out.rx_crc_error <= mac_out.rx_mac_crc_error when sel = SEL_ETH_I else '0';
ethernet_i_out.rx_crc_ok <= mac_out.rx_mac_crc_ok when sel = SEL_ETH_I else '0'; ethernet_i_out.rx_crc_ok <= mac_out.rx_mac_crc_ok when sel = SEL_ETH_I else '0';
ethernet_i_out.rx_header_rcv <= rx_mac_header_rcv_delayed when sel = SEL_ETH_I else '0'; ethernet_i_out.rx_header_rcv <= rx_mac_header_rcv_delayed when sel = SEL_ETH_I else '0';
ethernet_i_out.rx_mac_address <= mac_out.rx_header.mac_source; ethernet_i_out.rx_header.mac_header <= mac_out.rx_header;
ethernet_i_out.rx_length <= unsigned(mac_out.rx_header.mac_ethertype(0)) & unsigned(mac_out.rx_header.mac_ethertype(1)); ethernet_i_out.rx_header.length <= unsigned(mac_out.rx_header.mac_ethertype(0)) & unsigned(mac_out.rx_header.mac_ethertype(1));
-- TX fields (need to be driven here to avoid multiple drivers (blocks drive ALL signals of a record)) -- TX fields (need to be driven here to avoid multiple drivers (blocks drive ALL signals of a record))
tx_fields : for i in ethernet_ii_out'range generate tx_fields : for i in ethernet_ii_out'range generate

View File

@ -140,8 +140,8 @@ begin
case state is case state is
when IDLE => when IDLE =>
if tx_response then if tx_response then
ipv4_protocol_in.tx_ip_address <= ipv4_protocol_out.rx_ip_address; -- TODO: This is technically too late, but ¯\_(ツ)_/¯ ipv4_protocol_in.tx_ip_address <= ipv4_protocol_out.rx_header.ip_address; -- TODO: This is technically too late, but ¯\_(ツ)_/¯
ipv4_protocol_in.tx_length <= ipv4_protocol_out.rx_length; ipv4_protocol_in.tx_length <= ipv4_protocol_out.rx_header.length;
sr <= byte_vector'( sr <= byte_vector'(
x"00", x"00",
x"00", x"00",

View File

@ -131,9 +131,10 @@ begin
ipv4_out.rx_data <= ethernet_ii_out.rx_data; ipv4_out.rx_data <= ethernet_ii_out.rx_data;
ipv4_out.rx_data_valid <= ethernet_ii_out.rx_data_valid when state = PAYLOAD else '0'; ipv4_out.rx_data_valid <= ethernet_ii_out.rx_data_valid when state = PAYLOAD else '0';
ipv4_out.rx_ip_address <= sr_source_ip; ipv4_out.rx_header.eth_header <= ethernet_ii_out.rx_header;
ipv4_out.rx_protocol <= sr_protocol; ipv4_out.rx_header.ip_address <= sr_source_ip;
ipv4_out.rx_length <= unsigned(std_logic_vector'(sr_total_length(2) & sr_total_length(3))) - to_integer(unsigned(sr_ihl)) * 4; ipv4_out.rx_header.protocol <= sr_protocol;
ipv4_out.rx_header.length <= unsigned(std_logic_vector'(sr_total_length(2) & sr_total_length(3))) - to_integer(unsigned(sr_ihl)) * 4;
end block rx; end block rx;

View File

@ -57,7 +57,7 @@ begin
if ipv4_out.rx_header_rcv then if ipv4_out.rx_header_rcv then
rx_sel <= SEL_PROTOCOL_NONE; rx_sel <= SEL_PROTOCOL_NONE;
for i in IPV4_PROTOCOLS'range loop for i in IPV4_PROTOCOLS'range loop
if (ipv4_out.rx_protocol = IPV4_PROTOCOLS(i)) then if (ipv4_out.rx_header.protocol = IPV4_PROTOCOLS(i)) then
rx_sel <= i; rx_sel <= i;
end if; end if;
end loop; end loop;
@ -67,8 +67,7 @@ begin
mux : for i in ipv4_protocol_out'range generate mux : for i in ipv4_protocol_out'range generate
ipv4_protocol_out(i).rx_data <= ipv4_out.rx_data; ipv4_protocol_out(i).rx_data <= ipv4_out.rx_data;
ipv4_protocol_out(i).rx_ip_address <= ipv4_out.rx_ip_address; ipv4_protocol_out(i).rx_header <= ipv4_out.rx_header;
ipv4_protocol_out(i).rx_length <= ipv4_out.rx_length;
ipv4_protocol_out(i).rx_data_valid <= ipv4_out.rx_data_valid when rx_sel = i else '0'; ipv4_protocol_out(i).rx_data_valid <= ipv4_out.rx_data_valid when rx_sel = i else '0';
ipv4_protocol_out(i).rx_error_stb <= ipv4_out.rx_error_stb when rx_sel = i else '0'; ipv4_protocol_out(i).rx_error_stb <= ipv4_out.rx_error_stb when rx_sel = i else '0';
ipv4_protocol_out(i).rx_ok_stb <= ipv4_out.rx_ok_stb when rx_sel = i else '0'; ipv4_protocol_out(i).rx_ok_stb <= ipv4_out.rx_ok_stb when rx_sel = i else '0';

View File

@ -13,6 +13,10 @@ use ieee.std_logic_1164.all;
use ieee.numeric_std.all; use ieee.numeric_std.all;
package trashernet_pkg is package trashernet_pkg is
-- ----------------
-- Type definitions
-- ----------------
-- General types -- General types
subtype byte is std_logic_vector(7 downto 0); subtype byte is std_logic_vector(7 downto 0);
type byte_vector is array (natural range <>) of byte; type byte_vector is array (natural range <>) of byte;
@ -23,18 +27,56 @@ package trashernet_pkg is
function mac_to_string(constant mac_address : in mac_addr_t) return string; function mac_to_string(constant mac_address : in mac_addr_t) return string;
subtype ethertype_t is byte_vector(0 to 1); subtype ethertype_t is byte_vector(0 to 1);
type mac_header_fields is record type mac_header_t is record
mac_destination : mac_addr_t; -- Destination MAC address mac_destination : mac_addr_t; -- Destination MAC address
mac_source : mac_addr_t; -- Source MAC address mac_source : mac_addr_t; -- Source MAC address
mac_ethertype : ethertype_t; -- Ethertype or length mac_ethertype : ethertype_t; -- Ethertype or length
end record mac_header_fields; end record mac_header_t;
-- Ethernet specific types
type ethernet_i_header_t is record
mac_header : mac_header_t; -- MAC layer header
length : unsigned(15 downto 0); -- RX payload length in bytes
end record;
type ethernet_ii_header_t is record
mac_header : mac_header_t; -- MAC layer header
end record;
-- IP specific types -- IP specific types
subtype ip_addr_t is byte_vector(0 to 3); subtype ip_addr_t is byte_vector(0 to 3);
function ip_to_string(constant ip_address : in ip_addr_t) return string; function ip_to_string(constant ip_address : in ip_addr_t) return string;
function to_std_logic_vector(ip_address : ip_addr_t) return std_logic_vector; function to_std_logic_vector(ip_address : ip_addr_t) return std_logic_vector;
-- Configuration interface subtype ipv4_protocol is byte;
type ipv4_protocol_vector is array (natural range <>) of ipv4_protocol;
constant IPV4_PROTOCOL_ICMP : ipv4_protocol := x"01";
constant IPV4_PROTOCOL_TCP : ipv4_protocol := x"06";
constant IPV4_PROTOCOL_UDP : ipv4_protocol := x"11";
subtype ipv4_length is unsigned(15 downto 0);
type ipv4_header_t is record
ip_address : ip_addr_t; -- Source IP address
protocol : ipv4_protocol; -- Transport Protocol
length : ipv4_length; -- Telegram length (excluding header)
eth_header : ethernet_ii_header_t; -- Eth II header
end record ipv4_header_t;
-- UDP specific types
subtype udp_port_t is unsigned(15 downto 0);
subtype udp_length_t is unsigned(15 downto 0);
type udp_header_t is record
source_port : udp_port_t; -- UDP source port
destination_port : udp_port_t; -- UDP destination port
length : udp_length_t; -- UDP length
ipv4_header : ipv4_header_t; -- IPv4 header
end record;
-- ------------------------
-- Configuration interfaces
-- ------------------------
-- System configuration
type configuration_t is record type configuration_t is record
mac_address : mac_addr_t; -- MAC address of this node mac_address : mac_addr_t; -- MAC address of this node
end record configuration_t; end record configuration_t;
@ -44,6 +86,19 @@ package trashernet_pkg is
subnet_mask : ip_addr_t; -- Subnet mask of this node subnet_mask : ip_addr_t; -- Subnet mask of this node
end record ipv4_configuration_t; end record ipv4_configuration_t;
-- MAC ETH interface
type ethernet_ii_protocol_t is record
ethertype : ethertype_t;
end record;
type ethernet_ii_protocol_vector is array (natural range <>) of ethernet_ii_protocol_t;
constant ETHERNET_II_PROTOCOLS_NONE : ethernet_ii_protocol_vector(0 to -1) := (others => (ethertype => (x"00", x"00"))); -- @suppress "Null range": We want a NULL vector here to remove the logic
constant ETHERNET_II_PROTOCOL_IPV4 : ethernet_ii_protocol_t := (ethertype => (x"08", x"00"));
constant ETHERNET_II_PROTOCOL_ARP : ethernet_ii_protocol_t := (ethertype => (x"08", x"06"));
-- ----------------------
-- Application interfaces
-- ----------------------
-- PHY interface -- PHY interface
type phy_in_t is record type phy_in_t is record
tx_data : byte; -- TX Data tx_data : byte; -- TX Data
@ -63,12 +118,12 @@ package trashernet_pkg is
-- MAC interface -- MAC interface
type mac_in_t is record type mac_in_t is record
tx_header : mac_header_fields; -- TX MAC Header Data tx_header : mac_header_t; -- TX MAC Header Data
tx_mac_data : byte; -- Payload tx_mac_data : byte; -- Payload
tx_mac_data_en : std_logic; -- Start (and keep) transmitting a frame tx_mac_data_en : std_logic; -- Start (and keep) transmitting a frame
end record mac_in_t; end record mac_in_t;
type mac_out_t is record type mac_out_t is record
rx_header : mac_header_fields; -- RX MAC Header Data rx_header : mac_header_t; -- RX MAC Header Data
rx_mac_header_rcv : std_logic; -- `rx_mac` header have been received and are valid rx_mac_header_rcv : std_logic; -- `rx_mac` header have been received and are valid
rx_mac_data : byte; -- Ethernet data (after Ethertype) rx_mac_data : byte; -- Ethernet data (after Ethertype)
@ -80,24 +135,17 @@ package trashernet_pkg is
rx_mac_crc_error : std_logic; -- End of packet, CRC invalid rx_mac_crc_error : std_logic; -- End of packet, CRC invalid
end record mac_out_t; end record mac_out_t;
-- MAC ETH interface -- Ethernet I interface
type ethernet_ii_protocol_t is record
ethertype : ethertype_t;
end record;
type ethernet_ii_protocol_vector is array (natural range <>) of ethernet_ii_protocol_t;
constant ETHERNET_II_PROTOCOLS_NONE : ethernet_ii_protocol_vector(0 to -1) := (others => (ethertype => (x"00", x"00"))); -- @suppress "Null range": We want a NULL vector here to remove the logic
constant ETHERNET_II_PROTOCOL_IPV4 : ethernet_ii_protocol_t := (ethertype => (x"08", x"00"));
constant ETHERNET_II_PROTOCOL_ARP : ethernet_ii_protocol_t := (ethertype => (x"08", x"06"));
type ethernet_i_out_t is record type ethernet_i_out_t is record
rx_mac_address : mac_addr_t; -- Source MAC address
rx_crc_ok : std_logic; -- End of packet, CRC OK rx_crc_ok : std_logic; -- End of packet, CRC OK
rx_crc_error : std_logic; -- End of packet, CRC invalid rx_crc_error : std_logic; -- End of packet, CRC invalid
rx_header : ethernet_i_header_t;
rx_header_rcv : std_logic; -- Start of reception, `header` is valid
rx_data : byte; -- RX data rx_data : byte; -- RX data
rx_data_valid : std_logic; -- RX data valid strobe rx_data_valid : std_logic; -- RX data valid strobe
rx_length : unsigned(15 downto 0); -- RX payload length in bytes
rx_header_rcv : std_logic; -- Start of reception, `rx_length` is valid
tx_data_ack : std_logic; -- Give next data byte or disable `tx_en` tx_data_ack : std_logic; -- Give next data byte or disable `tx_en`
end record; end record;
type ethernet_i_in_t is record type ethernet_i_in_t is record
@ -108,13 +156,17 @@ package trashernet_pkg is
end record; end record;
constant ETHERNET_I_IN_UNUSED : ethernet_i_in_t := (tx_mac_address => (others => x"00"), tx_data => x"00", tx_length => x"0000", others => '0'); -- TODO constant ETHERNET_I_IN_UNUSED : ethernet_i_in_t := (tx_mac_address => (others => x"00"), tx_data => x"00", tx_length => x"0000", others => '0'); -- TODO
-- Ethernet II interface
type ethernet_ii_out_t is record type ethernet_ii_out_t is record
rx_mac_address : mac_addr_t; -- Source MAC address rx_header : ethernet_ii_header_t;
rx_data : byte; -- RX data rx_header_rcv : std_logic; -- Start of reception
rx_data_valid : std_logic; -- RX data valid strobe
rx_crc_ok : std_logic; -- End of packet, CRC OK rx_crc_ok : std_logic; -- End of packet, CRC OK
rx_crc_error : std_logic; -- End of packet, CRC invalid rx_crc_error : std_logic; -- End of packet, CRC invalid
rx_header_rcv : std_logic; -- Start of reception
rx_data : byte; -- RX data
rx_data_valid : std_logic; -- RX data valid strobe
tx_data_ack : std_logic; -- Give next data byte or disable `tx_en` tx_data_ack : std_logic; -- Give next data byte or disable `tx_en`
end record; end record;
type ethernet_ii_in_t is record type ethernet_ii_in_t is record
@ -138,23 +190,15 @@ package trashernet_pkg is
end record arp_in_t; end record arp_in_t;
-- IPv4 interface -- IPv4 interface
subtype ipv4_protocol is byte;
type ipv4_protocol_vector is array (natural range <>) of ipv4_protocol;
constant IPV4_PROTOCOL_ICMP : ipv4_protocol := x"01";
constant IPV4_PROTOCOL_TCP : ipv4_protocol := x"06";
constant IPV4_PROTOCOL_UDP : ipv4_protocol := x"11";
subtype ipv4_length is unsigned(15 downto 0);
type ipv4_out_t is record type ipv4_out_t is record
rx_ip_address : ip_addr_t; -- Source IP address rx_header : ipv4_header_t;
rx_protocol : ipv4_protocol; -- Transport Protocol rx_header_rcv : std_logic; -- Start of reception (`rx_ip_address` and `rx_protocol` are valid)
rx_length : ipv4_length; -- Telegram length (excluding header)
rx_ok_stb : std_logic; -- End of packet, checksum OK
rx_error_stb : std_logic; -- End of packet, checksum invalid
rx_data : byte; -- RX data rx_data : byte; -- RX data
rx_data_valid : std_logic; -- RX data valid strobe rx_data_valid : std_logic; -- RX data valid strobe
rx_ok_stb : std_logic; -- End of packet, checksum OK
rx_error_stb : std_logic; -- End of packet, checksum invalid
rx_header_rcv : std_logic; -- Start of reception (`rx_ip_address` and `rx_protocol` are valid)
tx_data_ack : std_logic; -- Give next data byte of disable `tx_en` tx_data_ack : std_logic; -- Give next data byte of disable `tx_en`
tx_ok_stb : std_logic; -- Transmission successful tx_ok_stb : std_logic; -- Transmission successful
@ -169,14 +213,14 @@ package trashernet_pkg is
end record ipv4_in_t; end record ipv4_in_t;
type ipv4_protocol_out_t is record type ipv4_protocol_out_t is record
rx_ip_address : ip_addr_t; -- Source IP address rx_header : ipv4_header_t;
rx_length : ipv4_length; -- Telegram length (excluding header) rx_header_rcv : std_logic; -- Start of reception
rx_ok_stb : std_logic; -- End of packet, checksum OK
rx_error_stb : std_logic; -- End of packet, checksum invalid
rx_data : byte; -- RX data rx_data : byte; -- RX data
rx_data_valid : std_logic; -- RX data valid strobe rx_data_valid : std_logic; -- RX data valid strobe
rx_ok_stb : std_logic; -- End of packet, checksum OK
rx_error_stb : std_logic; -- End of packet, checksum invalid
rx_header_rcv : std_logic; -- Start of reception
tx_data_ack : std_logic; -- Give next data byte of disable `tx_en` tx_data_ack : std_logic; -- Give next data byte of disable `tx_en`
tx_ok_stb : std_logic; -- Transmission successful tx_ok_stb : std_logic; -- Transmission successful
@ -193,16 +237,12 @@ package trashernet_pkg is
type ipv4_protocol_in_vector is array (natural range <>) of ipv4_protocol_in_t; type ipv4_protocol_in_vector is array (natural range <>) of ipv4_protocol_in_t;
-- UDP interface -- UDP interface
subtype udp_port_t is unsigned(15 downto 0);
subtype udp_length_t is unsigned(15 downto 0);
type udp_out_t is record type udp_out_t is record
rx_data : byte; -- RX Data rx_data : byte; -- RX Data
rx_data_valid : std_logic; -- RX data valid strobe rx_data_valid : std_logic; -- RX data valid strobe
rx_header_rcv : std_logic; -- Start of reception rx_header : udp_header_t; -- UDP header
rx_source_port : udp_port_t; -- UDP source port rx_header_rcv : std_logic; -- Start of reception, `rx_header` valid
rx_destination_port : udp_port_t; -- UDP destination port
rx_length : udp_length_t; -- UDP length
rx_ok_stb : std_logic; -- End of packet, checksum OK rx_ok_stb : std_logic; -- End of packet, checksum OK
rx_error_stb : std_logic; -- End of packet, checksum invalid rx_error_stb : std_logic; -- End of packet, checksum invalid
@ -236,7 +276,9 @@ package trashernet_pkg is
subtype portnum is integer range 0 to 65535; subtype portnum is integer range 0 to 65535;
type udp_port_vector is array (natural range <>) of portnum; type udp_port_vector is array (natural range <>) of portnum;
-- ------------------------
-- General helper functions -- General helper functions
-- ------------------------
function to_std_logic(constant bool : boolean) return std_logic; function to_std_logic(constant bool : boolean) return std_logic;
end package trashernet_pkg; end package trashernet_pkg;

View File

@ -0,0 +1,300 @@
-- -------------------------------------------------------------------------- --
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
-- -------------------------------------------------------------------------- --
-- trashernet_rmii.vhd : Ethernet OSI Layer 1, Physical
-- Implements interface to an RMII PHY.
-- -------------------------------------------------------------------------- --
-- 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_rmii is
generic(
SYSCLK_IS_REFCLK : boolean := false -- Do not generate synchronizers between the RMII and system clock domains
);
port(
-- Global
clk : in std_logic; -- Global clock (must not be slower than rmii_ref_clk)
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)
-- RMII physical signals
rmii_ref_clk : in std_logic; -- Synchronous clock reference for receive, transmit and control interface
rmii_crs_dv : in std_logic; -- Carrier Sense/Receive Data Valid
rmii_rxd : in std_logic_vector(1 downto 0); -- Receive Data
rmii_tx_en : out std_logic; -- Transmit Enable
rmii_txd : out std_logic_vector(1 downto 0) -- Transmit Data
);
end entity trashernet_rmii;
architecture rtl of trashernet_rmii is
signal rmii_rst : std_logic;
signal rmii_crs_dv_sync : std_logic;
begin
synchronizer_inst : entity work.synchronizer
generic map(
SIZE => 2
)
port map(
clk => clk,
rst => rst,
data_in => rmii_crs_dv,
data_out => rmii_crs_dv_sync
);
rmii_reset_gen : if SYSCLK_IS_REFCLK generate
rmii_rst <= rst;
else generate
rmii_reset_gen_p : process(rmii_ref_clk, rst) is
begin
if rst then
rmii_rst <= '1';
elsif rising_edge(rmii_ref_clk) then
rmii_rst <= '0';
end if;
end process rmii_reset_gen_p;
end generate rmii_reset_gen;
receive : block
type state_t is (IDLE, DATA);
signal state : state_t;
signal sr : byte;
signal dibit_cnt : integer range 0 to 3;
signal temp_rx_data : byte;
signal temp_rx_active : std_logic;
signal temp_rx_data_valid : std_logic;
begin
rxp : process(rmii_rst, rmii_ref_clk) is
begin
if rmii_rst then
state <= IDLE;
elsif rising_edge(rmii_ref_clk) then
-- Shift in data bytes
sr <= rmii_rxd & sr(sr'high downto 2);
if dibit_cnt = 3 then
dibit_cnt <= 0;
else
dibit_cnt <= dibit_cnt + 1;
end if;
-- Sync header
case state is
when IDLE =>
if sr = x"D5" then -- Sync header
state <= DATA;
dibit_cnt <= 1;
end if;
when DATA =>
null;
end case;
-- Stop receiving whenever CRSDV disappears
if rmii_crs_dv_sync = '0' then
state <= IDLE;
end if;
end if;
end process rxp;
temp_rx_data <= sr;
temp_rx_active <= '1' when (state = DATA) else '0'; -- TODO: We might want to delay this by one cycle
temp_rx_data_valid <= '1' when (state = DATA) and (dibit_cnt = 0) else '0';
phy_out.rx_error <= '0'; -- We don't implement RXER
phy_out.carrier_detect <= '1'; -- TODO: Not yet implemented.
cdc_or_register : if SYSCLK_IS_REFCLK generate
register_out : process(rmii_rst, rmii_ref_clk) is
begin
if rmii_rst then
phy_out.rx_data <= (others => '0');
phy_out.rx_active <= '0';
phy_out.rx_data_valid <= '0';
elsif rising_edge(rmii_ref_clk) then
phy_out.rx_data <= temp_rx_data;
phy_out.rx_active <= temp_rx_active;
phy_out.rx_data_valid <= temp_rx_data_valid;
end if;
end process register_out;
else generate
-- CDC
synchronizer_inst : entity work.synchronizer
generic map(
SIZE => 2
)
port map(
clk => clk,
rst => rst,
data_in => temp_rx_active,
data_out => phy_out.rx_active
);
cdc_strobe_inst : entity work.cdc_strobe
generic map(
SYNCHRONIZERS => 2
)
port map(
a_clk => rmii_ref_clk,
a_rst => rmii_rst,
a_in => temp_rx_data_valid,
b_clk => clk,
b_rst => rst,
b_out => phy_out.rx_data_valid
);
-- Not a synchronizer, just a byte latch in the rmii_ref_clk domain
rx_data_reg : process(rmii_ref_clk) is
begin
if rising_edge(rmii_ref_clk) then
if temp_rx_data_valid then
phy_out.rx_data <= temp_rx_data; -- TODO: We are assuming that the system clock domains runs at least x times faster than the PHY clock domain
end if;
end if;
end process rx_data_reg;
end generate cdc_or_register;
end block receive;
transmitter : block
constant SYNC_HEADER_SIZE_BYTES : natural := 8 - 1; -- Sync header 0x55 bytes ()
constant IPG_SIZE_BYTES : natural := 96 / 8;
signal temp_tx_data : byte;
signal temp_tx_data_en : std_logic;
signal temp_tx_data_ack : std_logic;
type state_t is (IDLE, HEADER, DATA, IPG);
signal state : state_t;
signal sr : byte;
signal dibit_cnt : integer range 0 to 3;
signal byte_cnt : integer range 0 to maximum(SYNC_HEADER_SIZE_BYTES, IPG_SIZE_BYTES) - 1;
signal byte_done : std_logic;
signal block_done : std_logic;
begin
cdc_or_register : if SYSCLK_IS_REFCLK generate
temp_tx_data_en <= phy_in.tx_data_en;
temp_tx_data <= phy_in.tx_data;
-- -----------------------------------------------------------------
phy_out_reg : process(rst, clk) is
begin
if rst then
phy_out.tx_data_ack <= '0';
elsif rising_edge(clk) then
phy_out.tx_data_ack <= temp_tx_data_ack;
end if;
end process phy_out_reg;
else generate
synchronizer_txdv_inst : entity work.synchronizer
generic map(
SIZE => 2
)
port map(
clk => rmii_ref_clk,
rst => rmii_rst,
data_in => phy_in.tx_data_en,
data_out => temp_tx_data_en
);
temp_tx_data <= phy_in.tx_data; -- TODO: Again, we are making assumptions about the clock relationship here
-- -----------------------------------------------------------------
cdc_strobe_inst : entity work.cdc_strobe
generic map(
SYNCHRONIZERS => 2
)
port map(
a_clk => rmii_ref_clk,
a_rst => rmii_rst,
a_in => temp_tx_data_ack,
b_clk => clk,
b_rst => rst,
b_out => phy_out.tx_data_ack
);
end generate cdc_or_register;
txp : process(rmii_ref_clk, rmii_rst) is
begin
if rmii_rst then
state <= IDLE;
elsif rising_edge(rmii_ref_clk) then
sr <= "00" & sr(sr'high downto 2);
if dibit_cnt = 3 then
dibit_cnt <= 0;
else
dibit_cnt <= dibit_cnt + 1;
end if;
if byte_done then
if byte_cnt > 0 then
byte_cnt <= byte_cnt - 1;
else
end if;
end if;
case state is
when IDLE =>
sr <= x"55";
byte_cnt <= SYNC_HEADER_SIZE_BYTES - 1;
dibit_cnt <= 0;
if temp_tx_data_en then
state <= HEADER;
end if;
when HEADER =>
sr <= x"55";
if not temp_tx_data_en then
state <= IDLE;
elsif block_done then
sr <= x"D5";
state <= DATA;
end if;
when DATA =>
if byte_done then
if temp_tx_data_en then
sr <= temp_tx_data;
else
state <= IPG;
byte_cnt <= IPG_SIZE_BYTES - 1;
end if;
end if;
when IPG =>
if block_done then
state <= IDLE;
end if;
end case;
end if;
end process txp;
byte_done <= '1' when dibit_cnt = 3 else '0';
block_done <= '1' when (byte_cnt = 0) and (byte_done = '1') else '0';
temp_tx_data_ack <= '1' when (state = DATA) and (byte_done = '1') and (temp_tx_data_en = '1') else '0';
phy_out.tx_active <= '1' when (state = HEADER) or (state = DATA) else '0';
rmii_txd <= sr(1 downto 0);
rmii_tx_en <= '1' when (state = HEADER) or (state = DATA) else '0';
end block transmitter;
end architecture rtl;

View File

@ -90,9 +90,10 @@ begin
end if; end if;
end process rxp; end process rxp;
udp_out.rx_source_port <= unsigned(std_logic_vector'(sr_source_port(0) & sr_source_port(1))); udp_out.rx_header.source_port <= unsigned(std_logic_vector'(sr_source_port(0) & sr_source_port(1)));
udp_out.rx_destination_port <= unsigned(std_logic_vector'(sr_destination_port(2) & sr_destination_port(3))); udp_out.rx_header.destination_port <= unsigned(std_logic_vector'(sr_destination_port(2) & sr_destination_port(3)));
udp_out.rx_length <= unsigned(std_logic_vector'(sr_length(4) & sr_length(5))); udp_out.rx_header.length <= unsigned(std_logic_vector'(sr_length(4) & sr_length(5)));
udp_out.rx_header.ipv4_header <= ipv4_protocol_out.rx_header;
end block receiver; end block receiver;
transmitter : block transmitter : block