add parameterized HW tests for all memory core modes

This commit is contained in:
Fischer Moseley 2024-03-06 14:53:27 -08:00
parent 0b2c286075
commit 71ec1174d1
3 changed files with 117 additions and 173 deletions

View File

@ -317,6 +317,6 @@ class MemoryCore(MantaCore):
if not all(isinstance(d, int) for d in datas): if not all(isinstance(d, int) for d in datas):
raise TypeError("Write data must all be integers.") 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)] bus_datas = [word for d in datas for word in value_to_words(d, self._n_mems)]
self._interface.write(bus_addrs, bus_datas) self._interface.write(bus_addrs, bus_datas)

View File

@ -8,16 +8,11 @@ from random import getrandbits
from math import ceil, log2 from math import ceil, log2
import os 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): class MemoryCoreLoopbackTest(Elaboratable):
def __init__(self, platform, width, depth, port): def __init__(self, platform, mode, width, depth, port):
self.platform = platform self.platform = platform
self.mode = mode
self.width = width self.width = width
self.depth = depth self.depth = depth
self.port = port self.port = port
@ -28,19 +23,22 @@ class MemoryCoreLoopbackTest(Elaboratable):
def platform_specific_config(self): def platform_specific_config(self):
return { return {
"cores": { "cores": {
"mem_core": {
"type": "memory",
"mode": "fpga_to_host",
"width": self.width,
"depth": self.depth,
},
"io_core": { "io_core": {
"type": "io", "type": "io",
"outputs": { "outputs": {
"addr": ceil(log2(self.depth)), "user_addr": ceil(log2(self.depth)),
"data": self.width, "user_data_in": self.width,
"we": 1, "user_write_enable": 1,
}, },
"inputs": {
"user_data_out": self.width,
},
},
"mem_core": {
"type": "memory",
"mode": self.mode,
"width": self.width,
"depth": self.depth,
}, },
}, },
"uart": { "uart": {
@ -69,17 +67,21 @@ class MemoryCoreLoopbackTest(Elaboratable):
uart_pins = platform.request("uart") uart_pins = platform.request("uart")
addr = self.get_probe("addr") user_addr = self.get_probe("user_addr")
data = self.get_probe("data") user_data_in = self.get_probe("user_data_in")
we = self.get_probe("we") user_data_out = self.get_probe("user_data_out")
user_write_enable = self.get_probe("user_write_enable")
m.d.comb += [ m.d.comb += self.manta.interface.rx.eq(uart_pins.rx.i)
self.manta.mem_core.user_addr.eq(addr), m.d.comb += uart_pins.tx.o.eq(self.manta.interface.tx)
self.manta.mem_core.user_data_in.eq(data), m.d.comb += self.manta.mem_core.user_addr.eq(user_addr)
self.manta.mem_core.user_write_enable.eq(we),
self.manta.interface.rx.eq(uart_pins.rx.i), if self.mode in ["bidirectional", "fpga_to_host"]:
uart_pins.tx.o.eq(self.manta.interface.tx), 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 return m
@ -87,48 +89,75 @@ class MemoryCoreLoopbackTest(Elaboratable):
self.platform.build(self, do_program=True) self.platform.build(self, do_program=True)
def write_user_side(self, addr, data): def write_user_side(self, addr, data):
self.manta.io_core.set_probe("we", 0) self.manta.io_core.set_probe("user_write_enable", 0)
self.manta.io_core.set_probe("addr", addr) self.manta.io_core.set_probe("user_addr", addr)
self.manta.io_core.set_probe("data", data) self.manta.io_core.set_probe("user_data_in", data)
self.manta.io_core.set_probe("we", 1) self.manta.io_core.set_probe("user_write_enable", 1)
self.manta.io_core.set_probe("we", 0) self.manta.io_core.set_probe("user_write_enable", 0)
def verify_register(self, addr, expected_data): def read_user_side(self, addr):
data = self.manta.mem_core.read(addr) self.manta.io_core.set_probe("user_write_enable", 0)
self.manta.io_core.set_probe("user_addr", addr)
if data != expected_data: return self.manta.io_core.get_probe("user_data_out")
raise ValueError(
f"Memory read from {hex(addr)} returned {hex(data)} instead of {hex(expected_data)}."
)
def verify(self): def verify(self):
self.build_and_program() self.build_and_program()
# Read and write randomly from the bus side if self.mode in ["bidirectional", "host_to_fpga"]:
for addr in jumble(range(self.depth)): for addr in jumble(range(self.depth)):
data = getrandbits(self.width)
self.write_user_side(addr, data) # Write a random balue to a random bus address
self.verify_register(addr, data) 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") @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"] 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") @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"] port = os.environ["ICESTICK_PORT"]
MemoryCoreLoopbackTest(ICEStickPlatform(), 1, 2, port).verify() MemoryCoreLoopbackTest(ICEStickPlatform(), mode, width, depth, 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()

View File

@ -2,6 +2,7 @@ from manta.memory_core import MemoryCore
from manta.utils import * from manta.utils import *
from random import randint, choice, getrandbits from random import randint, choice, getrandbits
from math import ceil from math import ceil
import pytest
class MemoryCoreTests: class MemoryCoreTests:
@ -258,125 +259,39 @@ class MemoryCoreTests:
yield self.mem_core.user_write_enable.eq(0) yield self.mem_core.user_write_enable.eq(0)
def test_bidirectional(): modes = ["bidirectional", "fpga_to_host", "host_to_fpga"]
mem_core = MemoryCore( widths = [23, randint(0, 128)]
mode="bidirectional", depths = [512, randint(0, 1024)]
width=23, base_addrs = [0, randint(0, 32678)]
depth=512,
base_addr=0, cases = [
interface=None, (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) tests = MemoryCoreTests(mem_core)
@simulate(mem_core) @simulate(mem_core)
def test_bidirectional_testbench(): def testbench():
yield from tests.bus_addrs_all_zero() if mode == "bidirectional":
yield from tests.user_addrs_all_zero() yield from tests.bus_addrs_all_zero()
yield from tests.user_addrs_all_zero()
yield from tests.bus_to_bus_functionality() yield from tests.bus_to_bus_functionality()
yield from tests.user_to_bus_functionality() yield from tests.user_to_bus_functionality()
yield from tests.bus_to_user_functionality() yield from tests.bus_to_user_functionality()
yield from tests.user_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(): testbench()
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()