add more MemoryCore tests
This commit is contained in:
parent
08adbd8ede
commit
f83dc59b4e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue