add more MemoryCore tests

This commit is contained in:
Fischer Moseley 2024-03-04 00:17:36 -08:00
parent 08adbd8ede
commit f83dc59b4e
5 changed files with 168 additions and 51 deletions

View File

@ -4,6 +4,7 @@ from amaranth.lib import wiring
from amaranth.lib.wiring import In, Out
from amaranth.sim import Simulator
from abc import ABC, abstractmethod
from random import sample
import os
@ -140,29 +141,26 @@ def simulate(top):
return decorator
# def simulate_decorator(testbench):
# def wrapper_accepting_arguments(top):
# sim = Simulator(top)
# sim.add_clock(1e-6) # 1 MHz
# sim.add_sync_process(testbench)
# vcd_path = testbench.__name__ + ".vcd"
# with sim.write_vcd(vcd_path):
# sim.run()
# return wrapper_accepting_arguments
def jumble(iterable):
"""
Returns the provided iterable, but with every element moved to a random
index. Very similar to random.shuffle, but returns an iteratable, instead
of modifying one in-place.
"""
return sample(iterable, len(iterable))
def verify_register(module, addr, expected_data):
"""
Read the contents of a register out over a module's bus connection, and verify
that it contains the expected data.
Read the contents of a register out over a module's bus connection, and
verify that it contains the expected data.
Unfortunately because Amaranth uses generator functions to define processes,
this must be a generator function and thus cannot return a value - it must
yield the next timestep. This means that the comparision with the expected
value must occur inside this function and not somewhere else, it's not
possible to return a value from here, and compare it in the calling function.
possible to return a value from here, and compare it in the calling
function.
"""
# Place read transaction on the bus

View File

@ -104,7 +104,7 @@ class MemoryCoreLoopbackTest(wiring.Component):
self.build_and_program()
# Read and write randomly from the bus side
for addr in sample(range(self.depth), k=self.depth):
for addr in jumble(range(self.depth)):
data = randint(0, 2**self.width - 1)
self.write_user_side(addr, data)
self.verify_register(addr, data)

View File

