From 5d5a50042f0da70ad93b7a446c4a296ae766b745 Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Wed, 6 Mar 2024 01:12:36 -0800 Subject: [PATCH] make model tracking automatic in memory core tests --- src/manta/ethernet/__init__.py | 4 +- src/manta/utils.py | 30 +++---- test/test_io_core_hw.py | 4 +- test/test_io_core_sim.py | 8 +- test/test_mem_core_hw.py | 4 +- test/test_mem_core_sim.py | 152 +++++++++++++++------------------ 6 files changed, 96 insertions(+), 106 deletions(-) diff --git a/src/manta/ethernet/__init__.py b/src/manta/ethernet/__init__.py index dc5ca69..4b8bbb8 100644 --- a/src/manta/ethernet/__init__.py +++ b/src/manta/ethernet/__init__.py @@ -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"]) diff --git a/src/manta/utils.py b/src/manta/utils.py index fdee740..a10af95 100644 --- a/src/manta/utils.py +++ b/src/manta/utils.py @@ -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`. diff --git a/test/test_io_core_hw.py b/test/test_io_core_hw.py index 6715728..d40aada 100644 --- a/test/test_io_core_hw.py +++ b/test/test_io_core_hw.py @@ -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) diff --git a/test/test_io_core_sim.py b/test/test_io_core_sim.py index 4edbd37..d99a72a 100644 --- a/test/test_io_core_sim.py +++ b/test/test_io_core_sim.py @@ -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 diff --git a/test/test_mem_core_hw.py b/test/test_mem_core_hw.py index 27c1208..9cb73f3 100644 --- a/test/test_mem_core_hw.py +++ b/test/test_mem_core_hw.py @@ -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) diff --git a/test/test_mem_core_sim.py b/test/test_mem_core_sim.py index 29bd73d..0c4fc32 100644 --- a/test/test_mem_core_sim.py +++ b/test/test_mem_core_sim.py @@ -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, )