diff --git a/.gitignore b/.gitignore index 5c3482a..b7284a8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ cocotb/sim_build cocotb/wave.ghw cocotb/cocotb_top_mac_test +cocotb/cocotb_top_hwitl diff --git a/cocotb/cocotb_top_hwitl.vhd b/cocotb/cocotb_top_hwitl.vhd new file mode 100644 index 0000000..e0b0e95 --- /dev/null +++ b/cocotb/cocotb_top_hwitl.vhd @@ -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 +-- 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 (mac) -- 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; diff --git a/cocotb/hw_itl.sh b/cocotb/hw_itl.sh index 9176b22..356d6dc 100755 --- a/cocotb/hw_itl.sh +++ b/cocotb/hw_itl.sh @@ -2,4 +2,4 @@ echo Hardware in the loop test -make TOPLEVEL=cocotb_top_mac_test MODULE=hw_itl +make TOPLEVEL=cocotb_top_hwitl MODULE=hw_itl diff --git a/design/top_hwitl.vhd b/design/top_hwitl.vhd new file mode 100644 index 0000000..47754c8 --- /dev/null +++ b/design/top_hwitl.vhd @@ -0,0 +1,205 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- top_hwitl.vhd: Top level design for hardware-in-the-loop tests +-- +-- Target: Simulation +-- -------------------------------------------------------------------------- -- +-- 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 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 LED_BLINK : boolean_vector(led_n'range) := ( + 6 downto 2 => true, + others => false + ); + + 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 led_sig : std_logic_vector(led_n'range); + + 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; + + signal button_n_sync : std_logic_vector(button_n'range); + signal button : std_logic_vector(button_n'range); + + 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; + + ledcon_inst : entity work.ledcon + generic map( + F_CLK => F_CLK, + BLINK => LED_BLINK + ) + port map( + clk => clk, + rst => rst, + sig => led_sig, + led_n => led_n + ); + + led_sig <= ( + 0 => phy_pll_lock, + 1 => phy_out.carrier_detect, + 2 => phy_out.rx_active, + 3 => phy_out.rx_error, + 4 => mac_out.rx_mac_crc_ok, + 5 => mac_out.rx_mac_crc_error, + 6 => mac_out.tx_active, + 7 => '0' + ); + + synchronizer_inst : entity trashernet.synchronizer + generic map( + SIZE => 2 + ) + port map( + clk => clk, + rst => rst, + data_in => button_n(0), + data_out => button_n_sync(0) + ); + + button <= not button_n_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) or (button(0) = '1') 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;