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):
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)

View File

@ -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()

View File

@ -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()