From 29dc7498d3bacfabdaf8144d7e67706566170380 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Mon, 28 Dec 2020 19:26:46 -0800 Subject: [PATCH] Add cocotb MAC testbenches --- tb/axis_gmii_rx/Makefile | 83 +++++ tb/axis_gmii_rx/test_axis_gmii_rx.py | 182 ++++++++++ tb/axis_gmii_tx/Makefile | 95 ++++++ tb/axis_gmii_tx/test_axis_gmii_tx.py | 187 +++++++++++ tb/axis_xgmii_rx_32/Makefile | 89 +++++ tb/axis_xgmii_rx_32/test_axis_xgmii_rx_32.py | 154 +++++++++ tb/axis_xgmii_rx_64/Makefile | 89 +++++ tb/axis_xgmii_rx_64/test_axis_xgmii_rx_64.py | 154 +++++++++ tb/axis_xgmii_tx_32/Makefile | 104 ++++++ tb/axis_xgmii_tx_32/test_axis_xgmii_tx_32.py | 235 +++++++++++++ tb/axis_xgmii_tx_64/Makefile | 104 ++++++ tb/axis_xgmii_tx_64/test_axis_xgmii_tx_64.py | 235 +++++++++++++ tb/eth_mac_10g/Makefile | 124 +++++++ tb/eth_mac_10g/test_eth_mac_10g.py | 287 ++++++++++++++++ tb/eth_mac_10g_fifo/Makefile | 168 ++++++++++ tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py | 310 ++++++++++++++++++ tb/eth_mac_1g/Makefile | 107 ++++++ tb/eth_mac_1g/test_eth_mac_1g.py | 261 +++++++++++++++ tb/eth_mac_1g_fifo/Makefile | 115 +++++++ tb/eth_mac_1g_fifo/test_eth_mac_1g_fifo.py | 269 +++++++++++++++ tb/eth_mac_1g_gmii/Makefile | 83 +++++ tb/eth_mac_1g_gmii/test_eth_mac_1g_gmii.py | 218 ++++++++++++ tb/eth_mac_1g_gmii_fifo/Makefile | 120 +++++++ .../test_eth_mac_1g_gmii_fifo.py | 237 +++++++++++++ tb/eth_mac_1g_rgmii/Makefile | 83 +++++ tb/eth_mac_1g_rgmii/test_eth_mac_1g_rgmii.py | 225 +++++++++++++ tb/eth_mac_1g_rgmii_fifo/Makefile | 120 +++++++ .../test_eth_mac_1g_rgmii_fifo.py | 246 ++++++++++++++ tb/eth_mac_mii/Makefile | 81 +++++ tb/eth_mac_mii/test_eth_mac_mii.py | 186 +++++++++++ tb/eth_mac_mii_fifo/Makefile | 118 +++++++ tb/eth_mac_mii_fifo/test_eth_mac_mii_fifo.py | 204 ++++++++++++ 32 files changed, 5273 insertions(+) create mode 100644 tb/axis_gmii_rx/Makefile create mode 100644 tb/axis_gmii_rx/test_axis_gmii_rx.py create mode 100644 tb/axis_gmii_tx/Makefile create mode 100644 tb/axis_gmii_tx/test_axis_gmii_tx.py create mode 100644 tb/axis_xgmii_rx_32/Makefile create mode 100644 tb/axis_xgmii_rx_32/test_axis_xgmii_rx_32.py create mode 100644 tb/axis_xgmii_rx_64/Makefile create mode 100644 tb/axis_xgmii_rx_64/test_axis_xgmii_rx_64.py create mode 100644 tb/axis_xgmii_tx_32/Makefile create mode 100644 tb/axis_xgmii_tx_32/test_axis_xgmii_tx_32.py create mode 100644 tb/axis_xgmii_tx_64/Makefile create mode 100644 tb/axis_xgmii_tx_64/test_axis_xgmii_tx_64.py create mode 100644 tb/eth_mac_10g/Makefile create mode 100644 tb/eth_mac_10g/test_eth_mac_10g.py create mode 100644 tb/eth_mac_10g_fifo/Makefile create mode 100644 tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py create mode 100644 tb/eth_mac_1g/Makefile create mode 100644 tb/eth_mac_1g/test_eth_mac_1g.py create mode 100644 tb/eth_mac_1g_fifo/Makefile create mode 100644 tb/eth_mac_1g_fifo/test_eth_mac_1g_fifo.py create mode 100644 tb/eth_mac_1g_gmii/Makefile create mode 100644 tb/eth_mac_1g_gmii/test_eth_mac_1g_gmii.py create mode 100644 tb/eth_mac_1g_gmii_fifo/Makefile create mode 100644 tb/eth_mac_1g_gmii_fifo/test_eth_mac_1g_gmii_fifo.py create mode 100644 tb/eth_mac_1g_rgmii/Makefile create mode 100644 tb/eth_mac_1g_rgmii/test_eth_mac_1g_rgmii.py create mode 100644 tb/eth_mac_1g_rgmii_fifo/Makefile create mode 100644 tb/eth_mac_1g_rgmii_fifo/test_eth_mac_1g_rgmii_fifo.py create mode 100644 tb/eth_mac_mii/Makefile create mode 100644 tb/eth_mac_mii/test_eth_mac_mii.py create mode 100644 tb/eth_mac_mii_fifo/Makefile create mode 100644 tb/eth_mac_mii_fifo/test_eth_mac_mii_fifo.py diff --git a/tb/axis_gmii_rx/Makefile b/tb/axis_gmii_rx/Makefile new file mode 100644 index 000000000..5092d2525 --- /dev/null +++ b/tb/axis_gmii_rx/Makefile @@ -0,0 +1,83 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_gmii_rx +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 8 +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_gmii_rx/test_axis_gmii_rx.py b/tb/axis_gmii_rx/test_axis_gmii_rx.py new file mode 100644 index 000000000..10d5ee2ef --- /dev/null +++ b/tb/axis_gmii_rx/test_axis_gmii_rx.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, GmiiSource +from cocotbext.axi import AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self._enable_generator = None + self._enable_cr = None + + cocotb.fork(Clock(dut.clk, 8, units="ns").start()) + + self.source = GmiiSource(dut.gmii_rxd, dut.gmii_rx_er, dut.gmii_rx_dv, + dut.clk, dut.rst, dut.clk_enable, dut.mii_select) + self.sink = AxiStreamSink(dut, "m_axis", dut.clk, dut.rst) + + dut.clk_enable.setimmediatevalue(1) + dut.mii_select.setimmediatevalue(0) + dut.ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + def set_enable_generator(self, generator=None): + if self._enable_cr is not None: + self._enable_cr.kill() + self._enable_cr = None + + self._enable_generator = generator + + if self._enable_generator is not None: + self._enable_cr = cocotb.fork(self._run_enable()) + + def clear_enable_generator(self): + self.set_enable_generator(None) + + async def _run_enable(self): + for val in self._enable_generator: + self.dut.clk_enable <= val + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.source.ifg = ifg + tb.dut.mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12, 0]) + factory.add_option("enable_gen", [None, cycle_en]) + factory.add_option("mii_sel", [False, True]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_axis_gmii_rx(request): + dut = "axis_gmii_rx" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 8 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['USER_WIDTH'] = (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis_gmii_tx/Makefile b/tb/axis_gmii_tx/Makefile new file mode 100644 index 000000000..1e0915cb1 --- /dev/null +++ b/tb/axis_gmii_tx/Makefile @@ -0,0 +1,95 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_gmii_tx +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 8 +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +export PARAM_PTP_TAG_ENABLE ?= PTP_TS_ENABLE +export PARAM_PTP_TAG_WIDTH ?= 16 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GPTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -GPTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_gmii_tx/test_axis_gmii_tx.py b/tb/axis_gmii_tx/test_axis_gmii_tx.py new file mode 100644 index 000000000..c1a64e466 --- /dev/null +++ b/tb/axis_gmii_tx/test_axis_gmii_tx.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiSink +from cocotbext.axi import AxiStreamSource + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self._enable_generator = None + self._enable_cr = None + + cocotb.fork(Clock(dut.clk, 8, units="ns").start()) + + self.source = AxiStreamSource(dut, "s_axis", dut.clk, dut.rst) + self.sink = GmiiSink(dut.gmii_txd, dut.gmii_tx_er, dut.gmii_tx_en, + dut.clk, dut.rst, dut.clk_enable, dut.mii_select) + + dut.clk_enable.setimmediatevalue(1) + dut.mii_select.setimmediatevalue(0) + dut.ptp_ts.setimmediatevalue(0) + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + def set_enable_generator(self, generator=None): + if self._enable_cr is not None: + self._enable_cr.kill() + self._enable_cr = None + + self._enable_generator = generator + + if self._enable_generator is not None: + self._enable_cr = cocotb.fork(self._run_enable()) + + def clear_enable_generator(self): + self.set_enable_generator(None) + + async def _run_enable(self): + for val in self._enable_generator: + self.dut.clk_enable <= val + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.dut.ifg_delay <= ifg + tb.dut.mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("enable_gen", [None, cycle_en]) + factory.add_option("mii_sel", [False, True]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_axis_gmii_tx(request): + dut = "axis_gmii_tx" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['PTP_TAG_ENABLE'] = parameters['PTP_TS_ENABLE'] + parameters['PTP_TAG_WIDTH'] = 16 + parameters['USER_WIDTH'] = (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis_xgmii_rx_32/Makefile b/tb/axis_xgmii_rx_32/Makefile new file mode 100644 index 000000000..ffc43934a --- /dev/null +++ b/tb/axis_xgmii_rx_32/Makefile @@ -0,0 +1,89 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_xgmii_rx_32 +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 32 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_xgmii_rx_32/test_axis_xgmii_rx_32.py b/tb/axis_xgmii_rx_32/test_axis_xgmii_rx_32.py new file mode 100644 index 000000000..d988bce14 --- /dev/null +++ b/tb/axis_xgmii_rx_32/test_axis_xgmii_rx_32.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiFrame, XgmiiSource +from cocotbext.axi import AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.clk, 3.2, units="ns").start()) + + self.source = XgmiiSource(dut.xgmii_rxd, dut.xgmii_rxc, dut.clk, dut.rst) + self.sink = AxiStreamSink(dut, "m_axis", dut.clk, dut.rst) + + dut.ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.source.ifg = ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = XgmiiFrame.from_payload(test_data) + await tb.source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12, 0]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_axis_xgmii_rx_32(request): + dut = "axis_xgmii_rx_32" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 32 + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['USER_WIDTH'] = (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis_xgmii_rx_64/Makefile b/tb/axis_xgmii_rx_64/Makefile new file mode 100644 index 000000000..d3924d4d2 --- /dev/null +++ b/tb/axis_xgmii_rx_64/Makefile @@ -0,0 +1,89 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_xgmii_rx_64 +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 64 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_xgmii_rx_64/test_axis_xgmii_rx_64.py b/tb/axis_xgmii_rx_64/test_axis_xgmii_rx_64.py new file mode 100644 index 000000000..0f6780f8d --- /dev/null +++ b/tb/axis_xgmii_rx_64/test_axis_xgmii_rx_64.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiFrame, XgmiiSource +from cocotbext.axi import AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.clk, 6.4, units="ns").start()) + + self.source = XgmiiSource(dut.xgmii_rxd, dut.xgmii_rxc, dut.clk, dut.rst) + self.sink = AxiStreamSink(dut, "m_axis", dut.clk, dut.rst) + + dut.ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.source.ifg = ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = XgmiiFrame.from_payload(test_data) + await tb.source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12, 0]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_axis_xgmii_rx_64(request): + dut = "axis_xgmii_rx_64" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 64 + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['USER_WIDTH'] = (parameters['PTP_TS_WIDTH'] if parameters['PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis_xgmii_tx_32/Makefile b/tb/axis_xgmii_tx_32/Makefile new file mode 100644 index 000000000..9fdee8590 --- /dev/null +++ b/tb/axis_xgmii_tx_32/Makefile @@ -0,0 +1,104 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_xgmii_tx_32 +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 32 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_ENABLE_DIC ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +export PARAM_PTP_TAG_ENABLE ?= $(PARAM_PTP_TS_ENABLE) +export PARAM_PTP_TAG_WIDTH ?= 16 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TAG_WIDTH'] if parameters['PTP_TAG_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_ENABLE_DIC) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GPTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -GPTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_xgmii_tx_32/test_axis_xgmii_tx_32.py b/tb/axis_xgmii_tx_32/test_axis_xgmii_tx_32.py new file mode 100644 index 000000000..dfa76d753 --- /dev/null +++ b/tb/axis_xgmii_tx_32/test_axis_xgmii_tx_32.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiSink +from cocotbext.axi import AxiStreamSource + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.clk, 3.2, units="ns").start()) + + self.source = AxiStreamSource(dut, "s_axis", dut.clk, dut.rst) + self.sink = XgmiiSink(dut.xgmii_txd, dut.xgmii_txc, dut.clk, dut.rst) + + dut.ifg_delay.setimmediatevalue(0) + dut.ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_alignment(dut, payload_data=None, ifg=12): + + enable_dic = int(os.getenv("PARAM_ENABLE_DIC")) + + tb = TB(dut) + + byte_width = tb.source.width // 8 + + tb.dut.ifg_delay <= ifg + + for length in range(60, 92): + + await tb.reset() + + test_frames = [payload_data(length) for k in range(10)] + start_lane = [] + + for test_data in test_frames: + await tb.source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.ctrl is None + + start_lane.append(rx_frame.rx_start_lane) + + tb.log.info("length: %d", length) + tb.log.info("start_lane: %s", start_lane) + + start_lane_ref = [] + + # compute expected starting lanes + lane = 0 + deficit_idle_count = 0 + + for test_data in test_frames: + if ifg == 0: + lane = 0 + + start_lane_ref.append(lane) + lane = (lane + len(test_data)+4+ifg) % byte_width + + if enable_dic: + offset = lane % 4 + if deficit_idle_count+offset >= 4: + offset += 4 + lane = (lane - offset) % byte_width + deficit_idle_count = (deficit_idle_count + offset) % 4 + else: + offset = lane % 4 + if offset > 0: + offset += 4 + lane = (lane - offset) % byte_width + + tb.log.info("start_lane_ref: %s", start_lane_ref) + + assert start_lane_ref == start_lane + + await RisingEdge(dut.clk) + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + factory = TestFactory(run_test_alignment) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +@pytest.mark.parametrize("enable_dic", [1, 0]) +def test_axis_xgmii_tx_32(request, enable_dic): + dut = "axis_xgmii_tx_32" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 32 + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['ENABLE_DIC'] = enable_dic + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['PTP_TAG_ENABLE'] = parameters['PTP_TS_ENABLE'] + parameters['PTP_TAG_WIDTH'] = 16 + parameters['USER_WIDTH'] = (parameters['PTP_TAG_WIDTH'] if parameters['PTP_TAG_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis_xgmii_tx_64/Makefile b/tb/axis_xgmii_tx_64/Makefile new file mode 100644 index 000000000..f7b39e7e4 --- /dev/null +++ b/tb/axis_xgmii_tx_64/Makefile @@ -0,0 +1,104 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = axis_xgmii_tx_64 +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 64 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_ENABLE_DIC ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_PTP_TS_ENABLE ?= 0 +export PARAM_PTP_TS_WIDTH ?= 96 +export PARAM_PTP_TAG_ENABLE ?= $(PARAM_PTP_TS_ENABLE) +export PARAM_PTP_TAG_WIDTH ?= 16 +#export PARAM_USER_WIDTH ?= (parameters['PTP_TAG_WIDTH'] if parameters['PTP_TAG_ENABLE'] else 0) + 1 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_ENABLE_DIC) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GPTP_TS_ENABLE=$(PARAM_PTP_TS_ENABLE) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GPTP_TAG_ENABLE=$(PARAM_PTP_TAG_ENABLE) + COMPILE_ARGS += -GPTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/axis_xgmii_tx_64/test_axis_xgmii_tx_64.py b/tb/axis_xgmii_tx_64/test_axis_xgmii_tx_64.py new file mode 100644 index 000000000..c65b52d72 --- /dev/null +++ b/tb/axis_xgmii_tx_64/test_axis_xgmii_tx_64.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiSink +from cocotbext.axi import AxiStreamSource + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.clk, 6.4, units="ns").start()) + + self.source = AxiStreamSource(dut, "s_axis", dut.clk, dut.rst) + self.sink = XgmiiSink(dut.xgmii_txd, dut.xgmii_txc, dut.clk, dut.rst) + + dut.ifg_delay.setimmediatevalue(0) + dut.ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_alignment(dut, payload_data=None, ifg=12): + + enable_dic = int(os.getenv("PARAM_ENABLE_DIC")) + + tb = TB(dut) + + byte_width = tb.source.width // 8 + + tb.dut.ifg_delay <= ifg + + for length in range(60, 92): + + await tb.reset() + + test_frames = [payload_data(length) for k in range(10)] + start_lane = [] + + for test_data in test_frames: + await tb.source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.ctrl is None + + start_lane.append(rx_frame.rx_start_lane) + + tb.log.info("length: %d", length) + tb.log.info("start_lane: %s", start_lane) + + start_lane_ref = [] + + # compute expected starting lanes + lane = 0 + deficit_idle_count = 0 + + for test_data in test_frames: + if ifg == 0: + lane = 0 + + start_lane_ref.append(lane) + lane = (lane + len(test_data)+4+ifg) % byte_width + + if enable_dic: + offset = lane % 4 + if deficit_idle_count+offset >= 4: + offset += 4 + lane = (lane - offset) % byte_width + deficit_idle_count = (deficit_idle_count + offset) % 4 + else: + offset = lane % 4 + if offset > 0: + offset += 4 + lane = (lane - offset) % byte_width + + tb.log.info("start_lane_ref: %s", start_lane_ref) + + assert start_lane_ref == start_lane + + await RisingEdge(dut.clk) + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + factory = TestFactory(run_test_alignment) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +@pytest.mark.parametrize("enable_dic", [1, 0]) +def test_axis_xgmii_tx_64(request, enable_dic): + dut = "axis_xgmii_tx_64" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 64 + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['ENABLE_DIC'] = enable_dic + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['PTP_TS_ENABLE'] = 0 + parameters['PTP_TS_WIDTH'] = 96 + parameters['PTP_TAG_ENABLE'] = parameters['PTP_TS_ENABLE'] + parameters['PTP_TAG_WIDTH'] = 16 + parameters['USER_WIDTH'] = (parameters['PTP_TAG_WIDTH'] if parameters['PTP_TAG_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_10g/Makefile b/tb/eth_mac_10g/Makefile new file mode 100644 index 000000000..15d943dd4 --- /dev/null +++ b/tb/eth_mac_10g/Makefile @@ -0,0 +1,124 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_10g +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/axis_xgmii_rx_32.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_tx_32.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_rx_64.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_tx_64.v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 64 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_ENABLE_DIC ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_PTP_PERIOD_NS ?= 6 +export PARAM_PTP_PERIOD_FNS ?= 26214 +export PARAM_TX_PTP_TS_ENABLE ?= 0 +export PARAM_TX_PTP_TS_WIDTH ?= 96 +export PARAM_TX_PTP_TAG_ENABLE ?= $(PARAM_TX_PTP_TS_ENABLE) +export PARAM_TX_PTP_TAG_WIDTH ?= 16 +export PARAM_RX_PTP_TS_ENABLE ?= 0 +export PARAM_RX_PTP_TS_WIDTH ?= 96 +# export PARAM_TX_USER_WIDTH ?= (parameters['TX_PTP_TAG_WIDTH'] if parameters['TX_PTP_TAG_ENABLE'] else 0) + 1 +export PARAM_TX_USER_WIDTH ?= 1 +# export PARAM_RX_USER_WIDTH ?= (parameters['RX_PTP_TS_WIDTH'] if parameters['RX_PTP_TS_ENABLE'] else 0) + 1 +export PARAM_RX_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)-$(PARAM_ENABLE_DIC) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_WIDTH=$(PARAM_TX_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TAG_WIDTH=$(PARAM_TX_PTP_TAG_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_WIDTH=$(PARAM_RX_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_USER_WIDTH=$(PARAM_TX_USER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_USER_WIDTH=$(PARAM_RX_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GPTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) + COMPILE_ARGS += -GPTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) + COMPILE_ARGS += -GTX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -GTX_PTP_TS_WIDTH=$(PARAM_TX_PTP_TS_WIDTH) + COMPILE_ARGS += -GTX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -GTX_PTP_TAG_WIDTH=$(PARAM_TX_PTP_TAG_WIDTH) + COMPILE_ARGS += -GRX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -GRX_PTP_TS_WIDTH=$(PARAM_RX_PTP_TS_WIDTH) + COMPILE_ARGS += -GTX_USER_WIDTH=$(PARAM_TX_USER_WIDTH) + COMPILE_ARGS += -GRX_USER_WIDTH=$(PARAM_RX_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_10g/test_eth_mac_10g.py b/tb/eth_mac_10g/test_eth_mac_10g.py new file mode 100644 index 000000000..32461075e --- /dev/null +++ b/tb/eth_mac_10g/test_eth_mac_10g.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import pytest +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiFrame, XgmiiSource, XgmiiSink +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + if len(dut.xgmii_txd) == 64: + cocotb.fork(Clock(dut.rx_clk, 6.4, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 6.4, units="ns").start()) + else: + cocotb.fork(Clock(dut.rx_clk, 3.2, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 3.2, units="ns").start()) + + self.xgmii_source = XgmiiSource(dut.xgmii_rxd, dut.xgmii_rxc, dut.rx_clk, dut.rx_rst) + self.xgmii_sink = XgmiiSink(dut.xgmii_txd, dut.xgmii_txc, dut.tx_clk, dut.tx_rst) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.tx_clk, dut.tx_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.rx_clk, dut.rx_rst) + + dut.rx_ptp_ts.setimmediatevalue(0) + dut.tx_ptp_ts.setimmediatevalue(0) + + async def reset(self): + self.dut.rx_rst.setimmediatevalue(0) + self.dut.tx_rst.setimmediatevalue(0) + await RisingEdge(self.dut.rx_clk) + await RisingEdge(self.dut.rx_clk) + self.dut.rx_rst <= 1 + self.dut.tx_rst <= 1 + await RisingEdge(self.dut.rx_clk) + await RisingEdge(self.dut.rx_clk) + self.dut.rx_rst <= 0 + self.dut.tx_rst <= 0 + await RisingEdge(self.dut.rx_clk) + await RisingEdge(self.dut.rx_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = XgmiiFrame.from_payload(test_data) + await tb.xgmii_source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.xgmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + + assert tb.xgmii_sink.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +async def run_test_tx_alignment(dut, payload_data=None, ifg=12): + + enable_dic = int(os.getenv("PARAM_ENABLE_DIC")) + + tb = TB(dut) + + byte_width = tb.axis_source.width // 8 + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + for length in range(60, 92): + + await tb.reset() + + test_frames = [payload_data(length) for k in range(10)] + start_lane = [] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.xgmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.ctrl is None + + start_lane.append(rx_frame.rx_start_lane) + + tb.log.info("length: %d", length) + tb.log.info("start_lane: %s", start_lane) + + start_lane_ref = [] + + # compute expected starting lanes + lane = 0 + deficit_idle_count = 0 + + for test_data in test_frames: + if ifg == 0: + lane = 0 + + start_lane_ref.append(lane) + lane = (lane + len(test_data)+4+ifg) % byte_width + + if enable_dic: + offset = lane % 4 + if deficit_idle_count+offset >= 4: + offset += 4 + lane = (lane - offset) % byte_width + deficit_idle_count = (deficit_idle_count + offset) % 4 + else: + offset = lane % 4 + if offset > 0: + offset += 4 + lane = (lane - offset) % byte_width + + tb.log.info("start_lane_ref: %s", start_lane_ref) + + assert start_lane_ref == start_lane + + await RisingEdge(dut.tx_clk) + + assert tb.xgmii_sink.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12, 0]) + factory.generate_tests() + + factory = TestFactory(run_test_tx_alignment) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +@pytest.mark.parametrize("enable_dic", [1, 0]) +@pytest.mark.parametrize("data_width", [32, 64]) +def test_eth_mac_10g(request, data_width, enable_dic): + dut = "eth_mac_10g" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "axis_xgmii_rx_32.v"), + os.path.join(rtl_dir, "axis_xgmii_rx_64.v"), + os.path.join(rtl_dir, "axis_xgmii_tx_32.v"), + os.path.join(rtl_dir, "axis_xgmii_tx_64.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = data_width + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['ENABLE_DIC'] = enable_dic + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['PTP_PERIOD_NS'] = 0x6 if parameters['DATA_WIDTH'] == 64 else 0x3 + parameters['PTP_PERIOD_FNS'] = 0x6666 if parameters['DATA_WIDTH'] == 64 else 0x3333 + parameters['TX_PTP_TS_ENABLE'] = 0 + parameters['TX_PTP_TS_WIDTH'] = 96 + parameters['TX_PTP_TAG_ENABLE'] = parameters['TX_PTP_TS_ENABLE'] + parameters['TX_PTP_TAG_WIDTH'] = 16 + parameters['RX_PTP_TS_ENABLE'] = 0 + parameters['RX_PTP_TS_WIDTH'] = 96 + parameters['TX_USER_WIDTH'] = (parameters['TX_PTP_TAG_WIDTH'] if parameters['TX_PTP_TAG_ENABLE'] else 0) + 1 + parameters['RX_USER_WIDTH'] = (parameters['RX_PTP_TS_WIDTH'] if parameters['RX_PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_10g_fifo/Makefile b/tb/eth_mac_10g_fifo/Makefile new file mode 100644 index 000000000..397549b2a --- /dev/null +++ b/tb/eth_mac_10g_fifo/Makefile @@ -0,0 +1,168 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_10g_fifo +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/eth_mac_10g.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_rx_32.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_tx_32.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_rx_64.v +VERILOG_SOURCES += ../../rtl/axis_xgmii_tx_64.v +VERILOG_SOURCES += ../../rtl/lfsr.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_adapter.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo_adapter.v + +# module parameters +export PARAM_DATA_WIDTH ?= 64 +export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_AXIS_DATA_WIDTH ?= $(PARAM_DATA_WIDTH) +export PARAM_AXIS_KEEP_ENABLE ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 ) +export PARAM_AXIS_KEEP_WIDTH ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_ENABLE_DIC ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_FIFO_DEPTH ?= 16384 +export PARAM_TX_FIFO_PIPELINE_OUTPUT ?= 2 +export PARAM_TX_FRAME_FIFO ?= 1 +export PARAM_TX_DROP_BAD_FRAME ?= $(PARAM_TX_FRAME_FIFO) +export PARAM_TX_DROP_WHEN_FULL ?= 0 +export PARAM_RX_FIFO_DEPTH ?= 16384 +export PARAM_RX_FIFO_PIPELINE_OUTPUT ?= 2 +export PARAM_RX_FRAME_FIFO ?= 1 +export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_LOGIC_PTP_PERIOD_NS ?= 6 +export PARAM_LOGIC_PTP_PERIOD_FNS ?= 26214 +export PARAM_PTP_PERIOD_NS ?= 6 +export PARAM_PTP_PERIOD_FNS ?= 26214 +export PARAM_PTP_USE_SAMPLE_CLOCK ?= 0 +export PARAM_TX_PTP_TS_ENABLE ?= 0 +export PARAM_RX_PTP_TS_ENABLE ?= 0 +export PARAM_TX_PTP_TS_FIFO_DEPTH ?= 64 +export PARAM_RX_PTP_TS_FIFO_DEPTH ?= 64 +export PARAM_PTP_TS_WIDTH ?= 96 +export PARAM_TX_PTP_TAG_ENABLE ?= 0 +export PARAM_PTP_TAG_WIDTH ?= 16 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)-$(PARAM_ENABLE_DIC) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_PIPELINE_OUTPUT=$(PARAM_TX_FIFO_PIPELINE_OUTPUT) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_PIPELINE_OUTPUT=$(PARAM_RX_FIFO_PIPELINE_OUTPUT) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).LOGIC_PTP_PERIOD_NS=$(PARAM_LOGIC_PTP_PERIOD_NS) + COMPILE_ARGS += -P $(TOPLEVEL).LOGIC_PTP_PERIOD_FNS=$(PARAM_LOGIC_PTP_PERIOD_FNS) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_USE_SAMPLE_CLOCK=$(PARAM_PTP_USE_SAMPLE_CLOCK) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_FIFO_DEPTH=$(PARAM_TX_PTP_TS_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_FIFO_DEPTH=$(PARAM_RX_PTP_TS_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).PTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH) + COMPILE_ARGS += -GAXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -GAXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -GAXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GENABLE_DIC=$(PARAM_ENABLE_DIC) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -GTX_FIFO_PIPELINE_OUTPUT=$(PARAM_TX_FIFO_PIPELINE_OUTPUT) + COMPILE_ARGS += -GTX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -GTX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -GTX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -GRX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -GRX_FIFO_PIPELINE_OUTPUT=$(PARAM_RX_FIFO_PIPELINE_OUTPUT) + COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + COMPILE_ARGS += -GLOGIC_PTP_PERIOD_NS=$(PARAM_LOGIC_PTP_PERIOD_NS) + COMPILE_ARGS += -GLOGIC_PTP_PERIOD_FNS=$(PARAM_LOGIC_PTP_PERIOD_FNS) + COMPILE_ARGS += -GPTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) + COMPILE_ARGS += -GPTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) + COMPILE_ARGS += -GPTP_USE_SAMPLE_CLOCK=$(PARAM_PTP_USE_SAMPLE_CLOCK) + COMPILE_ARGS += -GTX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -GRX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -GTX_PTP_TS_FIFO_DEPTH=$(PARAM_TX_PTP_TS_FIFO_DEPTH) + COMPILE_ARGS += -GRX_PTP_TS_FIFO_DEPTH=$(PARAM_RX_PTP_TS_FIFO_DEPTH) + COMPILE_ARGS += -GPTP_TS_WIDTH=$(PARAM_PTP_TS_WIDTH) + COMPILE_ARGS += -GTX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -GPTP_TAG_WIDTH=$(PARAM_PTP_TAG_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py b/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py new file mode 100644 index 000000000..fc2c1e501 --- /dev/null +++ b/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import pytest +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import XgmiiFrame, XgmiiSource, XgmiiSink +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + if len(dut.xgmii_txd) == 64: + cocotb.fork(Clock(dut.logic_clk, 6.4, units="ns").start()) + cocotb.fork(Clock(dut.rx_clk, 6.4, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 6.4, units="ns").start()) + else: + cocotb.fork(Clock(dut.logic_clk, 3.2, units="ns").start()) + cocotb.fork(Clock(dut.rx_clk, 3.2, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 3.2, units="ns").start()) + + self.xgmii_source = XgmiiSource(dut.xgmii_rxd, dut.xgmii_rxc, dut.rx_clk, dut.rx_rst) + self.xgmii_sink = XgmiiSink(dut.xgmii_txd, dut.xgmii_txc, dut.tx_clk, dut.tx_rst) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.logic_clk, dut.logic_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.logic_clk, dut.logic_rst) + + dut.ptp_sample_clk.setimmediatevalue(0) + dut.ptp_ts_96.setimmediatevalue(0) + + async def reset(self): + self.dut.logic_rst.setimmediatevalue(0) + self.dut.rx_rst.setimmediatevalue(0) + self.dut.tx_rst.setimmediatevalue(0) + await RisingEdge(self.dut.logic_clk) + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 1 + self.dut.rx_rst <= 1 + self.dut.tx_rst <= 1 + await RisingEdge(self.dut.logic_clk) + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 0 + self.dut.rx_rst <= 0 + self.dut.tx_rst <= 0 + await RisingEdge(self.dut.logic_clk) + await RisingEdge(self.dut.logic_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = XgmiiFrame.from_payload(test_data) + await tb.xgmii_source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12): + + tb = TB(dut) + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.xgmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + + assert tb.xgmii_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +async def run_test_tx_alignment(dut, payload_data=None, ifg=12): + + enable_dic = int(os.getenv("PARAM_ENABLE_DIC")) + + tb = TB(dut) + + byte_width = tb.axis_source.width // 8 + + tb.xgmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + + for length in range(60, 92): + + await tb.reset() + + test_frames = [payload_data(length) for k in range(10)] + start_lane = [] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.xgmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.ctrl is None + + start_lane.append(rx_frame.rx_start_lane) + + tb.log.info("length: %d", length) + tb.log.info("start_lane: %s", start_lane) + + start_lane_ref = [] + + # compute expected starting lanes + lane = 0 + deficit_idle_count = 0 + + for test_data in test_frames: + if ifg == 0: + lane = 0 + + start_lane_ref.append(lane) + lane = (lane + len(test_data)+4+ifg) % byte_width + + if enable_dic: + offset = lane % 4 + if deficit_idle_count+offset >= 4: + offset += 4 + lane = (lane - offset) % byte_width + deficit_idle_count = (deficit_idle_count + offset) % 4 + else: + offset = lane % 4 + if offset > 0: + offset += 4 + lane = (lane - offset) % byte_width + + tb.log.info("start_lane_ref: %s", start_lane_ref) + + assert start_lane_ref == start_lane + + await RisingEdge(dut.logic_clk) + + assert tb.xgmii_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12, 0]) + factory.generate_tests() + + factory = TestFactory(run_test_tx_alignment) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +@pytest.mark.parametrize("enable_dic", [1, 0]) +@pytest.mark.parametrize("data_width", [32, 64]) +def test_eth_mac_10g_fifo(request, data_width, enable_dic): + dut = "eth_mac_10g_fifo" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "eth_mac_10g.v"), + os.path.join(rtl_dir, "axis_xgmii_rx_32.v"), + os.path.join(rtl_dir, "axis_xgmii_rx_64.v"), + os.path.join(rtl_dir, "axis_xgmii_tx_32.v"), + os.path.join(rtl_dir, "axis_xgmii_tx_64.v"), + os.path.join(rtl_dir, "lfsr.v"), + os.path.join(axis_rtl_dir, "axis_adapter.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo_adapter.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = data_width + parameters['CTRL_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['AXIS_DATA_WIDTH'] = parameters['DATA_WIDTH'] + parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8) + parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['ENABLE_DIC'] = enable_dic + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_FIFO_DEPTH'] = 16384 + parameters['TX_FIFO_PIPELINE_OUTPUT'] = 2 + parameters['TX_FRAME_FIFO'] = 1 + parameters['TX_DROP_BAD_FRAME'] = parameters['TX_FRAME_FIFO'] + parameters['TX_DROP_WHEN_FULL'] = 0 + parameters['RX_FIFO_DEPTH'] = 16384 + parameters['RX_FIFO_PIPELINE_OUTPUT'] = 2 + parameters['RX_FRAME_FIFO'] = 1 + parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] + parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] + parameters['LOGIC_PTP_PERIOD_NS'] = 0x6 if parameters['AXIS_DATA_WIDTH'] == 64 else 0x3 + parameters['LOGIC_PTP_PERIOD_FNS'] = 0x6666 if parameters['AXIS_DATA_WIDTH'] == 64 else 0x3333 + parameters['PTP_PERIOD_NS'] = 0x6 if parameters['DATA_WIDTH'] == 64 else 0x3 + parameters['PTP_PERIOD_FNS'] = 0x6666 if parameters['DATA_WIDTH'] == 64 else 0x3333 + parameters['PTP_USE_SAMPLE_CLOCK'] = 0 + parameters['TX_PTP_TS_ENABLE'] = 0 + parameters['RX_PTP_TS_ENABLE'] = 0 + parameters['TX_PTP_TS_FIFO_DEPTH'] = 64 + parameters['RX_PTP_TS_FIFO_DEPTH'] = 64 + parameters['PTP_TS_WIDTH'] = 96 + parameters['TX_PTP_TAG_ENABLE'] = 0 + parameters['PTP_TAG_WIDTH'] = 16 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g/Makefile b/tb/eth_mac_1g/Makefile new file mode 100644 index 000000000..80b4a50ac --- /dev/null +++ b/tb/eth_mac_1g/Makefile @@ -0,0 +1,107 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_DATA_WIDTH ?= 8 +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_PTP_TS_ENABLE ?= 0 +export PARAM_TX_PTP_TS_WIDTH ?= 96 +export PARAM_TX_PTP_TAG_ENABLE ?= $(PARAM_TX_PTP_TS_ENABLE) +export PARAM_TX_PTP_TAG_WIDTH ?= 16 +export PARAM_RX_PTP_TS_ENABLE ?= 0 +export PARAM_RX_PTP_TS_WIDTH ?= 96 +# export PARAM_TX_USER_WIDTH ?= (TX_PTP_TAG_WIDTH if TX_PTP_TAG_ENABLE else 0) + 1 +export PARAM_TX_USER_WIDTH ?= 1 +# export PARAM_RX_USER_WIDTH ?= (RX_PTP_TS_WIDTH if RX_PTP_TS_ENABLE else 0) + 1 +export PARAM_RX_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TS_WIDTH=$(PARAM_TX_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).TX_PTP_TAG_WIDTH=$(PARAM_TX_PTP_TAG_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).RX_PTP_TS_WIDTH=$(PARAM_RX_PTP_TS_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_USER_WIDTH=$(PARAM_TX_USER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_USER_WIDTH=$(PARAM_RX_USER_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_PTP_TS_ENABLE=$(PARAM_TX_PTP_TS_ENABLE) + COMPILE_ARGS += -GTX_PTP_TS_WIDTH=$(PARAM_TX_PTP_TS_WIDTH) + COMPILE_ARGS += -GTX_PTP_TAG_ENABLE=$(PARAM_TX_PTP_TAG_ENABLE) + COMPILE_ARGS += -GTX_PTP_TAG_WIDTH=$(PARAM_TX_PTP_TAG_WIDTH) + COMPILE_ARGS += -GRX_PTP_TS_ENABLE=$(PARAM_RX_PTP_TS_ENABLE) + COMPILE_ARGS += -GRX_PTP_TS_WIDTH=$(PARAM_RX_PTP_TS_WIDTH) + COMPILE_ARGS += -GTX_USER_WIDTH=$(PARAM_TX_USER_WIDTH) + COMPILE_ARGS += -GRX_USER_WIDTH=$(PARAM_RX_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g/test_eth_mac_1g.py b/tb/eth_mac_1g/test_eth_mac_1g.py new file mode 100644 index 000000000..a2a053ac0 --- /dev/null +++ b/tb/eth_mac_1g/test_eth_mac_1g.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, GmiiSource, GmiiSink +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self._enable_generator_rx = None + self._enable_generator_tx = None + self._enable_cr_rx = None + self._enable_cr_tx = None + + cocotb.fork(Clock(dut.rx_clk, 8, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 8, units="ns").start()) + + self.gmii_source = GmiiSource(dut.gmii_rxd, dut.gmii_rx_er, dut.gmii_rx_dv, + dut.rx_clk, dut.rx_rst, dut.rx_clk_enable, dut.rx_mii_select) + self.gmii_sink = GmiiSink(dut.gmii_txd, dut.gmii_tx_er, dut.gmii_tx_en, + dut.tx_clk, dut.tx_rst, dut.tx_clk_enable, dut.tx_mii_select) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.tx_clk, dut.tx_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.rx_clk, dut.rx_rst) + + dut.rx_clk_enable.setimmediatevalue(1) + dut.tx_clk_enable.setimmediatevalue(1) + dut.rx_mii_select.setimmediatevalue(0) + dut.tx_mii_select.setimmediatevalue(0) + dut.rx_ptp_ts.setimmediatevalue(0) + dut.tx_ptp_ts.setimmediatevalue(0) + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.rx_rst.setimmediatevalue(0) + self.dut.tx_rst.setimmediatevalue(0) + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.rx_rst <= 1 + self.dut.tx_rst <= 1 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.rx_rst <= 0 + self.dut.tx_rst <= 0 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + + def set_enable_generator_rx(self, generator=None): + if self._enable_cr_rx is not None: + self._enable_cr_rx.kill() + self._enable_cr_rx = None + + self._enable_generator_rx = generator + + if self._enable_generator_rx is not None: + self._enable_cr_rx = cocotb.fork(self._run_enable_rx()) + + def set_enable_generator_tx(self, generator=None): + if self._enable_cr_tx is not None: + self._enable_cr_tx.kill() + self._enable_cr_tx = None + + self._enable_generator_tx = generator + + if self._enable_generator_tx is not None: + self._enable_cr_tx = cocotb.fork(self._run_enable_tx()) + + def clear_enable_generator_rx(self): + self.set_enable_generator_rx(None) + + def clear_enable_generator_tx(self): + self.set_enable_generator_tx(None) + + async def _run_enable_rx(self): + for val in self._enable_generator_rx: + self.dut.rx_clk_enable <= val + await RisingEdge(self.dut.rx_clk) + + async def _run_enable_tx(self): + for val in self._enable_generator_tx: + self.dut.tx_clk_enable <= val + await RisingEdge(self.dut.tx_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.gmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + tb.dut.rx_mii_select <= mii_sel + tb.dut.tx_mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator_rx(enable_gen()) + tb.set_enable_generator_tx(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.gmii_source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.gmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + tb.dut.rx_mii_select <= mii_sel + tb.dut.tx_mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator_rx(enable_gen()) + tb.set_enable_generator_tx(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.gmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.gmii_sink.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("enable_gen", [None, cycle_en]) + factory.add_option("mii_sel", [False, True]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g(request): + dut = "eth_mac_1g" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_PTP_TS_ENABLE'] = 0 + parameters['TX_PTP_TS_WIDTH'] = 96 + parameters['TX_PTP_TAG_ENABLE'] = parameters['TX_PTP_TS_ENABLE'] + parameters['TX_PTP_TAG_WIDTH'] = 16 + parameters['RX_PTP_TS_ENABLE'] = 0 + parameters['RX_PTP_TS_WIDTH'] = 96 + parameters['TX_USER_WIDTH'] = (parameters['TX_PTP_TAG_WIDTH'] if parameters['TX_PTP_TAG_ENABLE'] else 0) + 1 + parameters['RX_USER_WIDTH'] = (parameters['RX_PTP_TS_WIDTH'] if parameters['RX_PTP_TS_ENABLE'] else 0) + 1 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g_fifo/Makefile b/tb/eth_mac_1g_fifo/Makefile new file mode 100644 index 000000000..4859f6560 --- /dev/null +++ b/tb/eth_mac_1g_fifo/Makefile @@ -0,0 +1,115 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g_fifo +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_adapter.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo_adapter.v + +# module parameters +export PARAM_AXIS_DATA_WIDTH ?= 8 +export PARAM_AXIS_KEEP_ENABLE ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 ) +export PARAM_AXIS_KEEP_WIDTH ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_FIFO_DEPTH ?= 16384 +export PARAM_TX_FRAME_FIFO ?= 1 +export PARAM_TX_DROP_BAD_FRAME ?= $(PARAM_TX_FRAME_FIFO) +export PARAM_TX_DROP_WHEN_FULL ?= 0 +export PARAM_RX_FIFO_DEPTH ?= 16384 +export PARAM_RX_FRAME_FIFO ?= 1 +export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GAXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -GAXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -GAXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -GTX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -GTX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -GTX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -GRX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g_fifo/test_eth_mac_1g_fifo.py b/tb/eth_mac_1g_fifo/test_eth_mac_1g_fifo.py new file mode 100644 index 000000000..9e0b23774 --- /dev/null +++ b/tb/eth_mac_1g_fifo/test_eth_mac_1g_fifo.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, GmiiSource, GmiiSink +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self._enable_generator_rx = None + self._enable_generator_tx = None + self._enable_cr_rx = None + self._enable_cr_tx = None + + cocotb.fork(Clock(dut.logic_clk, 8, units="ns").start()) + cocotb.fork(Clock(dut.rx_clk, 8, units="ns").start()) + cocotb.fork(Clock(dut.tx_clk, 8, units="ns").start()) + + self.gmii_source = GmiiSource(dut.gmii_rxd, dut.gmii_rx_er, dut.gmii_rx_dv, + dut.rx_clk, dut.rx_rst, dut.rx_clk_enable, dut.rx_mii_select) + self.gmii_sink = GmiiSink(dut.gmii_txd, dut.gmii_tx_er, dut.gmii_tx_en, + dut.tx_clk, dut.tx_rst, dut.tx_clk_enable, dut.tx_mii_select) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.logic_clk, dut.logic_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.logic_clk, dut.logic_rst) + + dut.rx_clk_enable.setimmediatevalue(1) + dut.tx_clk_enable.setimmediatevalue(1) + dut.rx_mii_select.setimmediatevalue(0) + dut.tx_mii_select.setimmediatevalue(0) + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.logic_rst.setimmediatevalue(0) + self.dut.rx_rst.setimmediatevalue(0) + self.dut.tx_rst.setimmediatevalue(0) + for k in range(10): + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 1 + self.dut.rx_rst <= 1 + self.dut.tx_rst <= 1 + for k in range(10): + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 0 + self.dut.rx_rst <= 0 + self.dut.tx_rst <= 0 + for k in range(10): + await RisingEdge(self.dut.logic_clk) + + def set_enable_generator_rx(self, generator=None): + if self._enable_cr_rx is not None: + self._enable_cr_rx.kill() + self._enable_cr_rx = None + + self._enable_generator_rx = generator + + if self._enable_generator_rx is not None: + self._enable_cr_rx = cocotb.fork(self._run_enable_rx()) + + def set_enable_generator_tx(self, generator=None): + if self._enable_cr_tx is not None: + self._enable_cr_tx.kill() + self._enable_cr_tx = None + + self._enable_generator_tx = generator + + if self._enable_generator_tx is not None: + self._enable_cr_tx = cocotb.fork(self._run_enable_tx()) + + def clear_enable_generator_rx(self): + self.set_enable_generator_rx(None) + + def clear_enable_generator_tx(self): + self.set_enable_generator_tx(None) + + async def _run_enable_rx(self): + for val in self._enable_generator_rx: + self.dut.rx_clk_enable <= val + await RisingEdge(self.dut.rx_clk) + + async def _run_enable_tx(self): + for val in self._enable_generator_tx: + self.dut.tx_clk_enable <= val + await RisingEdge(self.dut.tx_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.gmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + tb.dut.rx_mii_select <= mii_sel + tb.dut.tx_mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator_rx(enable_gen()) + tb.set_enable_generator_tx(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.gmii_source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): + + tb = TB(dut) + + tb.gmii_source.ifg = ifg + tb.dut.ifg_delay <= ifg + tb.dut.rx_mii_select <= mii_sel + tb.dut.tx_mii_select <= mii_sel + + if enable_gen is not None: + tb.set_enable_generator_rx(enable_gen()) + tb.set_enable_generator_tx(enable_gen()) + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.gmii_sink.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.gmii_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("enable_gen", [None, cycle_en]) + factory.add_option("mii_sel", [False, True]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g_fifo(request): + dut = "eth_mac_1g_fifo" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + os.path.join(axis_rtl_dir, "axis_adapter.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo_adapter.v"), + ] + + parameters = {} + + parameters['AXIS_DATA_WIDTH'] = 8 + parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8) + parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_FIFO_DEPTH'] = 16384 + parameters['TX_FRAME_FIFO'] = 1 + parameters['TX_DROP_BAD_FRAME'] = parameters['TX_FRAME_FIFO'] + parameters['TX_DROP_WHEN_FULL'] = 0 + parameters['RX_FIFO_DEPTH'] = 16384 + parameters['RX_FRAME_FIFO'] = 1 + parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] + parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g_gmii/Makefile b/tb/eth_mac_1g_gmii/Makefile new file mode 100644 index 000000000..f1e9f2fb7 --- /dev/null +++ b/tb/eth_mac_1g_gmii/Makefile @@ -0,0 +1,83 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g_gmii +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/gmii_phy_if.v +VERILOG_SOURCES += ../../rtl/ssio_sdr_in.v +VERILOG_SOURCES += ../../rtl/ssio_sdr_out.v +VERILOG_SOURCES += ../../rtl/oddr.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g_gmii/test_eth_mac_1g_gmii.py b/tb/eth_mac_1g_gmii/test_eth_mac_1g_gmii.py new file mode 100644 index 000000000..caa2fbabb --- /dev/null +++ b/tb/eth_mac_1g_gmii/test_eth_mac_1g_gmii.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, GmiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=1000e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.gtx_clk, 8, units="ns").start()) + + self.gmii_phy = GmiiPhy(dut.gmii_txd, dut.gmii_tx_er, dut.gmii_tx_en, dut.mii_tx_clk, dut.gmii_tx_clk, + dut.gmii_rxd, dut.gmii_rx_er, dut.gmii_rx_dv, dut.gmii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.tx_clk, dut.tx_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.rx_clk, dut.rx_rst) + + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.gtx_rst.setimmediatevalue(0) + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.gtx_rst <= 1 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.gtx_rst <= 0 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + + def set_speed(self, speed): + pass + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.gmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + tb.set_speed(speed) + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.gmii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.gmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + tb.set_speed(speed) + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.gmii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.gmii_phy.tx.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g_gmii(request): + dut = "eth_mac_1g_gmii" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "gmii_phy_if.v"), + os.path.join(rtl_dir, "ssio_sdr_in.v"), + os.path.join(rtl_dir, "ssio_sdr_out.v"), + os.path.join(rtl_dir, "oddr.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g_gmii_fifo/Makefile b/tb/eth_mac_1g_gmii_fifo/Makefile new file mode 100644 index 000000000..27f103221 --- /dev/null +++ b/tb/eth_mac_1g_gmii_fifo/Makefile @@ -0,0 +1,120 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g_gmii_fifo +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/eth_mac_1g_gmii.v +VERILOG_SOURCES += ../../rtl/gmii_phy_if.v +VERILOG_SOURCES += ../../rtl/ssio_sdr_in.v +VERILOG_SOURCES += ../../rtl/ssio_sdr_out.v +VERILOG_SOURCES += ../../rtl/oddr.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_adapter.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo_adapter.v + +# module parameters +export PARAM_AXIS_DATA_WIDTH ?= 8 +export PARAM_AXIS_KEEP_ENABLE ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 ) +export PARAM_AXIS_KEEP_WIDTH ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_FIFO_DEPTH ?= 16384 +export PARAM_TX_FRAME_FIFO ?= 1 +export PARAM_TX_DROP_BAD_FRAME ?= $(PARAM_TX_FRAME_FIFO) +export PARAM_TX_DROP_WHEN_FULL ?= 0 +export PARAM_RX_FIFO_DEPTH ?= 16384 +export PARAM_RX_FRAME_FIFO ?= 1 +export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GAXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -GAXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -GAXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -GTX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -GTX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -GTX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -GRX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g_gmii_fifo/test_eth_mac_1g_gmii_fifo.py b/tb/eth_mac_1g_gmii_fifo/test_eth_mac_1g_gmii_fifo.py new file mode 100644 index 000000000..b0883ad5e --- /dev/null +++ b/tb/eth_mac_1g_gmii_fifo/test_eth_mac_1g_gmii_fifo.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, GmiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=1000e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.gtx_clk, 8, units="ns").start()) + cocotb.fork(Clock(dut.logic_clk, 8, units="ns").start()) + + self.gmii_phy = GmiiPhy(dut.gmii_txd, dut.gmii_tx_er, dut.gmii_tx_en, dut.mii_tx_clk, dut.gmii_tx_clk, + dut.gmii_rxd, dut.gmii_rx_er, dut.gmii_rx_dv, dut.gmii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.logic_clk, dut.logic_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.logic_clk, dut.logic_rst) + + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.gtx_rst.setimmediatevalue(0) + self.dut.logic_rst.setimmediatevalue(0) + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.gtx_rst <= 1 + self.dut.logic_rst <= 1 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.gtx_rst <= 0 + self.dut.logic_rst <= 0 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + + def set_speed(self, speed): + pass + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.gmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + tb.set_speed(speed) + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.gmii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.gmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + tb.set_speed(speed) + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.gmii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.gmii_phy.tx.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g_gmii_fifo(request): + dut = "eth_mac_1g_gmii_fifo" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "eth_mac_1g_gmii.v"), + os.path.join(rtl_dir, "gmii_phy_if.v"), + os.path.join(rtl_dir, "ssio_sdr_in.v"), + os.path.join(rtl_dir, "ssio_sdr_out.v"), + os.path.join(rtl_dir, "oddr.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + os.path.join(axis_rtl_dir, "axis_adapter.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo_adapter.v"), + ] + + parameters = {} + + parameters['AXIS_DATA_WIDTH'] = 8 + parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8) + parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_FIFO_DEPTH'] = 16384 + parameters['TX_FRAME_FIFO'] = 1 + parameters['TX_DROP_BAD_FRAME'] = parameters['TX_FRAME_FIFO'] + parameters['TX_DROP_WHEN_FULL'] = 0 + parameters['RX_FIFO_DEPTH'] = 16384 + parameters['RX_FRAME_FIFO'] = 1 + parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] + parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g_rgmii/Makefile b/tb/eth_mac_1g_rgmii/Makefile new file mode 100644 index 000000000..c1e095bd1 --- /dev/null +++ b/tb/eth_mac_1g_rgmii/Makefile @@ -0,0 +1,83 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g_rgmii +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/iddr.v +VERILOG_SOURCES += ../../rtl/oddr.v +VERILOG_SOURCES += ../../rtl/ssio_ddr_in.v +VERILOG_SOURCES += ../../rtl/rgmii_phy_if.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g_rgmii/test_eth_mac_1g_rgmii.py b/tb/eth_mac_1g_rgmii/test_eth_mac_1g_rgmii.py new file mode 100644 index 000000000..6d2c5693c --- /dev/null +++ b/tb/eth_mac_1g_rgmii/test_eth_mac_1g_rgmii.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, RgmiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=1000e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self.rgmii_phy = RgmiiPhy(dut.rgmii_txd, dut.rgmii_tx_ctl, dut.rgmii_tx_clk, + dut.rgmii_rxd, dut.rgmii_rx_ctl, dut.rgmii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.tx_clk, dut.tx_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.rx_clk, dut.rx_rst) + + dut.ifg_delay.setimmediatevalue(0) + + dut.gtx_clk.setimmediatevalue(0) + dut.gtx_clk90.setimmediatevalue(0) + + cocotb.fork(self._run_gtx_clk()) + + async def reset(self): + self.dut.gtx_rst.setimmediatevalue(0) + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + self.dut.gtx_rst <= 1 + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + self.dut.gtx_rst <= 0 + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + + async def _run_gtx_clk(self): + t = Timer(2, 'ns') + while True: + self.dut.gtx_clk <= 1 + await t + self.dut.gtx_clk90 <= 1 + await t + self.dut.gtx_clk <= 0 + await t + self.dut.gtx_clk90 <= 0 + await t + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.rgmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.rgmii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.rgmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.rgmii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.rgmii_phy.tx.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g_rgmii(request): + dut = "eth_mac_1g_rgmii" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "iddr.v"), + os.path.join(rtl_dir, "oddr.v"), + os.path.join(rtl_dir, "ssio_ddr_in.v"), + os.path.join(rtl_dir, "rgmii_phy_if.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_1g_rgmii_fifo/Makefile b/tb/eth_mac_1g_rgmii_fifo/Makefile new file mode 100644 index 000000000..e51e76b7f --- /dev/null +++ b/tb/eth_mac_1g_rgmii_fifo/Makefile @@ -0,0 +1,120 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_1g_rgmii_fifo +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/eth_mac_1g_rgmii.v +VERILOG_SOURCES += ../../rtl/iddr.v +VERILOG_SOURCES += ../../rtl/oddr.v +VERILOG_SOURCES += ../../rtl/ssio_ddr_in.v +VERILOG_SOURCES += ../../rtl/rgmii_phy_if.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_adapter.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo_adapter.v + +# module parameters +export PARAM_AXIS_DATA_WIDTH ?= 8 +export PARAM_AXIS_KEEP_ENABLE ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 ) +export PARAM_AXIS_KEEP_WIDTH ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_FIFO_DEPTH ?= 16384 +export PARAM_TX_FRAME_FIFO ?= 1 +export PARAM_TX_DROP_BAD_FRAME ?= $(PARAM_TX_FRAME_FIFO) +export PARAM_TX_DROP_WHEN_FULL ?= 0 +export PARAM_RX_FIFO_DEPTH ?= 16384 +export PARAM_RX_FRAME_FIFO ?= 1 +export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GAXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -GAXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -GAXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -GTX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -GTX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -GTX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -GRX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_1g_rgmii_fifo/test_eth_mac_1g_rgmii_fifo.py b/tb/eth_mac_1g_rgmii_fifo/test_eth_mac_1g_rgmii_fifo.py new file mode 100644 index 000000000..57cc78e09 --- /dev/null +++ b/tb/eth_mac_1g_rgmii_fifo/test_eth_mac_1g_rgmii_fifo.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, RgmiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=1000e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.logic_clk, 8, units="ns").start()) + + self.rgmii_phy = RgmiiPhy(dut.rgmii_txd, dut.rgmii_tx_ctl, dut.rgmii_tx_clk, + dut.rgmii_rxd, dut.rgmii_rx_ctl, dut.rgmii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.logic_clk, dut.logic_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.logic_clk, dut.logic_rst) + + dut.ifg_delay.setimmediatevalue(0) + + dut.gtx_clk.setimmediatevalue(0) + dut.gtx_clk90.setimmediatevalue(0) + + cocotb.fork(self._run_gtx_clk()) + + async def reset(self): + self.dut.gtx_rst.setimmediatevalue(0) + self.dut.logic_rst.setimmediatevalue(0) + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + self.dut.gtx_rst <= 1 + self.dut.logic_rst <= 1 + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + self.dut.gtx_rst <= 0 + self.dut.logic_rst <= 0 + await RisingEdge(self.dut.gtx_clk) + await RisingEdge(self.dut.gtx_clk) + + async def _run_gtx_clk(self): + t = Timer(2, 'ns') + while True: + self.dut.gtx_clk <= 1 + await t + self.dut.gtx_clk90 <= 1 + await t + self.dut.gtx_clk <= 0 + await t + self.dut.gtx_clk90 <= 0 + await t + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.rgmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.rgmii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6): + + tb = TB(dut, speed) + + tb.rgmii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.rx_clk) + + if speed == 10e6: + assert dut.speed == 0 + elif speed == 100e6: + assert dut.speed == 1 + else: + assert dut.speed == 2 + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.rgmii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.rgmii_phy.tx.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_1g_rgmii_fifo(request): + dut = "eth_mac_1g_rgmii_fifo" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "eth_mac_1g_rgmii.v"), + os.path.join(rtl_dir, "iddr.v"), + os.path.join(rtl_dir, "oddr.v"), + os.path.join(rtl_dir, "ssio_ddr_in.v"), + os.path.join(rtl_dir, "rgmii_phy_if.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + os.path.join(axis_rtl_dir, "axis_adapter.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo_adapter.v"), + ] + + parameters = {} + + parameters['AXIS_DATA_WIDTH'] = 8 + parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8) + parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_FIFO_DEPTH'] = 16384 + parameters['TX_FRAME_FIFO'] = 1 + parameters['TX_DROP_BAD_FRAME'] = parameters['TX_FRAME_FIFO'] + parameters['TX_DROP_WHEN_FULL'] = 0 + parameters['RX_FIFO_DEPTH'] = 16384 + parameters['RX_FRAME_FIFO'] = 1 + parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] + parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_mii/Makefile b/tb/eth_mac_mii/Makefile new file mode 100644 index 000000000..1ec24bebd --- /dev/null +++ b/tb/eth_mac_mii/Makefile @@ -0,0 +1,81 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_mii +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/ssio_sdr_in.v +VERILOG_SOURCES += ../../rtl/mii_phy_if.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v + +# module parameters +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_mii/test_eth_mac_mii.py b/tb/eth_mac_mii/test_eth_mac_mii.py new file mode 100644 index 000000000..0e0c852d1 --- /dev/null +++ b/tb/eth_mac_mii/test_eth_mac_mii.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, MiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=100e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + self.mii_phy = MiiPhy(dut.mii_txd, dut.mii_tx_er, dut.mii_tx_en, dut.mii_tx_clk, + dut.mii_rxd, dut.mii_rx_er, dut.mii_rx_dv, dut.mii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.tx_clk, dut.tx_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.rx_clk, dut.rx_rst) + + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.rst <= 1 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + self.dut.rst <= 0 + await RisingEdge(self.dut.tx_clk) + await RisingEdge(self.dut.tx_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6): + + tb = TB(dut, speed) + + tb.mii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.mii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.rx_clk) + await RisingEdge(dut.rx_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6): + + tb = TB(dut, speed) + + tb.mii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.mii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.mii_phy.tx.empty() + + await RisingEdge(dut.tx_clk) + await RisingEdge(dut.tx_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_mii(request): + dut = "eth_mac_mii" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "ssio_sdr_in.v"), + os.path.join(rtl_dir, "mii_phy_if.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + ] + + parameters = {} + + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/eth_mac_mii_fifo/Makefile b/tb/eth_mac_mii_fifo/Makefile new file mode 100644 index 000000000..b933a9d15 --- /dev/null +++ b/tb/eth_mac_mii_fifo/Makefile @@ -0,0 +1,118 @@ +# Copyright (c) 2020 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = eth_mac_mii_fifo +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v +VERILOG_SOURCES += ../../rtl/eth_mac_mii.v +VERILOG_SOURCES += ../../rtl/ssio_sdr_in.v +VERILOG_SOURCES += ../../rtl/mii_phy_if.v +VERILOG_SOURCES += ../../rtl/eth_mac_1g.v +VERILOG_SOURCES += ../../rtl/axis_gmii_rx.v +VERILOG_SOURCES += ../../rtl/axis_gmii_tx.v +VERILOG_SOURCES += ../../rtl/lfsr.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_adapter.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo.v +VERILOG_SOURCES += ../../lib/axis/rtl/axis_async_fifo_adapter.v + +# module parameters +export PARAM_AXIS_DATA_WIDTH ?= 8 +export PARAM_AXIS_KEEP_ENABLE ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 ) +export PARAM_AXIS_KEEP_WIDTH ?= $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 ) +export PARAM_ENABLE_PADDING ?= 1 +export PARAM_MIN_FRAME_LENGTH ?= 64 +export PARAM_TX_FIFO_DEPTH ?= 16384 +export PARAM_TX_FRAME_FIFO ?= 1 +export PARAM_TX_DROP_BAD_FRAME ?= $(PARAM_TX_FRAME_FIFO) +export PARAM_TX_DROP_WHEN_FULL ?= 0 +export PARAM_RX_FIFO_DEPTH ?= 16384 +export PARAM_RX_FRAME_FIFO ?= 1 +export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) +export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -P $(TOPLEVEL).AXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -P $(TOPLEVEL).MIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).TX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).TX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GAXIS_DATA_WIDTH=$(PARAM_AXIS_DATA_WIDTH) + COMPILE_ARGS += -GAXIS_KEEP_ENABLE=$(PARAM_AXIS_KEEP_ENABLE) + COMPILE_ARGS += -GAXIS_KEEP_WIDTH=$(PARAM_AXIS_KEEP_WIDTH) + COMPILE_ARGS += -GENABLE_PADDING=$(PARAM_ENABLE_PADDING) + COMPILE_ARGS += -GMIN_FRAME_LENGTH=$(PARAM_MIN_FRAME_LENGTH) + COMPILE_ARGS += -GTX_FIFO_DEPTH=$(PARAM_TX_FIFO_DEPTH) + COMPILE_ARGS += -GTX_FRAME_FIFO=$(PARAM_TX_FRAME_FIFO) + COMPILE_ARGS += -GTX_DROP_BAD_FRAME=$(PARAM_TX_DROP_BAD_FRAME) + COMPILE_ARGS += -GTX_DROP_WHEN_FULL=$(PARAM_TX_DROP_WHEN_FULL) + COMPILE_ARGS += -GRX_FIFO_DEPTH=$(PARAM_RX_FIFO_DEPTH) + COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) + COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) + COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf sim_build_* + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst + +include $(shell cocotb-config --makefiles)/Makefile.sim + diff --git a/tb/eth_mac_mii_fifo/test_eth_mac_mii_fifo.py b/tb/eth_mac_mii_fifo/test_eth_mac_mii_fifo.py new file mode 100644 index 000000000..5672c6f6e --- /dev/null +++ b/tb/eth_mac_mii_fifo/test_eth_mac_mii_fifo.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.eth import GmiiFrame, MiiPhy +from cocotbext.axi import AxiStreamSource, AxiStreamSink + + +class TB: + def __init__(self, dut, speed=100e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.fork(Clock(dut.logic_clk, 40, units="ns").start()) + + self.mii_phy = MiiPhy(dut.mii_txd, dut.mii_tx_er, dut.mii_tx_en, dut.mii_tx_clk, + dut.mii_rxd, dut.mii_rx_er, dut.mii_rx_dv, dut.mii_rx_clk, speed=speed) + + self.axis_source = AxiStreamSource(dut, "tx_axis", dut.logic_clk, dut.logic_rst) + self.axis_sink = AxiStreamSink(dut, "rx_axis", dut.logic_clk, dut.logic_rst) + + dut.ifg_delay.setimmediatevalue(0) + + async def reset(self): + self.dut.logic_rst.setimmediatevalue(0) + for k in range(10): + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 1 + for k in range(10): + await RisingEdge(self.dut.logic_clk) + self.dut.logic_rst <= 0 + for k in range(10): + await RisingEdge(self.dut.logic_clk) + + +async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6): + + tb = TB(dut, speed) + + tb.mii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = GmiiFrame.from_payload(test_data) + await tb.mii_phy.rx.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.axis_sink.recv() + + assert rx_frame.tdata == test_data + assert rx_frame.tuser == 0 + + assert tb.axis_sink.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6): + + tb = TB(dut, speed) + + tb.mii_phy.rx.ifg = ifg + tb.dut.ifg_delay <= ifg + + await tb.reset() + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + await tb.axis_source.send(test_data) + + for test_data in test_frames: + rx_frame = await tb.mii_phy.tx.recv() + + assert rx_frame.get_payload() == test_data + assert rx_frame.check_fcs() + assert rx_frame.error is None + + assert tb.mii_phy.tx.empty() + + await RisingEdge(dut.logic_clk) + await RisingEdge(dut.logic_clk) + + +def size_list(): + return list(range(60, 128)) + [512, 1514] + [60]*10 + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def cycle_en(): + return itertools.cycle([0, 0, 0, 1]) + + +if cocotb.SIM_NAME: + + for test in [run_test_rx, run_test_tx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("ifg", [12]) + factory.add_option("speed", [100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_eth_mac_mii_fifo(request): + dut = "eth_mac_mii_fifo" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + os.path.join(rtl_dir, "eth_mac_mii.v"), + os.path.join(rtl_dir, "ssio_sdr_in.v"), + os.path.join(rtl_dir, "mii_phy_if.v"), + os.path.join(rtl_dir, "eth_mac_1g.v"), + os.path.join(rtl_dir, "axis_gmii_rx.v"), + os.path.join(rtl_dir, "axis_gmii_tx.v"), + os.path.join(rtl_dir, "lfsr.v"), + os.path.join(axis_rtl_dir, "axis_adapter.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo.v"), + os.path.join(axis_rtl_dir, "axis_async_fifo_adapter.v"), + ] + + parameters = {} + + parameters['AXIS_DATA_WIDTH'] = 8 + parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8) + parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8 + parameters['ENABLE_PADDING'] = 1 + parameters['MIN_FRAME_LENGTH'] = 64 + parameters['TX_FIFO_DEPTH'] = 16384 + parameters['TX_FRAME_FIFO'] = 1 + parameters['TX_DROP_BAD_FRAME'] = parameters['TX_FRAME_FIFO'] + parameters['TX_DROP_WHEN_FULL'] = 0 + parameters['RX_FIFO_DEPTH'] = 16384 + parameters['RX_FRAME_FIFO'] = 1 + parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] + parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, + "sim_build_"+request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + )