make model tracking automatic in memory core tests

This commit is contained in:
Fischer Moseley 2024-03-06 01:12:36 -08:00
parent c1935bcb11
commit 5d5a50042f
6 changed files with 96 additions and 106 deletions

View File

@ -2,7 +2,7 @@ from amaranth import *
from manta.utils import *
from manta.ethernet.source_bridge import UDPSourceBridge
from manta.ethernet.sink_bridge import UDPSinkBridge
from random import randint
from random import getrandbits
import socket
@ -339,7 +339,7 @@ class EthernetInterface(Elaboratable):
# https://en.wikipedia.org/wiki/MAC_address#Ranges_of_group_and_locally_administered_addresses
if "mac_address" not in liteeth_config:
addr = list(f"{randint(0, (2**48) - 1):012x}")
addr = list(f"{getrandbits(48):012x}")
addr[1] = "2"
liteeth_config["mac_address"] = int("".join(addr), 16)
print(liteeth_config["mac_address"])

View File

@ -2,7 +2,7 @@ from amaranth import *
from amaranth.lib import data
from amaranth.sim import Simulator
from abc import ABC, abstractmethod
from random import sample
from random import sample, randint
import os
@ -75,6 +75,20 @@ def words_to_value(data):
return int("".join([f"{i:016b}" for i in data[::-1]]), 2)
def value_to_words(data, n_words):
"""
Takes a integer, interprets it as a set of 16-bit integers
concatenated together, and splits it into a list of 16-bit numbers.
"""
if not isinstance(data, int) or data < 0:
raise ValueError("Behavior is only defined for nonnegative integers.")
# Convert to binary, split into 16-bit chunks, and then convert back to list of int
binary = f"{data:0b}".zfill(n_words * 16)
return [int(binary[i : i + 16], 2) for i in range(0, 16 * n_words, 16)][::-1]
def check_value_fits_in_bits(value, n_bits):
"""
Rasies an exception if the provided value isn't an integer that cannot
@ -91,20 +105,6 @@ def check_value_fits_in_bits(value, n_bits):
raise ValueError("Signed integer too large.")
def value_to_words(data, n_words):
"""
Takes a integer, interprets it as a set of 16-bit integers
concatenated together, and splits it into a list of 16-bit numbers.
"""
if not isinstance(data, int) or data < 0:
raise ValueError("Behavior is only defined for nonnegative integers.")
# Convert to binary, split into 16-bit chunks, and then convert back to list of int
binary = f"{data:0b}".zfill(n_words * 16)
return [int(binary[i : i + 16], 2) for i in range(0, 16 * n_words, 16)][::-1]
def split_into_chunks(data, chunk_size):
"""
Split a list into a list of lists, where each sublist has length `chunk_size`.

View File

@ -4,7 +4,7 @@ from amaranth_boards.icestick import ICEStickPlatform
from manta import Manta
from manta.utils import *
import pytest
from random import randint
from random import getrandbits
class IOCoreLoopbackTest(Elaboratable):
@ -120,7 +120,7 @@ class IOCoreLoopbackTest(Elaboratable):
# to the second input, and so on...
for input, output in zip(inputs, outputs):
width = self.config["cores"]["io_core"]["inputs"][input]
value = randint(0, 2**width - 1)
value = getrandbits(width)
self.manta.io_core.set_probe(output, value)
readback = self.manta.io_core.get_probe(input)

View File

@ -2,7 +2,7 @@ from amaranth import *
from amaranth.sim import Simulator
from manta.io_core import IOCore
from manta.utils import *
from random import randint
from random import getrandbits
probe0 = Signal(1)
probe1 = Signal(2)
@ -51,7 +51,7 @@ def test_output_probe_buffer_initial_value():
def test_output_probes_are_writeable():
for o in outputs:
addrs = io_core._memory_map[o.name]["addrs"]
test_value = randint(0, (2**o.width) - 1)
test_value = getrandbits(o.width)
datas = value_to_words(test_value, len(addrs))
# write value to registers
@ -67,7 +67,7 @@ def test_output_probes_are_writeable():
def test_output_probes_update():
for o in outputs:
addrs = io_core._memory_map[o.name]["addrs"]
test_value = randint(0, (2**o.width) - 1)
test_value = getrandbits(o.width)
datas = value_to_words(test_value, len(addrs))
# write value to registers
@ -93,7 +93,7 @@ def test_output_probes_update():
def test_input_probes_update():
for i in inputs:
# set input probe value
test_value = randint(0, (2**i.width) - 1)
test_value = getrandbits(i.width)
yield i.eq(test_value)
# pulse strobe register

View File

