diff --git a/cocotb/.gitignore b/cocotb/.gitignore new file mode 100644 index 0000000..9f9d0ed --- /dev/null +++ b/cocotb/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.o +results.xml +*.cf diff --git a/cocotb/Makefile b/cocotb/Makefile new file mode 100644 index 0000000..3fa7843 --- /dev/null +++ b/cocotb/Makefile @@ -0,0 +1,14 @@ +# defaults +SIM ?= ghdl +TOPLEVEL_LANG ?= vhdl +BASE = $(PWD)/.. + +COMPILE_ARGS=--std=08 + +VHDL_SOURCES_trashernet += $(BASE)/trashernet/*.vhd +VHDL_SOURCES_bench += $(BASE)/cocotb/*.vhd + +TOPLEVEL = bench.cocotb_trashernet_phy +MODULE = cocotb_trashernet_phy + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/cocotb/cocotb_trashernet_phy.py b/cocotb/cocotb_trashernet_phy.py new file mode 100644 index 0000000..4c41d4f --- /dev/null +++ b/cocotb/cocotb_trashernet_phy.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import cocotb +from cocotb.triggers import * +from cocotb.result import * + +from cocotb.queue import Queue + +# DUT interface (internal) +tb_rx_queue = Queue() +tb_rx_queue_done_ev = Event() + +async def dut_receiver(dut): + scoreboard = tb_rx_queue + while True: + await Edge(dut.coco_dut_rxs) + byte = dut.coco_dut_rxd.value + dut._log.info("[DT:RX <-] Byte %02X", byte) + assert not scoreboard.empty(), "Data was provided on the RX interface even though none was expected" + if not scoreboard.empty(): + sb_byte = await scoreboard.get() + assert byte == sb_byte, f"Received {byte} instead of {sb_byte}" + +async def dut_rxa(dut): + scoreboard = tb_rx_queue + await Edge(dut.coco_dut_rxa) # TODO: Workaround for some stupid bug somewhere + while True: + await Edge(dut.coco_dut_rxa) + if dut.coco_dut_rxa.value: + dut._log.info("[DT:RX <-] Start of frame") + else: + dut._log.info("[DT:RX <-] End of frame") + if scoreboard.empty(): + tb_rx_queue_done_ev.set() + +# TB / ETH interface (external) +tb_tx_queue = Queue() + +async def tb_transmitter(dut): + queue = tb_tx_queue + while True: + byte = await queue.get() + if byte == -1: + dut._log.info("[TB:TX ->] Wait IPG") + await Timer(16, units="us") # IPG + else: # TX byte + dut._log.info("[TB:TX ->] Byte %02X", byte) + dut.coco_tb_txd.value = byte + dut.coco_tb_txs.value = not dut.coco_tb_txs.value + await Edge(dut.coco_tb_txa); + +async def tb_send_packet(dut, data): + queue = tb_tx_queue + scoreboard = tb_rx_queue + + queue.put_nowait(0x55) + queue.put_nowait(0x55) + queue.put_nowait(0x55) + queue.put_nowait(0xD5) + + for byte in data: + scoreboard.put_nowait(byte) + queue.put_nowait(byte) + + queue.put_nowait(-1) + +async def timeout(dut): + await Timer(50, units="us") + assert False, "Timeout" + +@cocotb.test() +async def simple_rx(dut): + """Receive a packet using trashernet.""" + + await cocotb.start(timeout(dut)) + + await cocotb.start(dut_receiver(dut)) + await cocotb.start(dut_rxa(dut)) + await cocotb.start(tb_transmitter(dut)) + + dut.rst.value = 1 + await Timer(20, units="ns") + dut.rst.value = 0 + await Timer(20, units="ns") + + await tb_send_packet(dut, b'\x02\x03\x05\x07') + + await cocotb.start(timeout(dut)) + + await tb_rx_queue_done_ev.wait() diff --git a/cocotb/cocotb_trashernet_phy.vhd b/cocotb/cocotb_trashernet_phy.vhd new file mode 100644 index 0000000..d615e5f --- /dev/null +++ b/cocotb/cocotb_trashernet_phy.vhd @@ -0,0 +1,104 @@ +-- -------------------------------------------------------------------------- -- +-- TRASHERNET - A Trashy Ethernet Stack for FPGAs -- +-- -------------------------------------------------------------------------- -- +-- bench_trashernet_phy.vhd : Cocotb test bench for the 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; + +library trashernet; +use trashernet.trashernet_pkg.all; + +entity cocotb_trashernet_phy is +end entity cocotb_trashernet_phy; + +architecture bench of cocotb_trashernet_phy is + signal clk : std_logic; + signal phy_clk : std_logic; + signal rst : std_logic; + signal phy_out : phy_out_t; + signal phy_in : phy_in_t; + signal rx_p : std_logic; + signal tx_p : std_logic; + signal tx_n : std_logic; + + -- Cocotb interface + signal coco_dut_rxd : std_logic_vector(7 downto 0); -- RX data + signal coco_dut_rxs : std_logic := '0'; -- RX toggle + signal coco_dut_rxa : std_logic; -- RX active + + signal coco_tb_txd : std_logic_vector(7 downto 0); -- TX data + signal coco_tb_txs : std_logic := '0'; -- TX toggle + signal coco_tb_txa : std_logic := '0'; -- TX done toggle + +begin + -- Instantiate design + trashernet_phy_cdc_inst : entity trashernet.trashernet_phy_cdc + generic map( + F_CLK => 49000000, + F_CLK_PHY => 100000000 + ) + port map( + clk => clk, + phy_clk => phy_clk, + rst => rst, + phy_out => phy_out, + phy_in => phy_in, + rx_p => rx_p, + tx_p => tx_p, + tx_n => tx_n + ); + + -- Create infrastructure + 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; + + phy_clock_driver : process + constant period : time := 10 ns; + begin + phy_clk <= '0'; + wait for period / 2; + phy_clk <= '1'; + wait for period / 2; + end process phy_clock_driver; + + -- Cocotb adapter (ETH) + test : process is + begin + rx_p <= '0'; + + loop + wait on coco_tb_txs; + + for j in coco_tb_txd'low to coco_tb_txd'high loop + rx_p <= not coco_tb_txd(j); + wait for 50 ns; + rx_p <= coco_tb_txd(j); + wait for 50 ns; + end loop; + coco_tb_txa <= not coco_tb_txa; + end loop; + end process test; + + receiver : process is + begin + wait until rising_edge(clk); + if phy_out.rx_data_valid then + coco_dut_rxd <= phy_out.rx_data; + coco_dut_rxs <= not coco_dut_rxs; + end if; + end process receiver; + coco_dut_rxa <= phy_out.rx_active; +end architecture bench;