From 71ec1174d1d9fe5c69f0d7a1f0bb29334ebd168d Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:53:27 -0800 Subject: [PATCH] add parameterized HW tests for all memory core modes --- src/manta/memory_core.py | 2 +- test/test_mem_core_hw.py | 145 +++++++++++++++++++++++--------------- test/test_mem_core_sim.py | 143 ++++++++----------------------------- 3 files changed, 117 insertions(+), 173 deletions(-) diff --git a/src/manta/memory_core.py b/src/manta/memory_core.py index edcbb74..f8c11b4 100644 --- a/src/manta/memory_core.py +++ b/src/manta/memory_core.py @@ -317,6 +317,6 @@ class MemoryCore(MantaCore): if not all(isinstance(d, int) for d in datas): raise TypeError("Write data must all be integers.") - bus_addrs = self._convert_user_to_bus_addr([addrs])[0] + bus_addrs = self._convert_user_to_bus_addr(addrs) bus_datas = [word for d in datas for word in value_to_words(d, self._n_mems)] self._interface.write(bus_addrs, bus_datas) diff --git a/test/test_mem_core_hw.py b/test/test_mem_core_hw.py index 242e4c6..fb8f853 100644 --- a/test/test_mem_core_hw.py +++ b/test/test_mem_core_hw.py @@ -8,16 +8,11 @@ from random import getrandbits from math import ceil, log2 import os -""" -Fundamentally we want a function to generate a configuration (as a dictionary) -for a memory core given the width, depth, and platform. This could be a random -configuration, or a standard one. -""" - class MemoryCoreLoopbackTest(Elaboratable): - def __init__(self, platform, width, depth, port): + def __init__(self, platform, mode, width, depth, port): self.platform = platform + self.mode = mode self.width = width self.depth = depth self.port = port @@ -28,19 +23,22 @@ class MemoryCoreLoopbackTest(Elaboratable): def platform_specific_config(self): return { "cores": { - "mem_core": { - "type": "memory", - "mode": "fpga_to_host", - "width": self.width, - "depth": self.depth, - }, "io_core": { "type": "io", "outputs": { - "addr": ceil(log2(self.depth)), - "data": self.width, - "we": 1, + "user_addr": ceil(log2(self.depth)), + "user_data_in": self.width, + "user_write_enable": 1, }, + "inputs": { + "user_data_out": self.width, + }, + }, + "mem_core": { + "type": "memory", + "mode": self.mode, + "width": self.width, + "depth": self.depth, }, }, "uart": { @@ -69,17 +67,21 @@ class MemoryCoreLoopbackTest(Elaboratable): uart_pins = platform.request("uart") - addr = self.get_probe("addr") - data = self.get_probe("data") - we = self.get_probe("we") + user_addr = self.get_probe("user_addr") + user_data_in = self.get_probe("user_data_in") + user_data_out = self.get_probe("user_data_out") + user_write_enable = self.get_probe("user_write_enable") - m.d.comb += [ - self.manta.mem_core.user_addr.eq(addr), - self.manta.mem_core.user_data_in.eq(data), - self.manta.mem_core.user_write_enable.eq(we), - self.manta.interface.rx.eq(uart_pins.rx.i), - uart_pins.tx.o.eq(self.manta.interface.tx), - ] + m.d.comb += self.manta.interface.rx.eq(uart_pins.rx.i) + m.d.comb += uart_pins.tx.o.eq(self.manta.interface.tx) + m.d.comb += self.manta.mem_core.user_addr.eq(user_addr) + + if self.mode in ["bidirectional", "fpga_to_host"]: + m.d.comb += self.manta.mem_core.user_data_in.eq(user_data_in) + m.d.comb += self.manta.mem_core.user_write_enable.eq(user_write_enable) + + if self.mode in ["bidirectional", "host_to_fpga"]: + m.d.comb += user_data_out.eq(self.manta.mem_core.user_data_out) return m @@ -87,48 +89,75 @@ class MemoryCoreLoopbackTest(Elaboratable): self.platform.build(self, do_program=True) def write_user_side(self, addr, data): - self.manta.io_core.set_probe("we", 0) - self.manta.io_core.set_probe("addr", addr) - self.manta.io_core.set_probe("data", data) - self.manta.io_core.set_probe("we", 1) - self.manta.io_core.set_probe("we", 0) + self.manta.io_core.set_probe("user_write_enable", 0) + self.manta.io_core.set_probe("user_addr", addr) + self.manta.io_core.set_probe("user_data_in", data) + self.manta.io_core.set_probe("user_write_enable", 1) + self.manta.io_core.set_probe("user_write_enable", 0) - def verify_register(self, addr, expected_data): - data = self.manta.mem_core.read(addr) - - if data != expected_data: - raise ValueError( - f"Memory read from {hex(addr)} returned {hex(data)} instead of {hex(expected_data)}." - ) + def read_user_side(self, addr): + self.manta.io_core.set_probe("user_write_enable", 0) + self.manta.io_core.set_probe("user_addr", addr) + return self.manta.io_core.get_probe("user_data_out") def verify(self): self.build_and_program() - # Read and write randomly from the bus side - for addr in jumble(range(self.depth)): - data = getrandbits(self.width) - self.write_user_side(addr, data) - self.verify_register(addr, data) + if self.mode in ["bidirectional", "host_to_fpga"]: + for addr in jumble(range(self.depth)): + + # Write a random balue to a random bus address + data = getrandbits(self.width) + self.manta.mem_core.write(addr, data) + + # Verify the same number is returned when reading on the user side + readback = self.read_user_side(addr) + if readback != data: + raise ValueError( + f"Memory read from {hex(addr)} returned {hex(data)} instead of {hex(readback)}." + ) + + if self.mode in ["bidirectional", "fpga_to_host"]: + for addr in jumble(range(self.depth)): + + # Write a random value to a random user address + data = getrandbits(self.width) + self.write_user_side(addr, data) + + # Verify the same number is returned when reading on the bus side + readback = self.manta.mem_core.read(addr) + if readback != data: + raise ValueError( + f"Memory read from {hex(addr)} returned {hex(data)} instead of {hex(readback)}." + ) + + +# Nexys4DDR Tests + +# Omit the bidirectional mode for now, pending completion of: +# https://github.com/amaranth-lang/amaranth/issues/1011 +modes = ["fpga_to_host", "host_to_fpga"] +widths = [1, 8, 14, 16, 33] +depths = [2, 512, 1024] +nexys4ddr_cases = [(m, w, d) for m in modes for w in widths for d in depths] @pytest.mark.skipif(not xilinx_tools_installed(), reason="no toolchain installed") -def test_mem_core_xilinx(): +@pytest.mark.parametrize("mode, width, depth", nexys4ddr_cases) +def test_mem_core_xilinx(mode, width, depth): port = os.environ["NEXYS4DDR_PORT"] - MemoryCoreLoopbackTest(Nexys4DDRPlatform(), 33, 1024, port).verify() + MemoryCoreLoopbackTest(Nexys4DDRPlatform(), mode, width, depth, port).verify() + + +# IceStick Tests +modes = ["fpga_to_host", "host_to_fpga"] +widths = [1, 8, 14, 16, 33] +depths = [2, 512, 1024] +ice40_cases = [(m, w, d) for m in modes for w in widths for d in depths] @pytest.mark.skipif(not ice40_tools_installed(), reason="no toolchain installed") -def test_mem_core_ice40(): +@pytest.mark.parametrize("mode, width, depth", ice40_cases) +def test_mem_core_ice40(mode, width, depth): port = os.environ["ICESTICK_PORT"] - MemoryCoreLoopbackTest(ICEStickPlatform(), 1, 2, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 1, 512, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 1, 1024, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 8, 2, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 8, 512, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 8, 1024, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 14, 512, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 14, 1024, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 16, 512, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 16, 1024, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 33, 512, port).verify() - MemoryCoreLoopbackTest(ICEStickPlatform(), 33, 1024, port).verify() + MemoryCoreLoopbackTest(ICEStickPlatform(), mode, width, depth, port).verify() diff --git a/test/test_mem_core_sim.py b/test/test_mem_core_sim.py index 0c4fc32..7fe2bae 100644 --- a/test/test_mem_core_sim.py +++ b/test/test_mem_core_sim.py @@ -2,6 +2,7 @@ from manta.memory_core import MemoryCore from manta.utils import * from random import randint, choice, getrandbits from math import ceil +import pytest class MemoryCoreTests: @@ -258,125 +259,39 @@ class MemoryCoreTests: yield self.mem_core.user_write_enable.eq(0) -def test_bidirectional(): - mem_core = MemoryCore( - mode="bidirectional", - width=23, - depth=512, - base_addr=0, - interface=None, - ) +modes = ["bidirectional", "fpga_to_host", "host_to_fpga"] +widths = [23, randint(0, 128)] +depths = [512, randint(0, 1024)] +base_addrs = [0, randint(0, 32678)] + +cases = [ + (m, w, d, ba) for m in modes for w in widths for d in depths for ba in base_addrs +] + + +@pytest.mark.parametrize("mode, width, depth, base_addr", cases) +def test_mem_core(mode, width, depth, base_addr): + mem_core = MemoryCore(mode, width, depth, base_addr, interface=None) tests = MemoryCoreTests(mem_core) @simulate(mem_core) - def test_bidirectional_testbench(): - yield from tests.bus_addrs_all_zero() - yield from tests.user_addrs_all_zero() + def testbench(): + if mode == "bidirectional": + yield from tests.bus_addrs_all_zero() + yield from tests.user_addrs_all_zero() - yield from tests.bus_to_bus_functionality() - yield from tests.user_to_bus_functionality() - yield from tests.bus_to_user_functionality() - yield from tests.user_to_user_functionality() + yield from tests.bus_to_bus_functionality() + yield from tests.user_to_bus_functionality() + yield from tests.bus_to_user_functionality() + yield from tests.user_to_user_functionality() - test_bidirectional_testbench() + if mode == "fpga_to_host": + yield from tests.bus_addrs_all_zero() + yield from tests.user_to_bus_functionality() + if mode == "host_to_fpga": + yield from tests.user_addrs_all_zero() + yield from tests.bus_to_user_functionality() -def test_bidirectional_random(): - mem_core = MemoryCore( - mode="bidirectional", - width=randint(0, 128), - depth=randint(0, 1024), - base_addr=randint(0, 32678), - interface=None, - ) - - tests = MemoryCoreTests(mem_core) - - @simulate(mem_core) - def test_bidirectional_random_testbench(): - yield from tests.bus_addrs_all_zero() - yield from tests.user_addrs_all_zero() - - yield from tests.bus_to_bus_functionality() - yield from tests.user_to_bus_functionality() - yield from tests.bus_to_user_functionality() - yield from tests.user_to_user_functionality() - - test_bidirectional_random_testbench() - - -def test_fpga_to_host(): - mem_core = MemoryCore( - mode="fpga_to_host", - width=23, - depth=512, - base_addr=0, - interface=None, - ) - - tests = MemoryCoreTests(mem_core) - - @simulate(mem_core) - def test_fpga_to_host_testbench(): - yield from tests.bus_addrs_all_zero() - yield from tests.user_to_bus_functionality() - - test_fpga_to_host_testbench() - - -def test_fpga_to_host_random(): - mem_core = MemoryCore( - mode="fpga_to_host", - width=randint(0, 128), - depth=randint(0, 1024), - base_addr=randint(0, 32678), - interface=None, - ) - - tests = MemoryCoreTests(mem_core) - - @simulate(mem_core) - def test_fpga_to_host_random_testbench(): - yield from tests.bus_addrs_all_zero() - yield from tests.user_to_bus_functionality() - - test_fpga_to_host_random_testbench() - - -def test_host_to_fpga(): - mem_core = MemoryCore( - mode="host_to_fpga", - width=23, - depth=512, - base_addr=0, - interface=None, - ) - - tests = MemoryCoreTests(mem_core) - - @simulate(mem_core) - def test_host_to_fpga_testbench(): - yield from tests.user_addrs_all_zero() - yield from tests.bus_to_user_functionality() - - test_host_to_fpga_testbench() - - -def test_host_to_fpga_random(): - mem_core = MemoryCore( - mode="host_to_fpga", - width=randint(0, 128), - depth=randint(0, 1024), - base_addr=randint(0, 32678), - interface=None, - ) - - tests = MemoryCoreTests(mem_core) - - @simulate(mem_core) - def test_host_to_fpga_random_testbench(): - yield from tests.user_addrs_all_zero() - yield from tests.bus_to_user_functionality() - - test_host_to_fpga_random_testbench() + testbench()