@ -4,7 +4,7 @@ from amaranth_boards.icestick import ICEStickPlatform
from manta import Manta
from manta.utils import *
import pytest
from random import randint, sample
from random import randint, getrandbits
from math import ceil, log2
"""
@ -105,7 +105,7 @@ class MemoryCoreLoopbackTest(Elaboratable):
# Read and write randomly from the bus side
for addr in jumble(range(self.depth)):
data = randint(0, 2**self.width - 1)
data = getrandbits(self.width)
self.write_user_side(addr, data)
self.verify_register(addr, data)

View File

@ -1,6 +1,6 @@
from manta.memory_core import MemoryCore
from manta.utils import *
from random import randint, choice
from random import randint, choice, getrandbits
from math import ceil
@ -18,56 +18,56 @@ class MemoryCoreTests:
range(self.base_addr, self.max_addr)
) # include the endpoint!
self.user_addrs = list(range(self.mem_core._depth))
self.model = {}
# A model of what each bus address contains
self.model = {i: 0 for i in self.bus_addrs}
def bus_addrs_all_zero(self):
for addr in self.bus_addrs:
yield from self.verify_bus_side(addr, 0)
yield from self.verify_bus_side(addr)
def user_addrs_all_zero(self):
for addr in self.user_addrs:
yield from self.verify_user_side(addr, 0)
yield from self.verify_user_side(addr)
def bus_to_bus_functionality(self):
yield from self.one_bus_write_then_one_bus_read()
yield from self.multi_bus_writes_then_multi_bus_reads()
# yield from self.one_bus_write_then_one_bus_read()
# yield from self.multi_bus_writes_then_multi_bus_reads()
yield from self.rand_bus_writes_rand_bus_reads()
def user_to_bus_functionality(self):
yield from self.one_user_write_then_one_bus_read()
yield from self.multi_user_write_then_multi_bus_reads()
# yield from self.one_user_write_then_one_bus_read()
# yield from self.multi_user_write_then_multi_bus_reads()
yield from self.rand_user_writes_rand_bus_reads()
def bus_to_user_functionality(self):
yield from self.one_bus_write_then_one_user_read()
yield from self.multi_bus_write_then_multi_user_reads()
# yield from self.one_bus_write_then_one_user_read()
# yield from self.multi_bus_write_then_multi_user_reads()
yield from self.rand_bus_writes_rand_user_reads()
def user_to_user_functionality(self):
yield from self.one_user_write_then_one_user_read()
yield from self.multi_user_write_then_multi_user_read()
# yield from self.one_user_write_then_one_user_read()
# yield from self.multi_user_write_then_multi_user_read()
yield from self.rand_user_write_rand_user_read()
def one_bus_write_then_one_bus_read(self):
for addr in self.bus_addrs:
data_width = self._get_data_width(addr)
data = randint(0, (2**data_width) - 1)
data_width = self.get_data_width(addr)
data = getrandbits(data_width)
yield from self.write_bus_side(addr, data)
yield from self.verify_bus_side(addr, data)
yield from self.verify_bus_side(addr)
def multi_bus_writes_then_multi_bus_reads(self):
# write-write-write then read-read-read
for addr in jumble(self.bus_addrs):
data_width = self._get_data_width(addr)
data = randint(0, (2**data_width) - 1)
data_width = self.get_data_width(addr)
data = getrandbits(data_width)
self.model[addr] = data
yield from self.write_bus_side(addr, data)
for addr in jumble(self.bus_addrs):
yield from self.verify_bus_side(addr, self.model[addr])
yield from self.verify_bus_side(addr)
def rand_bus_writes_rand_bus_reads(self):
# random reads and writes in random orders
@ -76,43 +76,35 @@ class MemoryCoreTests:
operation = choice(["read", "write"])
if operation == "read":
yield from self.verify_bus_side(addr, self.model[addr])
yield from self.verify_bus_side(addr)
elif operation == "write":
data_width = self._get_data_width(addr)
data = randint(0, (2**data_width) - 1)
self.model[addr] = data
data_width = self.get_data_width(addr)
data = getrandbits(data_width)
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)
data = getrandbits(self.width)
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):
for i in range(self.n_mems):
bus_addr = self.base_addr + user_addr + (i * self.depth)
yield from self.verify_bus_side(bus_addr, word)
yield from self.verify_bus_side(bus_addr)
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)
data = getrandbits(self.width)
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])
yield from self.verify_bus_side(bus_addr)
def rand_user_writes_rand_bus_reads(self):
# random reads and writes in random orders
@ -128,23 +120,18 @@ class MemoryCoreTests:
# 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])
yield from self.verify_bus_side(bus_addr)
# write to user side
elif operation == "write":
data = randint(0, (2**self.width) - 1)
data = getrandbits(self.width)
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 one_bus_write_then_one_user_read(self):
for user_addr in self.user_addrs:
# Try and set the value at the user address to a given value,
# by writing to the appropriate memory locaitons on the bus side
data = randint(0, (2**self.width) - 1)
data = getrandbits(self.width)
words = value_to_words(data, self.n_mems)
@ -152,25 +139,18 @@ class MemoryCoreTests:
bus_addr = self.base_addr + user_addr + (i * self.depth)
yield from self.write_bus_side(bus_addr, word)
yield from self.verify_user_side(user_addr, data)
yield from self.verify_user_side(user_addr)
def multi_bus_write_then_multi_user_reads(self):
# write-write-write then read-read-read
for bus_addr in jumble(self.bus_addrs):
data_width = self._get_data_width(bus_addr)
data = randint(0, (2**data_width) - 1)
data_width = self.get_data_width(bus_addr)
data = getrandbits(data_width)
self.model[bus_addr] = data
yield from self.write_bus_side(bus_addr, data)
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)
]
value = words_to_value([self.model[addr] for addr in bus_addrs])
yield from self.verify_user_side(user_addr, value)
yield from self.verify_user_side(user_addr)
def rand_bus_writes_rand_user_reads(self):
for _ in range(5 * self.depth):
@ -179,41 +159,31 @@ class MemoryCoreTests:
# write random data to random bus address
if operation == "write":
bus_addr = randint(self.base_addr, self.max_addr - 1)
data_width = self._get_data_width(bus_addr)
data = randint(0, (2**data_width) - 1)
self.model[bus_addr] = data
data_width = self.get_data_width(bus_addr)
data = getrandbits(data_width)
yield from self.write_bus_side(bus_addr, data)
# read from random user_addr
if operation == "read":
user_addr = randint(0, self.depth - 1)
bus_addrs = [
self.base_addr + user_addr + (i * self.depth)
for i in range(self.n_mems)
]
value = words_to_value([self.model[addr] for addr in bus_addrs])
yield from self.verify_user_side(user_addr, value)
yield from self.verify_user_side(user_addr)
def one_user_write_then_one_user_read(self):
for addr in self.user_addrs:
data = randint(0, (2**self.width) - 1)
data = getrandbits(self.width)
yield from self.write_user_side(addr, data)
yield from self.verify_user_side(addr, data)
yield from self.verify_user_side(addr)
def multi_user_write_then_multi_user_read(self):
# write-write-write then read-read-read
self.foo = {}
for user_addr in jumble(self.user_addrs):
data = randint(0, (2**self.width) - 1)
self.foo[user_addr] = data
data = getrandbits(self.width)
yield from self.write_user_side(user_addr, data)
for user_addr in jumble(self.user_addrs):
yield from self.verify_user_side(user_addr, self.foo[user_addr])
yield from self.verify_user_side(user_addr)
def rand_user_write_rand_user_read(self):
# random reads and writes in random orders
@ -222,14 +192,13 @@ class MemoryCoreTests:
operation = choice(["read", "write"])
if operation == "read":
yield from self.verify_user_side(user_addr, self.foo[user_addr])
yield from self.verify_user_side(user_addr)
elif operation == "write":
data = randint(0, (2**self.width) - 1)
self.foo[user_addr] = data
data = getrandbits(self.width)
yield from self.write_user_side(user_addr, data)
def _get_data_width(self, addr):
def get_data_width(self, addr):
# 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
@ -240,17 +209,29 @@ class MemoryCoreTests:
else:
return self.width % 16
def verify_bus_side(self, addr, expected_data):
yield from verify_register(self.mem_core, addr, expected_data)
def verify_bus_side(self, addr):
yield from verify_register(self.mem_core, addr, self.model[addr])
for _ in range(4):
yield
def write_bus_side(self, addr, data):
self.model[addr] = data
yield from write_register(self.mem_core, addr, data)
for _ in range(4):
yield
def verify_user_side(self, addr, expected_data):
def verify_user_side(self, addr):
# Determine the expected value on the user side by looking
# up the appropriate bus addresses in the model
# Convert to bus addresses:
bus_words = []
for i in range(self.n_mems):
bus_addr = self.base_addr + addr + (i * self.depth)
bus_words.append(self.model[bus_addr])
expected_data = words_to_value(bus_words)
yield self.mem_core.user_addr.eq(addr)
yield
yield
@ -262,6 +243,12 @@ class MemoryCoreTests:
)
def write_user_side(self, 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 + addr + (i * self.depth)
self.model[bus_addr] = word
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)
@ -294,11 +281,12 @@ def test_bidirectional():
test_bidirectional_testbench()
def test_bidirectional_random():
mem_core = MemoryCore(
mode="bidirectional",
width=randint(0, 128),
depth=randint(0, 2048),
depth=randint(0, 1024),
base_addr=randint(0, 32678),
interface=None,
)
@ -336,11 +324,12 @@ def test_fpga_to_host():
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, 2048),
depth=randint(0, 1024),
base_addr=randint(0, 32678),
interface=None,
)
@ -373,11 +362,12 @@ def test_host_to_fpga():
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, 2048),
depth=randint(0, 1024),
base_addr=randint(0, 32678),
interface=None,
)