bench: Add cocotb based test bench
This commit is contained in:
parent
5c27b685dc
commit
64f6197dbb
4
cocotb/.gitignore
vendored
Normal file
4
cocotb/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.o
|
||||||
|
results.xml
|
||||||
|
*.cf
|
14
cocotb/Makefile
Normal file
14
cocotb/Makefile
Normal file
@ -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
|
90
cocotb/cocotb_trashernet_phy.py
Normal file
90
cocotb/cocotb_trashernet_phy.py
Normal file
@ -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()
|
104
cocotb/cocotb_trashernet_phy.vhd
Normal file
104
cocotb/cocotb_trashernet_phy.vhd
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- TRASHERNET - A Trashy Ethernet Stack for FPGAs --
|
||||||
|
-- -------------------------------------------------------------------------- --
|
||||||
|
-- bench_trashernet_phy.vhd : Cocotb test bench for the 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;
|
||||||
|
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user