From cda8910ccf3c7366e32581edf424fb6ab19d905a Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sat, 26 Dec 2020 23:50:53 -0800 Subject: [PATCH] Add PHY wrapper tests --- tests/gmii_phy/Makefile | 65 +++++++++++ tests/gmii_phy/test_gmii_phy.py | 184 +++++++++++++++++++++++++++++ tests/gmii_phy/test_gmii_phy.v | 46 ++++++++ tests/mii_phy/Makefile | 65 +++++++++++ tests/mii_phy/test_mii_phy.py | 175 +++++++++++++++++++++++++++ tests/mii_phy/test_mii_phy.v | 45 +++++++ tests/rgmii_phy/Makefile | 65 +++++++++++ tests/rgmii_phy/test_rgmii_phy.py | 188 ++++++++++++++++++++++++++++++ tests/rgmii_phy/test_rgmii_phy.v | 43 +++++++ 9 files changed, 876 insertions(+) create mode 100644 tests/gmii_phy/Makefile create mode 100644 tests/gmii_phy/test_gmii_phy.py create mode 100644 tests/gmii_phy/test_gmii_phy.v create mode 100644 tests/mii_phy/Makefile create mode 100644 tests/mii_phy/test_mii_phy.py create mode 100644 tests/mii_phy/test_mii_phy.v create mode 100644 tests/rgmii_phy/Makefile create mode 100644 tests/rgmii_phy/test_rgmii_phy.py create mode 100644 tests/rgmii_phy/test_rgmii_phy.v diff --git a/tests/gmii_phy/Makefile b/tests/gmii_phy/Makefile new file mode 100644 index 0000000..241db7d --- /dev/null +++ b/tests/gmii_phy/Makefile @@ -0,0 +1,65 @@ +# 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 = 1ns + +DUT = test_gmii_phy +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-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/tests/gmii_phy/test_gmii_phy.py b/tests/gmii_phy/test_gmii_phy.py new file mode 100644 index 0000000..6403a9f --- /dev/null +++ b/tests/gmii_phy/test_gmii_phy.py @@ -0,0 +1,184 @@ +#!/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, GmiiPhy + + +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.phy_gtx_clk, 8, units="ns").start()) + + self.gmii_phy = GmiiPhy(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk, dut.phy_gtx_clk, + dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst, speed=speed) + + if speed == 1000e6: + self.source = GmiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_gtx_clk, dut.phy_rst) + self.source.mii_mode = False + self.sink = GmiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst) + self.sink.mii_mode = False + else: + self.source = GmiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk, dut.phy_rst) + self.source.mii_mode = True + self.sink = GmiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst) + self.sink.mii_mode = True + + async def reset(self): + self.dut.phy_rst.setimmediatevalue(0) + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 1 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 0 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_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.source.ifg = 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.source.send(test_frame) + + 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.phy_tx_clk) + await RisingEdge(dut.phy_tx_clk) + + +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.source.ifg = 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.gmii_phy.rx.send(test_frame) + + 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.phy_rx_clk) + await RisingEdge(dut.phy_rx_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_tx, run_test_rx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + + +def test_gmii_phy(request): + dut = "test_gmii_phy" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(tests_dir, f"{dut}.v"), + ] + + parameters = {} + + 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/tests/gmii_phy/test_gmii_phy.v b/tests/gmii_phy/test_gmii_phy.v new file mode 100644 index 0000000..abc3318 --- /dev/null +++ b/tests/gmii_phy/test_gmii_phy.v @@ -0,0 +1,46 @@ +/* + +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. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ns + +/* + * GMII PHY test + */ +module test_gmii_phy +( + inout wire phy_rst, + inout wire [7:0] phy_txd, + inout wire phy_tx_er, + inout wire phy_tx_en, + inout wire phy_tx_clk, + inout wire phy_gtx_clk, + inout wire [7:0] phy_rxd, + inout wire phy_rx_er, + inout wire phy_rx_dv, + inout wire phy_rx_clk +); + +endmodule diff --git a/tests/mii_phy/Makefile b/tests/mii_phy/Makefile new file mode 100644 index 0000000..4200585 --- /dev/null +++ b/tests/mii_phy/Makefile @@ -0,0 +1,65 @@ +# 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 = 1ns + +DUT = test_mii_phy +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-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/tests/mii_phy/test_mii_phy.py b/tests/mii_phy/test_mii_phy.py new file mode 100644 index 0000000..00e868b --- /dev/null +++ b/tests/mii_phy/test_mii_phy.py @@ -0,0 +1,175 @@ +#!/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, MiiSource, MiiSink, MiiPhy + + +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.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk, + dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst, speed=speed) + + self.source = MiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, + dut.phy_tx_clk, dut.phy_rst) + self.sink = MiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, + dut.phy_rx_clk, dut.phy_rst) + + async def reset(self): + self.dut.phy_rst.setimmediatevalue(0) + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 1 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 0 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_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.source.ifg = 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.source.send(test_frame) + + 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.phy_tx_clk) + await RisingEdge(dut.phy_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.source.ifg = 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.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.phy_rx_clk) + await RisingEdge(dut.phy_rx_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_tx, run_test_rx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("speed", [100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + + +def test_mii_phy(request): + dut = "test_mii_phy" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(tests_dir, f"{dut}.v"), + ] + + parameters = {} + + 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/tests/mii_phy/test_mii_phy.v b/tests/mii_phy/test_mii_phy.v new file mode 100644 index 0000000..93dd14f --- /dev/null +++ b/tests/mii_phy/test_mii_phy.v @@ -0,0 +1,45 @@ +/* + +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. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ns + +/* + * MII PHY test + */ +module test_mii_phy +( + inout wire phy_rst, + inout wire [3:0] phy_txd, + inout wire phy_tx_er, + inout wire phy_tx_en, + inout wire phy_tx_clk, + inout wire [3:0] phy_rxd, + inout wire phy_rx_er, + inout wire phy_rx_dv, + inout wire phy_rx_clk +); + +endmodule diff --git a/tests/rgmii_phy/Makefile b/tests/rgmii_phy/Makefile new file mode 100644 index 0000000..5619100 --- /dev/null +++ b/tests/rgmii_phy/Makefile @@ -0,0 +1,65 @@ +# 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 = 1ns + +DUT = test_rgmii_phy +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +SIM_BUILD ?= sim_build_$(MODULE) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-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/tests/rgmii_phy/test_rgmii_phy.py b/tests/rgmii_phy/test_rgmii_phy.py new file mode 100644 index 0000000..ece35fb --- /dev/null +++ b/tests/rgmii_phy/test_rgmii_phy.py @@ -0,0 +1,188 @@ +#!/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, RgmiiSource, RgmiiSink, RgmiiPhy + + +class TB: + def __init__(self, dut, speed=1000e6): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + if speed == 1000e6: + cocotb.fork(Clock(dut.phy_tx_clk, 8, units="ns").start()) + elif speed == 100e6: + cocotb.fork(Clock(dut.phy_tx_clk, 40, units="ns").start()) + elif speed == 10e6: + cocotb.fork(Clock(dut.phy_tx_clk, 400, units="ns").start()) + + self.rgmii_phy = RgmiiPhy(dut.phy_txd, dut.phy_tx_ctl, dut.phy_tx_clk, + dut.phy_rxd, dut.phy_rx_ctl, dut.phy_rx_clk, dut.phy_rst, speed=speed) + + self.source = RgmiiSource(dut.phy_txd, dut.phy_tx_ctl, dut.phy_tx_clk, dut.phy_rst) + self.sink = RgmiiSink(dut.phy_rxd, dut.phy_rx_ctl, dut.phy_rx_clk, dut.phy_rst) + + if speed == 1000e6: + self.source.mii_mode = False + self.sink.mii_mode = False + else: + self.source.mii_mode = True + self.sink.mii_mode = True + + async def reset(self): + self.dut.phy_rst.setimmediatevalue(0) + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 1 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_clk) + self.dut.phy_rst <= 0 + await RisingEdge(self.dut.phy_tx_clk) + await RisingEdge(self.dut.phy_tx_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.source.ifg = 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.source.send(test_frame) + + 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.phy_tx_clk) + await RisingEdge(dut.phy_tx_clk) + + +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.source.ifg = 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.rgmii_phy.rx.send(test_frame) + + 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.phy_rx_clk) + await RisingEdge(dut.phy_rx_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_tx, run_test_rx]: + + factory = TestFactory(test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [incrementing_payload]) + factory.add_option("speed", [1000e6, 100e6, 10e6]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + + +def test_rgmii_phy(request): + dut = "test_rgmii_phy" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(tests_dir, f"{dut}.v"), + ] + + parameters = {} + + 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/tests/rgmii_phy/test_rgmii_phy.v b/tests/rgmii_phy/test_rgmii_phy.v new file mode 100644 index 0000000..1280d38 --- /dev/null +++ b/tests/rgmii_phy/test_rgmii_phy.v @@ -0,0 +1,43 @@ +/* + +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. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ns + +/* + * RGMII PHY test + */ +module test_rgmii_phy +( + inout wire phy_rst, + inout wire [3:0] phy_txd, + inout wire phy_tx_ctl, + inout wire phy_tx_clk, + inout wire [3:0] phy_rxd, + inout wire phy_rx_ctl, + inout wire phy_rx_clk +); + +endmodule