Compare commits
13 Commits
1c281bc0f7
...
7bb82a9acf
Author | SHA1 | Date | |
---|---|---|---|
7bb82a9acf | |||
3c1c14f953 | |||
50b65bccec | |||
461c7ebce1 | |||
eab8783c6b | |||
8f4e996d57 | |||
e420b08eb2 | |||
7aa1b56bf9 | |||
8f3420bd54 | |||
4b4e5fdbfa | |||
0e80900426 | |||
fd721ae24f | |||
82d4e6808b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@ cocotb/sim_build
|
|||||||
cocotb/wave.ghw
|
cocotb/wave.ghw
|
||||||
|
|
||||||
cocotb/cocotb_top_mac_test
|
cocotb/cocotb_top_mac_test
|
||||||
|
cocotb/cocotb_top_hwitl
|
||||||
|
@ -5,7 +5,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`
|
||||||
* Layer 2, Data link: `trashernet_mac`
|
* Layer 2, Data link: `trashernet_mac`, `trashernet_eth`
|
||||||
|
|
||||||
When writing it, the following were the main design philosophies:
|
When writing it, the following were the main design philosophies:
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ BASE = $(PWD)/..
|
|||||||
BENCHTOP ?= cocotb_top_mac_test
|
BENCHTOP ?= cocotb_top_mac_test
|
||||||
|
|
||||||
COMPILE_ARGS=--std=08
|
COMPILE_ARGS=--std=08
|
||||||
SIM_ARGS ?= --wave=wave.ghw
|
# SIM_ARGS ?= --wave=wave.ghw
|
||||||
|
|
||||||
VHDL_SOURCES_trashernet += $(BASE)/trashernet/*.vhd
|
VHDL_SOURCES_trashernet += $(BASE)/trashernet/*.vhd
|
||||||
VHDL_SOURCES_design += $(BASE)/design/*.vhd
|
VHDL_SOURCES_design += $(BASE)/design/*.vhd
|
||||||
|
77
cocotb/cocotb_top_hwitl.vhd
Normal file
77
cocotb/cocotb_top_hwitl.vhd
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- cocotb_top_hwitl.vhd : Test bench for cocotb HW-in-the-loop tests
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- 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;
|
||||||
|
|
||||||
|
library design;
|
||||||
|
use design.all;
|
||||||
|
|
||||||
|
entity cocotb_top_hwitl is
|
||||||
|
end entity cocotb_top_hwitl;
|
||||||
|
|
||||||
|
architecture bench of cocotb_top_hwitl is
|
||||||
|
-- DUT signals
|
||||||
|
signal clk : std_logic;
|
||||||
|
signal rst_n : std_logic;
|
||||||
|
signal rx_p : std_logic;
|
||||||
|
signal tx_p : std_logic;
|
||||||
|
signal tx_n : std_logic;
|
||||||
|
signal led_n : std_logic_vector(7 downto 0);
|
||||||
|
signal button_n : std_logic_vector(3 downto 0);
|
||||||
|
signal debug_data : std_logic_vector(7 downto 0);
|
||||||
|
|
||||||
|
-- Generic test bench
|
||||||
|
signal bench_ready : std_logic := '0';
|
||||||
|
begin
|
||||||
|
top_mac_test_inst : entity top_hwitl(eth) -- TODO: I haven't managed to get configurations working with cocotb yet, so changing this line changes the test case :(
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
rst_n => rst_n,
|
||||||
|
rx_p => rx_p,
|
||||||
|
tx_p => tx_p,
|
||||||
|
tx_n => tx_n,
|
||||||
|
led_n => led_n,
|
||||||
|
button_n => button_n,
|
||||||
|
debug_data => debug_data
|
||||||
|
);
|
||||||
|
|
||||||
|
clock_driver : process
|
||||||
|
constant period : time := 20 ns;
|
||||||
|
begin
|
||||||
|
clk <= '0';
|
||||||
|
wait for period / 2;
|
||||||
|
clk <= '1';
|
||||||
|
wait for period / 2;
|
||||||
|
end process clock_driver;
|
||||||
|
|
||||||
|
rstsim : process is
|
||||||
|
begin
|
||||||
|
rst_n <= '0';
|
||||||
|
wait for 400 ns;
|
||||||
|
rst_n <= '1';
|
||||||
|
wait for 100 ns;
|
||||||
|
wait until rising_edge(clk);
|
||||||
|
bench_ready <= '1';
|
||||||
|
wait;
|
||||||
|
end process rstsim;
|
||||||
|
|
||||||
|
cocovc_eth_inst : entity work.cocovc_eth
|
||||||
|
port map(
|
||||||
|
rx_p => tx_p,
|
||||||
|
rx_n => tx_n,
|
||||||
|
tx_p => rx_p,
|
||||||
|
tx_n => open
|
||||||
|
);
|
||||||
|
|
||||||
|
button_n <= (others => '1');
|
||||||
|
|
||||||
|
end architecture bench;
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import *
|
from cocotb.triggers import *
|
||||||
@ -13,6 +13,14 @@ import time
|
|||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import fcntl
|
import fcntl
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Set up virtual device using
|
||||||
|
sudo ip link add virt0 type dummy
|
||||||
|
sudo ip link set up virt0
|
||||||
|
sudo ip addr add 192.168.2.10/24 dev virt0
|
||||||
|
'''
|
||||||
|
|
||||||
class MacDevReceiver():
|
class MacDevReceiver():
|
||||||
def __init__(self, dut, eth_tx, eth_rx, dev):
|
def __init__(self, dut, eth_tx, eth_rx, dev):
|
||||||
@ -28,13 +36,21 @@ class MacDevReceiver():
|
|||||||
ETH_P_ALL=3
|
ETH_P_ALL=3
|
||||||
self.macdev=socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
|
self.macdev=socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
|
||||||
self.macdev.bind((dev, 0))
|
self.macdev.bind((dev, 0))
|
||||||
fcntl.fcntl(self.macdev, fcntl.F_SETFL, os.O_NONBLOCK)
|
fcntl.fcntl(self.macdev, fcntl.F_SETFL, os.O_NONBLOCK) # Not the best way to poll, but I couldn't get asyncio to play nicely with threading...
|
||||||
|
|
||||||
|
def eth_fcs(self, data):
|
||||||
|
crc = zlib.crc32(data) & 0xFFFF_FFFF
|
||||||
|
return crc.to_bytes(4, byteorder='little')
|
||||||
|
|
||||||
async def main(self):
|
async def main(self):
|
||||||
ETH_HEAD = b'\x55\x55\x55\x55\xD5'
|
ETH_HEAD = b'\x55\x55\x55\x55\xD5'
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
r = self.macdev.recv(2000)
|
r = self.macdev.recv(2000)
|
||||||
|
if len(r) < 60:
|
||||||
|
r += b'\x00' * (60 - len(r))
|
||||||
|
r += self.eth_fcs(r)
|
||||||
|
self.dut._log.debug("DUT RX: " + str(r))
|
||||||
await self.eth_tx.send(ETH_HEAD + r);
|
await self.eth_tx.send(ETH_HEAD + r);
|
||||||
except:
|
except:
|
||||||
await Timer(1, "us")
|
await Timer(1, "us")
|
||||||
@ -42,7 +58,14 @@ class MacDevReceiver():
|
|||||||
async def main_rx(self):
|
async def main_rx(self):
|
||||||
while True:
|
while True:
|
||||||
frame = await self.eth_rx.queue.get()
|
frame = await self.eth_rx.queue.get()
|
||||||
self.dut._log.info("RX Frame: " + str(frame))
|
try:
|
||||||
|
while (frame[0] != 0xD5):
|
||||||
|
frame = frame[1:]
|
||||||
|
frame = frame[1:]
|
||||||
|
except:
|
||||||
|
self.dut._log.debug("NLP / Invalid frame")
|
||||||
|
continue
|
||||||
|
self.dut._log.debug("DUT TX: " + str(frame))
|
||||||
self.macdev.send(frame)
|
self.macdev.send(frame)
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
echo Hardware in the loop test
|
echo Hardware in the loop test
|
||||||
|
|
||||||
make TOPLEVEL=cocotb_top_mac_test MODULE=hw_itl
|
make TOPLEVEL=cocotb_top_hwitl MODULE=hw_itl
|
||||||
|
283
design/top_hwitl.vhd
Normal file
283
design/top_hwitl.vhd
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- top_hwitl.vhd: Top level design for hardware-in-the-loop tests
|
||||||
|
--
|
||||||
|
-- Target: Simulation
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- 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;
|
||||||
|
|
||||||
|
library trashernet;
|
||||||
|
use trashernet.trashernet_pkg.all;
|
||||||
|
|
||||||
|
entity top_hwitl is
|
||||||
|
port(
|
||||||
|
clk : in std_logic;
|
||||||
|
rst_n : in std_logic;
|
||||||
|
rx_p : in std_logic;
|
||||||
|
tx_p : out std_logic;
|
||||||
|
tx_n : out std_logic;
|
||||||
|
led_n : out std_logic_vector(7 downto 0);
|
||||||
|
button_n : in std_logic_vector(3 downto 0);
|
||||||
|
debug_data : out std_logic_vector(7 downto 0)
|
||||||
|
);
|
||||||
|
end entity top_hwitl;
|
||||||
|
|
||||||
|
architecture mac of top_hwitl is
|
||||||
|
component pll0
|
||||||
|
port(
|
||||||
|
CLK : in std_logic;
|
||||||
|
CLKOP : out std_logic;
|
||||||
|
LOCK : out std_logic
|
||||||
|
);
|
||||||
|
end component pll0;
|
||||||
|
|
||||||
|
constant F_CLK : integer := 50000000;
|
||||||
|
constant F_CLK_PHY : integer := 140000000;
|
||||||
|
|
||||||
|
constant ETH_CONFIG : configuration_t := (
|
||||||
|
mac_address => (x"00", x"ff", x"ff", x"11", x"22", x"33")
|
||||||
|
);
|
||||||
|
|
||||||
|
signal rst : std_logic;
|
||||||
|
signal clk_phy : std_logic;
|
||||||
|
signal phy_pll_lock : std_logic;
|
||||||
|
|
||||||
|
signal phy_out : phy_out_t;
|
||||||
|
signal phy_in : phy_in_t;
|
||||||
|
|
||||||
|
signal mac_out : mac_out_t;
|
||||||
|
signal mac_in : mac_in_t;
|
||||||
|
|
||||||
|
type status_t is (IDLE, TX);
|
||||||
|
signal state : status_t;
|
||||||
|
constant BYTE_CNT_MAX : integer := 100;
|
||||||
|
signal byte_cnt : integer range 0 to BYTE_CNT_MAX;
|
||||||
|
|
||||||
|
constant TMO_MAX : integer := integer(round((real(F_CLK) * 0.25))) - 1;
|
||||||
|
signal tmo : integer range 0 to TMO_MAX;
|
||||||
|
begin
|
||||||
|
trashernet_mac_inst : entity trashernet.trashernet_mac
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
rst => rst,
|
||||||
|
phy_out => phy_out,
|
||||||
|
phy_in => phy_in,
|
||||||
|
mac_out => mac_out,
|
||||||
|
mac_in => mac_in
|
||||||
|
);
|
||||||
|
|
||||||
|
pll0_inst : pll0
|
||||||
|
port map(
|
||||||
|
CLK => clk,
|
||||||
|
CLKOP => clk_phy,
|
||||||
|
LOCK => phy_pll_lock
|
||||||
|
);
|
||||||
|
|
||||||
|
trashernet_phy_cdc_inst : entity trashernet.trashernet_phy_cdc
|
||||||
|
generic map(
|
||||||
|
F_CLK => F_CLK,
|
||||||
|
F_CLK_PHY => F_CLK_PHY
|
||||||
|
)
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
phy_clk => clk_phy,
|
||||||
|
rst => rst,
|
||||||
|
phy_out => phy_out,
|
||||||
|
phy_in => phy_in,
|
||||||
|
rx_p => rx_p,
|
||||||
|
tx_p => tx_p,
|
||||||
|
tx_n => tx_n
|
||||||
|
);
|
||||||
|
|
||||||
|
reset_sync : process(clk, rst_n) is
|
||||||
|
begin
|
||||||
|
if (rst_n = '0') then
|
||||||
|
rst <= '1';
|
||||||
|
elsif (rising_edge(clk)) then
|
||||||
|
rst <= '0';
|
||||||
|
end if;
|
||||||
|
end process reset_sync;
|
||||||
|
|
||||||
|
receiver : process(clk, rst) is
|
||||||
|
begin
|
||||||
|
if rst then
|
||||||
|
state <= IDLE;
|
||||||
|
mac_in.tx_mac_data_en <= '0';
|
||||||
|
tmo <= 0;
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
if (tmo /= 0) then
|
||||||
|
tmo <= tmo - 1;
|
||||||
|
end if;
|
||||||
|
case state is
|
||||||
|
when IDLE =>
|
||||||
|
if mac_out.rx_mac_crc_ok then
|
||||||
|
if (mac_out.rx_header.mac_destination = ETH_CONFIG.mac_address) then -- Note: We won't respond to broadcasts!
|
||||||
|
state <= TX;
|
||||||
|
byte_cnt <= BYTE_CNT_MAX;
|
||||||
|
mac_in.tx_header.mac_destination <= mac_out.rx_header.mac_source;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if tmo = 0 then
|
||||||
|
state <= TX;
|
||||||
|
byte_cnt <= BYTE_CNT_MAX;
|
||||||
|
mac_in.tx_header.mac_destination <= (others => x"FF");
|
||||||
|
end if;
|
||||||
|
when TX =>
|
||||||
|
tmo <= TMO_MAX;
|
||||||
|
mac_in.tx_header.mac_ethertype <= (x"00", std_logic_vector(to_unsigned(BYTE_CNT_MAX, 8)));
|
||||||
|
mac_in.tx_mac_data_en <= '1';
|
||||||
|
mac_in.tx_mac_data <= std_logic_vector(to_unsigned(byte_cnt, 8));
|
||||||
|
if mac_out.tx_mac_data_ack then
|
||||||
|
if byte_cnt = 1 then
|
||||||
|
mac_in.tx_mac_data_en <= '0';
|
||||||
|
state <= IDLE;
|
||||||
|
else
|
||||||
|
byte_cnt <= byte_cnt - 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end case;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
end process receiver;
|
||||||
|
|
||||||
|
mac_in.tx_header.mac_source <= ETH_CONFIG.mac_address;
|
||||||
|
|
||||||
|
debug_data(0) <= tx_p;
|
||||||
|
debug_data(1) <= tx_n;
|
||||||
|
|
||||||
|
end architecture mac;
|
||||||
|
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- top_hwitl (eth)
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
use ieee.math_real.all;
|
||||||
|
|
||||||
|
library trashernet;
|
||||||
|
use trashernet.trashernet_pkg.all;
|
||||||
|
|
||||||
|
architecture eth of top_hwitl is
|
||||||
|
component pll0
|
||||||
|
port(
|
||||||
|
CLK : in std_logic;
|
||||||
|
CLKOP : out std_logic;
|
||||||
|
LOCK : out std_logic
|
||||||
|
);
|
||||||
|
end component pll0;
|
||||||
|
|
||||||
|
constant F_CLK : integer := 50000000;
|
||||||
|
constant F_CLK_PHY : integer := 140000000;
|
||||||
|
|
||||||
|
constant ETH_CONFIG : configuration_t := (
|
||||||
|
mac_address => (x"00", x"ff", x"ff", x"11", x"22", x"33")
|
||||||
|
);
|
||||||
|
|
||||||
|
signal rst : std_logic;
|
||||||
|
signal clk_phy : std_logic;
|
||||||
|
signal phy_pll_lock : std_logic;
|
||||||
|
|
||||||
|
signal phy_out : phy_out_t;
|
||||||
|
signal phy_in : phy_in_t;
|
||||||
|
|
||||||
|
signal mac_out : mac_out_t;
|
||||||
|
signal mac_in : mac_in_t;
|
||||||
|
|
||||||
|
constant PROT_ARP : integer := 0;
|
||||||
|
constant PROT_IP : integer := 1;
|
||||||
|
constant ETHERNET_II_PROTOCOLS : ethernet_ii_protocol_vector := (PROT_ARP => ETHERNET_II_PROTOCOL_ARP, PROT_IP => ETHERNET_II_PROTOCOL_IP);
|
||||||
|
signal ethernet_i_out : ethernet_i_out_t;
|
||||||
|
signal ethernet_i_in : ethernet_i_in_t;
|
||||||
|
signal ethernet_ii_out : ethernet_ii_out_vector(ETHERNET_II_PROTOCOLS'range);
|
||||||
|
signal ethernet_ii_in : ethernet_ii_in_vector(ETHERNET_II_PROTOCOLS'range);
|
||||||
|
begin
|
||||||
|
trashernet_eth_inst : entity trashernet.trashernet_eth
|
||||||
|
generic map(
|
||||||
|
ETHERNET_II_PROTOCOLS => ETHERNET_II_PROTOCOLS
|
||||||
|
)
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
rst => rst,
|
||||||
|
mac_out => mac_out,
|
||||||
|
mac_in => mac_in,
|
||||||
|
config => ETH_CONFIG,
|
||||||
|
ethernet_i_out => ethernet_i_out,
|
||||||
|
ethernet_i_in => ethernet_i_in,
|
||||||
|
ethernet_ii_out => ethernet_ii_out,
|
||||||
|
ethernet_ii_in => ethernet_ii_in
|
||||||
|
);
|
||||||
|
|
||||||
|
trashernet_mac_inst : entity trashernet.trashernet_mac
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
rst => rst,
|
||||||
|
phy_out => phy_out,
|
||||||
|
phy_in => phy_in,
|
||||||
|
mac_out => mac_out,
|
||||||
|
mac_in => mac_in
|
||||||
|
);
|
||||||
|
|
||||||
|
pll0_inst : pll0
|
||||||
|
port map(
|
||||||
|
CLK => clk,
|
||||||
|
CLKOP => clk_phy,
|
||||||
|
LOCK => phy_pll_lock
|
||||||
|
);
|
||||||
|
|
||||||
|
trashernet_phy_cdc_inst : entity trashernet.trashernet_phy_cdc
|
||||||
|
generic map(
|
||||||
|
F_CLK => F_CLK,
|
||||||
|
F_CLK_PHY => F_CLK_PHY
|
||||||
|
)
|
||||||
|
port map(
|
||||||
|
clk => clk,
|
||||||
|
phy_clk => clk_phy,
|
||||||
|
rst => rst,
|
||||||
|
phy_out => phy_out,
|
||||||
|
phy_in => phy_in,
|
||||||
|
rx_p => rx_p,
|
||||||
|
tx_p => tx_p,
|
||||||
|
tx_n => tx_n
|
||||||
|
);
|
||||||
|
|
||||||
|
reset_sync : process(clk, rst_n) is
|
||||||
|
begin
|
||||||
|
if (rst_n = '0') then
|
||||||
|
rst <= '1';
|
||||||
|
elsif (rising_edge(clk)) then
|
||||||
|
rst <= '0';
|
||||||
|
end if;
|
||||||
|
end process reset_sync;
|
||||||
|
|
||||||
|
receiver : process(clk, rst) is
|
||||||
|
begin
|
||||||
|
if rst then
|
||||||
|
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
if ethernet_i_out.rx_header_rcv then
|
||||||
|
report "RX ETH-I: " & to_string(to_integer(ethernet_i_out.rx_length));
|
||||||
|
end if;
|
||||||
|
if (ethernet_ii_out(PROT_ARP).rx_header_rcv) then
|
||||||
|
report "RX ARP";
|
||||||
|
end if;
|
||||||
|
if (ethernet_ii_out(PROT_IP).rx_header_rcv) then
|
||||||
|
report "RX IP";
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process receiver;
|
||||||
|
|
||||||
|
end architecture eth;
|
@ -52,10 +52,6 @@ architecture rtl of top_mac_test is
|
|||||||
others => false
|
others => false
|
||||||
);
|
);
|
||||||
|
|
||||||
type configuration_t is record
|
|
||||||
mac_address : mac_addr_t;
|
|
||||||
end record configuration_t;
|
|
||||||
|
|
||||||
constant ETH_CONFIG : configuration_t := (
|
constant ETH_CONFIG : configuration_t := (
|
||||||
mac_address => (x"00", x"ff", x"ff", x"11", x"22", x"33")
|
mac_address => (x"00", x"ff", x"ff", x"11", x"22", x"33")
|
||||||
);
|
);
|
||||||
|
175
trashernet/trashernet_eth.vhd
Normal file
175
trashernet/trashernet_eth.vhd
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- trashernet_eth.vhd : Ethernet OSI Layer 2, Data Link, ETH I/II Ethertype
|
||||||
|
-- Implements arbitration of different ethertypes.
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- 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.trashernet_pkg.all;
|
||||||
|
|
||||||
|
entity trashernet_eth is
|
||||||
|
generic(
|
||||||
|
ETHERNET_II_PROTOCOLS : ethernet_ii_protocol_vector := ETHERNET_II_PROTOCOLS_NONE
|
||||||
|
);
|
||||||
|
port(
|
||||||
|
-- Global
|
||||||
|
clk : in std_logic; -- Global clock
|
||||||
|
rst : in std_logic; -- Asynchronous reset
|
||||||
|
|
||||||
|
-- MAC application interface
|
||||||
|
mac_out : in mac_out_t; -- MAC application IF (out of MAC)
|
||||||
|
mac_in : out mac_in_t; -- MAC application IF (into MAC)
|
||||||
|
|
||||||
|
-- Configuration
|
||||||
|
config : in configuration_t; -- Global Trashernet configuration
|
||||||
|
|
||||||
|
-- Ethernet 802.3 application interface
|
||||||
|
ethernet_i_out : out ethernet_i_out_t; -- Ethernet 802.3 IF (out from MAC)
|
||||||
|
ethernet_i_in : in ethernet_i_in_t := ETHERNET_I_IN_UNUSED; -- Ethernet 802.3 IF (out from MAC)
|
||||||
|
|
||||||
|
-- Ethernet II application interface
|
||||||
|
ethernet_ii_out : out ethernet_ii_out_vector(ETHERNET_II_PROTOCOLS'range); -- Ethernet II IF (out from MAC)
|
||||||
|
ethernet_ii_in : in ethernet_ii_in_vector(ETHERNET_II_PROTOCOLS'range) := (others => ETHERNET_II_IN_UNUSED) -- Ethernet II IF (into MAC)
|
||||||
|
);
|
||||||
|
end entity trashernet_eth;
|
||||||
|
|
||||||
|
architecture rtl of trashernet_eth is
|
||||||
|
signal tx_data_ack_ii : std_logic_vector(ethernet_ii_out'range);
|
||||||
|
signal tx_data_ack_i : std_logic;
|
||||||
|
begin
|
||||||
|
rx : block
|
||||||
|
constant SEL_ETH_I : integer := ethernet_ii_out'low - 1;
|
||||||
|
constant SEL_ETH_NONE : integer := ethernet_ii_out'low - 2;
|
||||||
|
signal sel : integer range SEL_ETH_NONE to ethernet_ii_out'high;
|
||||||
|
signal mac_destination_matches : std_logic;
|
||||||
|
signal rx_mac_header_rcv_delayed : std_logic;
|
||||||
|
|
||||||
|
begin
|
||||||
|
mac_destination_matches <= '1' when --
|
||||||
|
((mac_out.rx_header.mac_destination = (x"FF", x"FF", x"FF", x"FF", x"FF", x"FF")) or --
|
||||||
|
(mac_out.rx_header.mac_destination = config.mac_address)) else
|
||||||
|
'0';
|
||||||
|
|
||||||
|
mux : process(clk, rst) is
|
||||||
|
begin
|
||||||
|
if rst then
|
||||||
|
sel <= SEL_ETH_NONE;
|
||||||
|
rx_mac_header_rcv_delayed <= '0';
|
||||||
|
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
rx_mac_header_rcv_delayed <= mac_out.rx_mac_header_rcv;
|
||||||
|
|
||||||
|
if mac_out.rx_mac_header_rcv then
|
||||||
|
sel <= SEL_ETH_NONE; -- By default, let's assume it's not for us
|
||||||
|
if mac_destination_matches then
|
||||||
|
if (unsigned(std_logic_vector'(mac_out.rx_header.mac_ethertype(0) & mac_out.rx_header.mac_ethertype(1))) < 1500) then -- Ethernet 802.3 Frame
|
||||||
|
sel <= SEL_ETH_I;
|
||||||
|
|
||||||
|
else -- Ethernet II Frame
|
||||||
|
for i in ETHERNET_II_PROTOCOLS'range loop
|
||||||
|
if (mac_out.rx_header.mac_ethertype = ETHERNET_II_PROTOCOLS(i).ethertype) then
|
||||||
|
sel <= i;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process mux;
|
||||||
|
|
||||||
|
-- Shared fields in the Ethernet II application interface
|
||||||
|
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_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_crc_error <= mac_out.rx_mac_crc_error when sel = i else '0';
|
||||||
|
ethernet_ii_out(i).rx_crc_ok <= mac_out.rx_mac_crc_ok when sel = i else '0';
|
||||||
|
ethernet_ii_out(i).rx_header_rcv <= rx_mac_header_rcv_delayed when sel = i else '0';
|
||||||
|
end generate shared_fields;
|
||||||
|
|
||||||
|
-- Shared fields in the Ethernet application interface
|
||||||
|
ethernet_i_out.rx_data <= mac_out.rx_mac_data;
|
||||||
|
ethernet_i_out.rx_data_valid <= mac_out.rx_mac_valid when sel = SEL_ETH_I;
|
||||||
|
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_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_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 : for i in ethernet_ii_out'range generate
|
||||||
|
ethernet_ii_out(i).tx_data_ack <= tx_data_ack_ii(i);
|
||||||
|
end generate;
|
||||||
|
ethernet_i_out.tx_data_ack <= tx_data_ack_i;
|
||||||
|
end block rx;
|
||||||
|
|
||||||
|
tx : block
|
||||||
|
type state_t is (IDLE, TXD);
|
||||||
|
signal state : state_t;
|
||||||
|
constant SEL_ETH_I : integer := ethernet_ii_out'low - 1;
|
||||||
|
signal sel : integer range SEL_ETH_I to ethernet_ii_in'high;
|
||||||
|
begin
|
||||||
|
arb : process(clk, rst) is
|
||||||
|
begin
|
||||||
|
if rst then
|
||||||
|
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
case state is
|
||||||
|
when IDLE =>
|
||||||
|
if not mac_out.tx_active then
|
||||||
|
if (ethernet_i_in.tx_en) then -- ETH I has priority
|
||||||
|
sel <= SEL_ETH_I;
|
||||||
|
state <= TXD;
|
||||||
|
else
|
||||||
|
for i in ethernet_ii_in'range loop
|
||||||
|
if ethernet_ii_in(i).tx_en then
|
||||||
|
sel <= i;
|
||||||
|
state <= TXD;
|
||||||
|
exit; -- Prioritize according to vector
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when TXD =>
|
||||||
|
state <= IDLE when (mac_in.tx_mac_data_en or not mac_out.tx_active);
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
end process arb;
|
||||||
|
|
||||||
|
mux : process(all) is
|
||||||
|
begin
|
||||||
|
-- Defaults to avoid latch
|
||||||
|
tx_data_ack_i <= '0';
|
||||||
|
tx_data_ack_ii <= (others => '0');
|
||||||
|
|
||||||
|
-- Actual MUX
|
||||||
|
if (sel = SEL_ETH_I) then
|
||||||
|
mac_in.tx_mac_data_en <= ethernet_i_in.tx_en;
|
||||||
|
mac_in.tx_mac_data <= ethernet_i_in.tx_data;
|
||||||
|
tx_data_ack_i <= mac_out.tx_mac_data_ack;
|
||||||
|
mac_in.tx_header.mac_destination <= ethernet_i_in.tx_mac_address;
|
||||||
|
mac_in.tx_header.mac_ethertype <= byte(ethernet_i_in.tx_length(15 downto 8)) & byte(ethernet_i_in.tx_length(7 downto 0));
|
||||||
|
else
|
||||||
|
mac_in.tx_mac_data_en <= ethernet_ii_in(sel).tx_en;
|
||||||
|
mac_in.tx_mac_data <= ethernet_ii_in(sel).tx_data;
|
||||||
|
txack : for i in ethernet_ii_out'range loop
|
||||||
|
tx_data_ack_ii(i) <= mac_out.tx_mac_data_ack when sel = i else '0';
|
||||||
|
end loop txack;
|
||||||
|
|
||||||
|
mac_in.tx_header.mac_destination <= ethernet_ii_in(sel).tx_mac_address;
|
||||||
|
mac_in.tx_header.mac_ethertype <= ETHERNET_II_PROTOCOLS(sel).ethertype;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
mac_in.tx_header.mac_source <= config.mac_address;
|
||||||
|
end process mux;
|
||||||
|
end block tx;
|
||||||
|
end architecture rtl;
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
library ieee;
|
library ieee;
|
||||||
use ieee.std_logic_1164.all;
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
package trashernet_pkg is
|
package trashernet_pkg is
|
||||||
-- General types
|
-- General types
|
||||||
@ -29,6 +30,11 @@ package trashernet_pkg is
|
|||||||
-- 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);
|
||||||
|
|
||||||
|
-- Configuration interface
|
||||||
|
type configuration_t is record
|
||||||
|
mac_address : mac_addr_t; -- MAC address of this node
|
||||||
|
end record configuration_t;
|
||||||
|
|
||||||
-- 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
|
||||||
@ -65,6 +71,51 @@ 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
|
||||||
|
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"))); -- NULL range
|
||||||
|
constant ETHERNET_II_PROTOCOL_IP : 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
|
||||||
|
rx_mac_address : mac_addr_t; -- Source MAC address
|
||||||
|
rx_crc_ok : std_logic; -- End of packet, CRC OK
|
||||||
|
rx_crc_error : std_logic; -- End of packet, CRC invalid
|
||||||
|
|
||||||
|
rx_data : byte; -- RX data
|
||||||
|
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`
|
||||||
|
end record;
|
||||||
|
type ethernet_i_in_t is record
|
||||||
|
tx_mac_address : mac_addr_t; -- Destination MAC address
|
||||||
|
tx_data : byte; -- TX data
|
||||||
|
tx_en : std_logic; -- Start and continue transmitting
|
||||||
|
tx_length : unsigned(15 downto 0); -- TX payload length in bytes
|
||||||
|
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
|
||||||
|
|
||||||
|
type ethernet_ii_out_t is record
|
||||||
|
rx_mac_address : mac_addr_t; -- Source MAC address
|
||||||
|
rx_data : byte; -- RX data
|
||||||
|
rx_data_valid : std_logic; -- RX data valid strobe
|
||||||
|
rx_crc_ok : std_logic; -- End of packet, CRC OK
|
||||||
|
rx_crc_error : std_logic; -- End of packet, CRC invalid
|
||||||
|
rx_header_rcv : std_logic; -- Start of reception
|
||||||
|
tx_data_ack : std_logic; -- Give next data byte or disable `tx_en`
|
||||||
|
end record;
|
||||||
|
type ethernet_ii_in_t is record
|
||||||
|
tx_mac_address : mac_addr_t; -- Destination MAC address
|
||||||
|
tx_data : byte; -- TX data
|
||||||
|
tx_en : std_logic; -- Start and continue transmitting
|
||||||
|
end record;
|
||||||
|
type ethernet_ii_out_vector is array (natural range <>) of ethernet_ii_out_t;
|
||||||
|
type ethernet_ii_in_vector is array (natural range <>) of ethernet_ii_in_t;
|
||||||
|
constant ETHERNET_II_IN_UNUSED : ethernet_ii_in_t := (tx_mac_address => (others => x"00"), tx_data => x"00", others => '0');
|
||||||
end package trashernet_pkg;
|
end package trashernet_pkg;
|
||||||
|
|
||||||
package body trashernet_pkg is
|
package body trashernet_pkg is
|
||||||
|
Loading…
Reference in New Issue
Block a user