""" Set of utilities to build a simple test suite. """ from amaranth import * from amaranth.sim import * from typing import Generator import unittest import os from contextlib import nullcontext class BaseTestClass(unittest.TestCase): """ Base test class that provides a run_test helper function to do all the nice things. """ def _run_test(self, test: Generator, name: str): try: sim = Simulator(self.harness) except NameError: raise NotImplementedError(f"Must define a self.harness module for TestCase {self.__class__.__name__}!") sim.add_clock(100e-9) sim.add_sync_process(test) sim.reset() # Pretty hacky way to pass this info in but does it look like I care? if os.environ.get("TEST_SAVE_VCD"): ctx = sim.write_vcd(f"vcd_out/{name}.vcd") else: ctx = nullcontext() with ctx: sim.run() del sim ######### Random Utilities ######## def _write_csr(self, bus, index, data): yield bus.addr.eq(index) yield bus.w_stb.eq(1) yield bus.w_data.eq(data) yield Tick() yield bus.w_stb.eq(0) yield Tick() def _wait_for_signal(self, signal, polarity=False, require_edge=True, timeout=1000): ready_for_edge = not require_edge # If we don't require edge, we can just ignore while True: timeout -= 1 if timeout == 0: self.fail(f"_wait_for_signal({signal}, {polarity}, {require_edge}, {timeout}, timed out!") read = yield signal if read == polarity: if ready_for_edge: break else: ready_for_edge = True yield Tick() def provide_testcase_name(fn): """Decorator that provides a function with access to its own class and name.""" def wrapper(self): fn(self, f"{self.__class__.__name__}.{fn.__name__}") return wrapper