From 69fa7aa7c938af902c1eb17b2065ddc0c2a47650 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sun, 15 Nov 2020 02:46:32 -0800 Subject: [PATCH] Add tests --- tests/Makefile | 30 +++++ tests/axi/Makefile | 97 ++++++++++++++++ tests/axi/__init__.py | 0 tests/axi/test_axi.py | 238 ++++++++++++++++++++++++++++++++++++++++ tests/axi/test_axi.v | 94 ++++++++++++++++ tests/axil/Makefile | 79 +++++++++++++ tests/axil/__init__.py | 0 tests/axil/test_axil.py | 215 ++++++++++++++++++++++++++++++++++++ tests/axil/test_axil.v | 63 +++++++++++ tests/axis/Makefile | 85 ++++++++++++++ tests/axis/__init__.py | 0 tests/axis/test_axis.py | 162 +++++++++++++++++++++++++++ tests/axis/test_axis.v | 54 +++++++++ 13 files changed, 1117 insertions(+) create mode 100644 tests/Makefile create mode 100644 tests/axi/Makefile create mode 100644 tests/axi/__init__.py create mode 100644 tests/axi/test_axi.py create mode 100644 tests/axi/test_axi.v create mode 100644 tests/axil/Makefile create mode 100644 tests/axil/__init__.py create mode 100644 tests/axil/test_axil.py create mode 100644 tests/axil/test_axil.v create mode 100644 tests/axis/Makefile create mode 100644 tests/axis/__init__.py create mode 100644 tests/axis/test_axis.py create mode 100644 tests/axis/test_axis.v diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..651fe52 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,30 @@ +# 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. + +TOPTARGETS := all clean + +SUBDIRS := $(wildcard */.) + +$(TOPTARGETS): $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) + +.PHONY: $(TOPTARGETS) $(SUBDIRS) + diff --git a/tests/axi/Makefile b/tests/axi/Makefile new file mode 100644 index 0000000..58ae954 --- /dev/null +++ b/tests/axi/Makefile @@ -0,0 +1,97 @@ +# 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 = test_axi +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +# module parameters +export PARAM_DATA_WIDTH ?= 32 +export PARAM_ADDR_WIDTH ?= 32 +export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_ID_WIDTH ?= 8 +export PARAM_AWUSER_WIDTH ?= 1 +export PARAM_WUSER_WIDTH ?= 1 +export PARAM_BUSER_WIDTH ?= 1 +export PARAM_ARUSER_WIDTH ?= 1 +export PARAM_RUSER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ID_WIDTH=$(PARAM_ID_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).AWUSER_WIDTH=$(PARAM_AWUSER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).WUSER_WIDTH=$(PARAM_WUSER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).BUSER_WIDTH=$(PARAM_BUSER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ARUSER_WIDTH=$(PARAM_ARUSER_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).RUSER_WIDTH=$(PARAM_RUSER_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 -Wno-CASEINCOMPLETE + + COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH) + COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH) + COMPILE_ARGS += -GID_WIDTH=$(PARAM_ID_WIDTH) + COMPILE_ARGS += -GAWUSER_WIDTH=$(PARAM_AWUSER_WIDTH) + COMPILE_ARGS += -GWUSER_WIDTH=$(PARAM_WUSER_WIDTH) + COMPILE_ARGS += -GBUSER_WIDTH=$(PARAM_BUSER_WIDTH) + COMPILE_ARGS += -GARUSER_WIDTH=$(PARAM_ARUSER_WIDTH) + COMPILE_ARGS += -GRUSER_WIDTH=$(PARAM_RUSER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace + #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/axi/__init__.py b/tests/axi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/axi/test_axi.py b/tests/axi/test_axi.py new file mode 100644 index 0000000..e6a09d6 --- /dev/null +++ b/tests/axi/test_axi.py @@ -0,0 +1,238 @@ +""" + +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 random + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiMaster, AxiRam + +class TB(object): + def __init__(self, dut): + self.dut = dut + + cocotb.fork(Clock(dut.clk, 10, units="ns").start()) + + self.axi_master = AxiMaster(dut, "axi", dut.clk, dut.rst) + self.axi_ram = AxiRam(dut, "axi", dut.clk, dut.rst, size=2**16) + + self.axi_ram.write_if.log.setLevel(logging.DEBUG) + self.axi_ram.read_if.log.setLevel(logging.DEBUG) + + def set_idle_generator(self, generator=None): + if generator: + self.axi_master.write_if.aw_channel.set_pause_generator(generator()) + self.axi_master.write_if.w_channel.set_pause_generator(generator()) + self.axi_master.read_if.ar_channel.set_pause_generator(generator()) + self.axi_ram.write_if.b_channel.set_pause_generator(generator()) + self.axi_ram.read_if.r_channel.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.axi_master.write_if.b_channel.set_pause_generator(generator()) + self.axi_master.read_if.r_channel.set_pause_generator(generator()) + self.axi_ram.write_if.aw_channel.set_pause_generator(generator()) + self.axi_ram.write_if.w_channel.set_pause_generator(generator()) + self.axi_ram.read_if.ar_channel.set_pause_generator(generator()) + + async def cycle_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_write(dut, idle_inserter=None, backpressure_inserter=None, size=None): + + tb = TB(dut) + + byte_width = tb.axi_master.write_if.byte_width + max_burst_size = tb.axi_master.write_if.max_burst_size + + if size is None: + size = max_burst_size + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in list(range(1,byte_width*2))+[1024]: + for offset in list(range(byte_width))+list(range(4096-byte_width,4096)): + print(f"length {length}, offset {offset}") + addr = offset+0x1000 + test_data = bytearray([x%256 for x in range(length)]) + + tb.axi_ram.write_mem(addr-128, b'\xaa'*(length+256)) + + await tb.axi_master.write(addr, test_data, size=size) + + tb.axi_ram.hexdump((addr&0xfffffff0)-16, (((addr&0xf)+length-1)&0xfffffff0)+48) + + assert tb.axi_ram.read_mem(addr, length) == test_data + assert tb.axi_ram.read_mem(addr-1, 1) == b'\xaa' + assert tb.axi_ram.read_mem(addr+length, 1) == b'\xaa' + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None, size=None): + + tb = TB(dut) + + byte_width = tb.axi_master.write_if.byte_width + max_burst_size = tb.axi_master.write_if.max_burst_size + + if size is None: + size = max_burst_size + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in list(range(1,byte_width*2))+[1024]: + for offset in list(range(byte_width))+list(range(4096-byte_width,4096)): + print(f"length {length}, offset {offset}") + addr = offset+0x1000 + test_data = bytearray([x%256 for x in range(length)]) + + tb.axi_ram.write_mem(addr, test_data) + + data = await tb.axi_master.read(addr, length, size=size) + + assert data[0] == test_data + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + async def stress_test_worker(master, offset, aperture, count=16): + for k in range(count): + length = random.randint(1, min(512, aperture)) + addr = offset+random.randint(0, aperture-length) + test_data = bytearray([x%256 for x in range(length)]) + + await Timer(random.randint(1, 100), 'ns') + + await master.write(addr, test_data) + + await Timer(random.randint(1, 100), 'ns') + + data = await master.read(addr, length) + assert data[0] == test_data + + workers = [] + + for k in range(16): + workers.append(cocotb.fork(stress_test_worker(tb.axi_master, k*0x1000, 0x1000, count=16))) + + while workers: + await workers.pop(0).join() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + +if cocotb.SIM_NAME: + + data_width = int(os.getenv("PARAM_DATA_WIDTH")) + byte_width = data_width // 8 + max_burst_size = (byte_width-1).bit_length() + + for test in [run_test_write, run_test_read]: + + factory = TestFactory(test) + factory.add_option("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.add_option("size", [None]+list(range(max_burst_size))) + factory.generate_tests() + + factory = TestFactory(run_stress_test) + factory.generate_tests() + + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + +@pytest.mark.parametrize("data_width", [8, 16, 32]) +def test_axi(request, data_width): + dut = "test_axi" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(tests_dir, f"{dut}.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = data_width + parameters['ADDR_WIDTH'] = 32 + parameters['STRB_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['ID_WIDTH'] = 8 + parameters['AWUSER_WIDTH'] = 1 + parameters['WUSER_WIDTH'] = 1 + parameters['BUSER_WIDTH'] = 1 + parameters['ARUSER_WIDTH'] = 1 + parameters['RUSER_WIDTH'] = 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( + work_dir=tests_dir, + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) + diff --git a/tests/axi/test_axi.v b/tests/axi/test_axi.v new file mode 100644 index 0000000..5272698 --- /dev/null +++ b/tests/axi/test_axi.v @@ -0,0 +1,94 @@ +/* + +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 / 1ps + +/* + * AXI4 test module + */ +module test_axi # +( + parameter DATA_WIDTH = 32, + parameter ADDR_WIDTH = 32, + parameter STRB_WIDTH = (DATA_WIDTH/8), + parameter ID_WIDTH = 8, + parameter AWUSER_WIDTH = 1, + parameter WUSER_WIDTH = 1, + parameter BUSER_WIDTH = 1, + parameter ARUSER_WIDTH = 1, + parameter RUSER_WIDTH = 1 +) +( + input wire clk, + input wire rst, + + inout wire [ID_WIDTH-1:0] axi_awid, + inout wire [ADDR_WIDTH-1:0] axi_awaddr, + inout wire [7:0] axi_awlen, + inout wire [2:0] axi_awsize, + inout wire [1:0] axi_awburst, + inout wire axi_awlock, + inout wire [3:0] axi_awcache, + inout wire [2:0] axi_awprot, + inout wire [3:0] axi_awqos, + inout wire [3:0] axi_awregion, + inout wire [AWUSER_WIDTH-1:0] axi_awuser, + inout wire axi_awvalid, + inout wire axi_awready, + inout wire [DATA_WIDTH-1:0] axi_wdata, + inout wire [STRB_WIDTH-1:0] axi_wstrb, + inout wire axi_wlast, + inout wire [WUSER_WIDTH-1:0] axi_wuser, + inout wire axi_wvalid, + inout wire axi_wready, + inout wire [ID_WIDTH-1:0] axi_bid, + inout wire [1:0] axi_bresp, + inout wire [BUSER_WIDTH-1:0] axi_buser, + inout wire axi_bvalid, + inout wire axi_bready, + inout wire [ID_WIDTH-1:0] axi_arid, + inout wire [ADDR_WIDTH-1:0] axi_araddr, + inout wire [7:0] axi_arlen, + inout wire [2:0] axi_arsize, + inout wire [1:0] axi_arburst, + inout wire axi_arlock, + inout wire [3:0] axi_arcache, + inout wire [2:0] axi_arprot, + inout wire [3:0] axi_arqos, + inout wire [3:0] axi_arregion, + inout wire [ARUSER_WIDTH-1:0] axi_aruser, + inout wire axi_arvalid, + inout wire axi_arready, + inout wire [ID_WIDTH-1:0] axi_rid, + inout wire [DATA_WIDTH-1:0] axi_rdata, + inout wire [1:0] axi_rresp, + inout wire axi_rlast, + inout wire [RUSER_WIDTH-1:0] axi_ruser, + inout wire axi_rvalid, + inout wire axi_rready +); + +endmodule diff --git a/tests/axil/Makefile b/tests/axil/Makefile new file mode 100644 index 0000000..62a3090 --- /dev/null +++ b/tests/axil/Makefile @@ -0,0 +1,79 @@ +# 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 = test_axil +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +# module parameters +export PARAM_DATA_WIDTH ?= 32 +export PARAM_ADDR_WIDTH ?= 32 +export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_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 += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH) + COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace + #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/axil/__init__.py b/tests/axil/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/axil/test_axil.py b/tests/axil/test_axil.py new file mode 100644 index 0000000..13cef02 --- /dev/null +++ b/tests/axil/test_axil.py @@ -0,0 +1,215 @@ +""" + +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 os +import random + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiLiteMaster, AxiLiteRam + +class TB(object): + def __init__(self, dut): + self.dut = dut + + cocotb.fork(Clock(dut.clk, 10, units="ns").start()) + + self.axil_master = AxiLiteMaster(dut, "axil", dut.clk, dut.rst) + self.axil_ram = AxiLiteRam(dut, "axil", dut.clk, dut.rst, size=2**16) + + def set_idle_generator(self, generator=None): + if generator: + self.axil_master.write_if.aw_channel.set_pause_generator(generator()) + self.axil_master.write_if.w_channel.set_pause_generator(generator()) + self.axil_master.read_if.ar_channel.set_pause_generator(generator()) + self.axil_ram.write_if.b_channel.set_pause_generator(generator()) + self.axil_ram.read_if.r_channel.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.axil_master.write_if.b_channel.set_pause_generator(generator()) + self.axil_master.read_if.r_channel.set_pause_generator(generator()) + self.axil_ram.write_if.aw_channel.set_pause_generator(generator()) + self.axil_ram.write_if.w_channel.set_pause_generator(generator()) + self.axil_ram.read_if.ar_channel.set_pause_generator(generator()) + + async def cycle_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_write(dut, data_in=None, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + byte_width = tb.axil_master.write_if.byte_width + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in range(1,byte_width*2): + for offset in range(byte_width): + print(f"length {length}, offset {offset}") + addr = offset+0x1000 + test_data = bytearray([x%256 for x in range(length)]) + + tb.axil_ram.write_mem(addr-128, b'\xaa'*(length+256)) + + await tb.axil_master.write(addr, test_data) + + tb.axil_ram.hexdump((addr&0xfffffff0)-16, (((addr&0xf)+length-1)&0xfffffff0)+48) + + assert tb.axil_ram.read_mem(addr, length) == test_data + assert tb.axil_ram.read_mem(addr-1, 1) == b'\xaa' + assert tb.axil_ram.read_mem(addr+length, 1) == b'\xaa' + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + byte_width = tb.axil_master.write_if.byte_width + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + for length in range(1,byte_width*2): + for offset in range(byte_width): + print(f"length {length}, offset {offset}") + addr = offset+0x1000 + test_data = bytearray([x%256 for x in range(length)]) + + tb.axil_ram.write_mem(addr, test_data) + + data = await tb.axil_master.read(addr, length) + + assert data[0] == test_data + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + async def stress_test_worker(master, offset, aperture, count=16): + for k in range(count): + length = random.randint(1, min(32, aperture)) + addr = offset+random.randint(0, aperture-length) + test_data = bytearray([x%256 for x in range(length)]) + + await Timer(random.randint(1, 100), 'ns') + + await master.write(addr, test_data) + + await Timer(random.randint(1, 100), 'ns') + + data = await master.read(addr, length) + assert data[0] == test_data + + workers = [] + + for k in range(16): + workers.append(cocotb.fork(stress_test_worker(tb.axil_master, k*0x1000, 0x1000, count=16))) + + while workers: + await workers.pop(0).join() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + +if cocotb.SIM_NAME: + + for test in [run_test_write, run_test_read]: + + factory = TestFactory(test) + factory.add_option("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.generate_tests() + + factory = TestFactory(run_stress_test) + factory.generate_tests() + + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + +@pytest.mark.parametrize("data_width", [8, 16, 32]) +def test_axil(request, data_width): + dut = "test_axil" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(os.path.dirname(__file__), f"{dut}.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = data_width + parameters['ADDR_WIDTH'] = 32 + parameters['STRB_WIDTH'] = parameters['DATA_WIDTH'] // 8 + + 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( + work_dir=tests_dir, + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) + diff --git a/tests/axil/test_axil.v b/tests/axil/test_axil.v new file mode 100644 index 0000000..72ad2c6 --- /dev/null +++ b/tests/axil/test_axil.v @@ -0,0 +1,63 @@ +/* + +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 / 1ps + +/* + * AXI lite test module + */ +module test_axil # +( + parameter DATA_WIDTH = 32, + parameter ADDR_WIDTH = 16, + parameter STRB_WIDTH = (DATA_WIDTH/8) +) +( + input wire clk, + input wire rst, + + inout wire [ADDR_WIDTH-1:0] axil_awaddr, + inout wire [2:0] axil_awprot, + inout wire axil_awvalid, + inout wire axil_awready, + inout wire [DATA_WIDTH-1:0] axil_wdata, + inout wire [STRB_WIDTH-1:0] axil_wstrb, + inout wire axil_wvalid, + inout wire axil_wready, + inout wire [1:0] axil_bresp, + inout wire axil_bvalid, + inout wire axil_bready, + inout wire [ADDR_WIDTH-1:0] axil_araddr, + inout wire [2:0] axil_arprot, + inout wire axil_arvalid, + inout wire axil_arready, + inout wire [DATA_WIDTH-1:0] axil_rdata, + inout wire [1:0] axil_rresp, + inout wire axil_rvalid, + inout wire axil_rready +); + +endmodule diff --git a/tests/axis/Makefile b/tests/axis/Makefile new file mode 100644 index 0000000..a35c689 --- /dev/null +++ b/tests/axis/Makefile @@ -0,0 +1,85 @@ +# 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 = test_axis +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES += $(DUT).v + +# module parameters +export PARAM_DATA_WIDTH ?= 8 +export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) +export PARAM_ID_WIDTH ?= 8 +export PARAM_DEST_WIDTH ?= 8 +export PARAM_USER_WIDTH ?= 1 + +SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH) + +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).ID_WIDTH=$(PARAM_ID_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).DEST_WIDTH=$(PARAM_DEST_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 += -GID_WIDTH=$(PARAM_ID_WIDTH) + COMPILE_ARGS += -GDEST_WIDTH=$(PARAM_DEST_WIDTH) + COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace + #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/axis/__init__.py b/tests/axis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/axis/test_axis.py b/tests/axis/test_axis.py new file mode 100644 index 0000000..e4d0174 --- /dev/null +++ b/tests/axis/test_axis.py @@ -0,0 +1,162 @@ +#!/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 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.axi import AxiStreamFrame, AxiStreamSource, AxiStreamSink + +class TB(object): + def __init__(self, dut): + self.dut = dut + + cocotb.fork(Clock(dut.clk, 10, units="ns").start()) + + self.source = AxiStreamSource(dut, "axis", dut.clk, dut.rst) + self.sink = AxiStreamSink(dut, "axis", dut.clk, dut.rst) + + def set_idle_generator(self, generator=None): + if generator: + self.source.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.sink.set_pause_generator(generator()) + + 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, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + id_count = 2**len(tb.source.bus.tid) + + cur_id = 1 + + await tb.reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + test_frames = [] + + for test_data in [payload_data(l) for l in payload_lengths()]: + test_frame = AxiStreamFrame(test_data) + test_frame.tid = cur_id + test_frame.tdest = cur_id + tb.source.send(test_frame) + + test_frames.append(test_frame) + + cur_id = (cur_id + 1) % id_count + + for test_frame in test_frames: + await tb.sink.wait() + rx_frame = tb.sink.recv() + + assert rx_frame.tdata == test_frame.tdata + assert rx_frame.tid == test_frame.tid + assert rx_frame.tdest == test_frame.tdest + assert not rx_frame.tuser + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + +def size_list(): + data_width = int(os.getenv("PARAM_DATA_WIDTH")) + byte_width = data_width // 8 + return list(range(1,data_width*4+1))+[512]+[1]*64 + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + +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("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.generate_tests() + + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + +@pytest.mark.parametrize("data_width", [8, 16, 32]) +def test_axis(request, data_width): + dut = "test_axis" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(tests_dir, f"{dut}.v"), + ] + + parameters = {} + + parameters['DATA_WIDTH'] = data_width + parameters['KEEP_WIDTH'] = parameters['DATA_WIDTH'] // 8 + parameters['ID_WIDTH'] = 8 + parameters['DEST_WIDTH'] = 8 + parameters['USER_WIDTH'] = 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( + work_dir=tests_dir, + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) + diff --git a/tests/axis/test_axis.v b/tests/axis/test_axis.v new file mode 100644 index 0000000..d50d112 --- /dev/null +++ b/tests/axis/test_axis.v @@ -0,0 +1,54 @@ +/* + +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 / 1ps + +/* + * AXI4-Stream test + */ +module test_axis # +( + parameter DATA_WIDTH = 8, + parameter KEEP_WIDTH = (DATA_WIDTH/8), + parameter ID_WIDTH = 8, + parameter DEST_WIDTH = 8, + parameter USER_WIDTH = 1 +) +( + input wire clk, + input wire rst, + + inout wire [DATA_WIDTH-1:0] axis_tdata, + inout wire [KEEP_WIDTH-1:0] axis_tkeep, + inout wire axis_tvalid, + inout wire axis_tready, + inout wire axis_tlast, + inout wire [ID_WIDTH-1:0] axis_tid, + inout wire [DEST_WIDTH-1:0] axis_tdest, + inout wire [USER_WIDTH-1:0] axis_tuser +); + +endmodule