@ -1,6 +1,7 @@
from manta.memory_core import MemoryCore
from manta.utils import *
from random import randint, sample, choice
from random import randint, choice
from math import ceil
class MemoryCoreTests:
@ -10,6 +11,8 @@ class MemoryCoreTests:
self.max_addr = mem_core.max_addr
self.width = self.mem_core._width
self.depth = self.mem_core._depth
self.n_full = self.width // 16
self.n_mems = ceil(self.width / 16)
self.bus_addrs = list(
range(self.base_addr, self.max_addr)
@ -17,43 +20,38 @@ class MemoryCoreTests:
self.user_addrs = list(range(self.mem_core._depth))
self.model = {}
def check_each_address_on_bus_side_contains_zero(self):
def bus_addrs_all_zero(self):
for addr in self.bus_addrs:
yield from self.verify_bus_side(addr, 0)
def check_each_address_on_user_side_contains_zero(self):
def user_addrs_all_zero(self):
for addr in self.user_addrs:
yield from self.verify_user_side(addr, 0)
def check_write_then_immediately_read_bus_side(self):
def one_bus_write_then_one_bus_read(self):
for addr in self.bus_addrs:
# this part is a little hard to check since we might have a
# memory at the end of the address space that's less than
# 16-bits wide. so we'll have to calculate how wide our
# memory is
data_width = self.get_data_width(addr)
data = randint(0, (2**data_width) - 1)
yield from self.write_bus_side(addr, data)
yield from self.verify_bus_side(addr, data)
def check_multiple_writes_then_multiple_reads(self):
def multi_bus_writes_then_multi_bus_reads(self):
# write-write-write then read-read-read
for addr in sample(self.bus_addrs, len(self.bus_addrs)):
for addr in jumble(self.bus_addrs):
data_width = self.get_data_width(addr)
data = randint(0, (2**data_width) - 1)
self.model[addr] = data
yield from self.write_bus_side(addr, data)
for addr in sample(self.bus_addrs, len(self.bus_addrs)):
for addr in jumble(self.bus_addrs):
yield from self.verify_bus_side(addr, self.model[addr])
def check_random_reads_random_writes_random_orders(self):
def rand_bus_reads_writes(self):
# random reads and writes in random orders
for _ in range(5):
for addr in sample(self.bus_addrs, len(self.bus_addrs)):
for addr in jumble(self.bus_addrs):
operation = choice(["read", "write"])
if operation == "read":
@ -65,9 +63,69 @@ class MemoryCoreTests:
self.model[addr] = data
yield from self.write_bus_side(addr, data)
def one_user_write_then_one_bus_read(self):
for user_addr in self.user_addrs:
# write to user side
data = randint(0, (2**self.width) - 1)
yield from self.write_user_side(user_addr, data)
# verify contents when read out from the bus
words = value_to_words(data, self.n_mems)
for i, word in enumerate(words):
bus_addr = self.base_addr + user_addr + (i * self.depth)
yield from self.verify_bus_side(bus_addr, word)
def multi_user_write_then_multi_bus_reads(self):
# write-write-write then read-read-read
for user_addr in jumble(self.user_addrs):
# write a random number to the user side
data = randint(0, (2**self.width) - 1)
yield from self.write_user_side(user_addr, data)
# convert value to words, and save to self.model
words = value_to_words(data, self.n_mems)
for i, word in enumerate(words):
bus_addr = self.base_addr + user_addr + (i * self.depth)
self.model[bus_addr] = word
# read out every bus_addr in random order
for bus_addr in jumble(self.bus_addrs):
yield from self.verify_bus_side(bus_addr, self.model[bus_addr])
def rand_bus_reads_rand_user_writes(self):
# random reads and writes in random orders
for _ in range(5):
for user_addr in jumble(self.user_addrs):
bus_addrs = [
self.base_addr + user_addr + (i * self.depth)
for i in range(self.n_mems)
]
operation = choice(["read", "write"])
# read from bus side
if operation == "read":
for bus_addr in bus_addrs:
yield from self.verify_bus_side(bus_addr, self.model[bus_addr])
# write to user side
elif operation == "write":
data = randint(0, (2**self.width) - 1)
yield from self.write_user_side(user_addr, data)
# save words just written to self.model
words = value_to_words(data, self.n_mems)
for addr, word in zip(bus_addrs, words):
self.model[addr] = word
def get_data_width(self, addr):
n_full = self.width // 16
if addr < self.base_addr + (n_full * self.depth):
# this part is a little hard to check since we might have a
# memory at the end of the address space that's less than
# 16-bits wide. so we'll have to calculate how wide our
# memory is
if addr < self.base_addr + (self.n_full * self.depth):
return 16
else:
return self.width % 16
@ -84,7 +142,6 @@ class MemoryCoreTests:
def verify_user_side(self, addr, expected_data):
yield self.mem_core.user_addr.eq(addr)
yield self.mem_core.user_write_enable.eq(0)
yield
data = yield (self.mem_core.user_data_out)
@ -93,25 +150,87 @@ class MemoryCoreTests:
f"Read from {addr} yielded {data} instead of {expected_data}"
)
mem_core = MemoryCore(
mode="bidirectional",
width=23,
depth=512,
base_addr=0,
interface=None,
)
tests = MemoryCoreTests(mem_core)
def write_user_side(self, addr, data):
yield self.mem_core.user_addr.eq(addr)
yield self.mem_core.user_data_in.eq(data)
yield self.mem_core.user_write_enable.eq(1)
yield
yield self.mem_core.user_addr.eq(0)
yield self.mem_core.user_data_in.eq(0)
yield self.mem_core.user_write_enable.eq(0)
@simulate(mem_core)
def test_bidirectional_testbench():
yield from tests.check_each_address_on_bus_side_contains_zero()
yield from tests.check_each_address_on_user_side_contains_zero()
yield from tests.check_write_then_immediately_read_bus_side()
yield from tests.check_multiple_writes_then_multiple_reads()
yield from tests.check_random_reads_random_writes_random_orders()
def test_bidirectional():
mem_core = MemoryCore(
mode="bidirectional",
width=23,
depth=512,
base_addr=0,
interface=None,
)
tests = MemoryCoreTests(mem_core)
@simulate(mem_core)
def test_bidirectional_testbench():
yield from tests.bus_addrs_all_zero()
# Test Bus -> Bus functionality
yield from tests.user_addrs_all_zero()
yield from tests.one_bus_write_then_one_bus_read()
yield from tests.multi_bus_writes_then_multi_bus_reads()
yield from tests.rand_bus_reads_writes()
# Test User -> Bus functionality
yield from tests.one_user_write_then_one_bus_read()
yield from tests.multi_user_write_then_multi_bus_reads()
yield from tests.rand_bus_reads_rand_user_writes()
test_bidirectional_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()
# Test User -> Bus functionality
yield from tests.one_user_write_then_one_bus_read()
yield from tests.multi_user_write_then_multi_bus_reads()
yield from tests.rand_bus_reads_rand_user_writes()
test_fpga_to_host_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.one_user_write_then_one_bus_read()
# yield from tests.multi_user_write_then_multi_bus_reads()
# yield from tests.rand_bus_reads_rand_user_writes()
test_host_to_fpga_testbench()
# def test_sweep_core_widths():

View File

@ -57,5 +57,5 @@ def test_bytes_random_sample():
yield uart_rx.rx.eq(1)
yield
for i in sample(range(0xFF), k=0xFF):
for i in jumble(range(0xFF)):
yield from verify_receive(i)

View File

@ -49,5 +49,5 @@ def test_all_possible_bytes():
@simulate(uart_tx)
def test_bytes_random_sample():
for i in sample(range(0xFF), k=0xFF):
for i in jumble(range(0xFF)):
yield from verify_bit_sequence(i)