From f45ded2ca98b0cbf42b59b342b3638af355078ef Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Mon, 18 Apr 2022 17:06:53 +0200 Subject: [PATCH] bench: Add cocotb-based test bench for MAC test --- .gitignore | 10 +++ .gitmodules | 3 + bench/pll0.vhd | 42 +++++++++++ cocotb/Makefile | 17 +++++ cocotb/cocotb_helpers | 1 + cocotb/cocotb_top_mac_test.py | 48 +++++++++++++ cocotb/cocotb_top_mac_test.vhd | 77 +++++++++++++++++++++ cocotb/cocovc_eth.vhd | 123 +++++++++++++++++++++++++++++++++ 8 files changed, 321 insertions(+) create mode 100644 .gitmodules create mode 100644 bench/pll0.vhd create mode 100644 cocotb/Makefile create mode 160000 cocotb/cocotb_helpers create mode 100644 cocotb/cocotb_top_mac_test.py create mode 100644 cocotb/cocotb_top_mac_test.vhd create mode 100644 cocotb/cocovc_eth.vhd diff --git a/.gitignore b/.gitignore index 3f7ae5b..5c3482a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,11 @@ +# VUnit vunit_out + +# Cocotb +__pycache__ +*.o +cocotb/results.xml +cocotb/sim_build +cocotb/wave.ghw + +cocotb/cocotb_top_mac_test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f896305 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cocotb/cocotb_helpers"] + path = cocotb/cocotb_helpers + url = https://git.notsyncing.net/fpga/cocotb_helpers.git diff --git a/bench/pll0.vhd b/bench/pll0.vhd new file mode 100644 index 0000000..5388992 --- /dev/null +++ b/bench/pll0.vhd @@ -0,0 +1,42 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- pll0.vhd : Simulates the PLL component present in some demo designs +-- -------------------------------------------------------------------------- -- +-- 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; + +entity pll0 is + port( + CLK : in std_logic; + CLKOP : out std_logic; + LOCK : out std_logic + ); +end entity pll0; + +architecture bench of pll0 is +begin + clock_driver : process + constant period : time := 7.1429 ns; -- 140 MHz configured clock + begin + CLKOP <= '0'; + LOCK <= '0'; + + wait until CLK'event; + wait for 12 ns; + LOCK <= '1'; + + loop + CLKOP <= '0'; + wait for period / 2; + CLKOP <= '1'; + wait for period / 2; + end loop; + end process clock_driver; +end architecture bench; diff --git a/cocotb/Makefile b/cocotb/Makefile new file mode 100644 index 0000000..8c88e68 --- /dev/null +++ b/cocotb/Makefile @@ -0,0 +1,17 @@ +# defaults +SIM ?= ghdl +TOPLEVEL_LANG ?= vhdl +BASE = $(PWD)/.. + +COMPILE_ARGS=--std=08 +SIM_ARGS ?= --wave=wave.ghw + +VHDL_SOURCES_trashernet += $(BASE)/trashernet/*.vhd +VHDL_SOURCES_design += $(BASE)/design/*.vhd +VHDL_SOURCES_design += $(BASE)/bench/pll0.vhd +VHDL_SOURCES += $(BASE)/cocotb/*.vhd + +TOPLEVEL = cocotb_top_mac_test +MODULE = cocotb_top_mac_test + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/cocotb/cocotb_helpers b/cocotb/cocotb_helpers new file mode 160000 index 0000000..e2c72ef --- /dev/null +++ b/cocotb/cocotb_helpers @@ -0,0 +1 @@ +Subproject commit e2c72ef61d7742fbcb7e048bc928446ed4b56a82 diff --git a/cocotb/cocotb_top_mac_test.py b/cocotb/cocotb_top_mac_test.py new file mode 100644 index 0000000..ebb87be --- /dev/null +++ b/cocotb/cocotb_top_mac_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +import cocotb +from cocotb.triggers import * +from cocotb.result import * + +from cocotb.queue import Queue + +from cocotb_helpers import buffers + +ETH_HEAD = b'\x55\x55\x55\x55\xD5' + +async def timeout(dut): + await Timer(1, units="ms") + assert False, "Timeout" + +async def rxprinter(dut, rx): + while True: + frame = await rx.queue.get() + dut._log.info("RX Frame: " + str(frame)) + +@cocotb.test() +async def my_test(dut): + """TBD description.""" + + # Start verification components + eth_tx = buffers.tx_buffer(dut, "cocovc_eth_inst.cocotb_tx_") + await eth_tx.start() + eth_rx = buffers.rx_buffer(dut, "cocovc_eth_inst.cocotb_rx_") + await eth_rx.start() + + # Start local monitors + await cocotb.start(rxprinter(dut, eth_rx)) + + # Start timeout + await cocotb.start(timeout(dut)) + + # Wait for VHDL part to be ready + await Edge(dut.bench_ready) + + # Start test procedure + await Timer(400, units="us") # The device will send a frame at startup + + dut._log.info("Send first frame"); # This will trigger a response frame + await eth_tx.send(ETH_HEAD + b'\x00\xff\xff\x11"3\x00\xff\xff\xaa\xbb\xcc\x00\n\n\t\x08\x07\x06\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x10\x90\xe0'); + dut._log.info("Send second frame"); + await eth_tx.send(ETH_HEAD + b'World'); + await Timer(400, units="us") # Wait for response diff --git a/cocotb/cocotb_top_mac_test.vhd b/cocotb/cocotb_top_mac_test.vhd new file mode 100644 index 0000000..bac1467 --- /dev/null +++ b/cocotb/cocotb_top_mac_test.vhd @@ -0,0 +1,77 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- cocotb_top_mac_test.vhd : Test bench for the MAC test demo. +-- -------------------------------------------------------------------------- -- +-- 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_mac_test is +end entity cocotb_top_mac_test; + +architecture bench of cocotb_top_mac_test 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 design.top_mac_test + 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/cocovc_eth.vhd b/cocotb/cocovc_eth.vhd new file mode 100644 index 0000000..bf57b51 --- /dev/null +++ b/cocotb/cocovc_eth.vhd @@ -0,0 +1,123 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- cocovc_eth.vhd : Cocotb-based verification component for 10M Ethernet. +-- Note: This is cheapskated to work with Trashernet. It may not work with +-- every PHY. +-- -------------------------------------------------------------------------- -- +-- 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; + +entity cocovc_eth is + port( + rx_p : in std_logic; + rx_n : in std_logic; + tx_p : out std_logic; + tx_n : out std_logic + ); +end entity cocovc_eth; + +architecture bench of cocovc_eth is + -- Ethernet TX (TB/Python to DUT/VHDL) + signal cocotb_tx_d : std_logic_vector(7 downto 0) := x"00"; -- Data to be transferred to VHDL + signal cocotb_tx_dv : std_logic := '0'; -- '1' when `d` holds valid data that is to be processed by VHDL + signal cocotb_tx_da : std_logic := '0'; -- Toggles when VHDL has latched `d`, Python must advance data or deassert `dv` + + -- Ethernet RX (DUT/VHDL to TB/Python) + signal cocotb_rx_d : std_logic_vector(7 downto 0); -- Data to be transferred to Python + signal cocotb_rx_dt : std_logic := '0'; -- Toggles to announce new data (Python has to latch immediately) + signal cocotb_rx_a : std_logic := '0'; -- Block active indicator (rising on block start, falling on block end) +begin + -- Ethernet TX + eth_tx_proc : process is + variable d : std_logic_vector(cocotb_rx_d'range); + begin + tx_p <= '0'; + wait until cocotb_tx_dv = '1'; + + loop + exit when not cocotb_tx_dv; + + d := cocotb_tx_d; + cocotb_tx_da <= not cocotb_tx_da; + + for j in d'low to d'high loop + tx_p <= not d(j); + wait for 50 ns; + tx_p <= d(j); + wait for 50 ns; + end loop; + end loop; + + wait for 16 us; -- Wait IPG + cocotb_tx_da <= not cocotb_tx_da; -- Signal that end-of-block was latched + + end process eth_tx_proc; + tx_n <= not tx_p; + + -- Ethernet RX + eth_rx_proc : process is + constant SYM_TIME : time := 50 ns; + variable d : std_logic_vector(cocotb_rx_d'range) := x"00"; + variable cnt : integer range d'low to d'high; + variable bit : std_logic; + variable ignore : boolean; + variable first : boolean; -- Just a cheap workaround because the weather is too nice to implement it properly this afternoon + procedure latch(value : std_logic) is + begin + d(cnt) := value; + if (cnt = 7) then + cocotb_rx_d <= d; + cocotb_rx_dt <= not cocotb_rx_dt; + cnt := 0; + else + cnt := cnt + 1; + end if; + end procedure latch; + + begin + cocotb_rx_a <= '0'; + + loop + wait on rx_p; + exit when (rx_p'last_value = '0' or rx_p'last_value = '1'); + end loop; + + cocotb_rx_a <= '1'; + wait for 0 ns; + + cnt := 0; + bit := '0'; + ignore := false; + first := true; + loop + wait on rx_p for SYM_TIME * 1.5; + exit when rx_p'last_event > 2.5 * SYM_TIME; + + if rx_p'event then -- short + if not ignore then + latch(bit); + end if; + ignore := not ignore; + else -- long + if (first) then + bit := '1'; + cnt := 1; + d(0) := '1'; + end if; + bit := not bit; + latch(bit); + ignore := true; + end if; + + first := false; + end loop; + end process eth_rx_proc; + +end architecture bench;