diff --git a/src/manta/utils.py b/src/manta/utils.py index 7a75ac9..7e9ace4 100644 --- a/src/manta/utils.py +++ b/src/manta/utils.py @@ -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 diff --git a/test/test_mem_core_hw.py b/test/test_mem_core_hw.py index 185eff1..7e801ac 100644 --- a/test/test_mem_core_hw.py +++ b/test/test_mem_core_hw.py @@ -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) diff --git a/test/test_mem_core_sim.py b/test/test_mem_core_sim.py index 222827c..0a362fd 100644 --- a/test/test_mem_core_sim.py +++ b/test/test_mem_core_sim.py @@ -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(): diff --git a/test/test_uart_rx_sim.py b/test/test_uart_rx_sim.py index 23c9f43..111ce09 100644 --- a/test/test_uart_rx_sim.py +++ b/test/test_uart_rx_sim.py @@ -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) diff --git a/test/test_uart_tx_sim.py b/test/test_uart_tx_sim.py index 63bd373..1176cfc 100644 --- a/test/test_uart_tx_sim.py +++ b/test/test_uart_tx_sim.py @@ -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)