gateware: put in some testing infrastructure
It's pretty hacky tbh, probably should be improved. But also this will probably scale with the entire project so I don't care.
This commit is contained in:
parent
490b92b1fd
commit
ad3be1f4c7
@ -121,12 +121,14 @@ class I2C(Elaboratable):
|
||||
|
||||
return m
|
||||
|
||||
|
||||
from amaranth.lib.io import pin_layout
|
||||
i2c_layout = [
|
||||
("sda", pin_layout(1, "io")),
|
||||
("scl", pin_layout(1, "io")),
|
||||
]
|
||||
|
||||
|
||||
class I2CBusSimulator(Elaboratable):
|
||||
def __init__(self):
|
||||
self.interfaces = []
|
||||
@ -188,8 +190,35 @@ class TestHarness(Elaboratable):
|
||||
|
||||
return m
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
|
||||
import inspect
|
||||
|
||||
from contextlib import nullcontext
|
||||
|
||||
from typing import Generator
|
||||
|
||||
from tests import BaseTestClass, provide_testcase_name
|
||||
|
||||
class TestCSROperation(BaseTestClass):
|
||||
def setUp(self):
|
||||
self.harness = TestHarness()
|
||||
|
||||
@provide_testcase_name
|
||||
def test_send_byte(self, test_name):
|
||||
def test():
|
||||
yield Tick()
|
||||
|
||||
self._run_test(test, test_name)
|
||||
|
||||
|
||||
# TODO switch to unittest or something
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
def write_csr(bus, index, data):
|
||||
yield bus.addr.eq(index)
|
||||
yield bus.w_stb.eq(1)
|
||||
|
@ -9,10 +9,15 @@ from minerva.core import Minerva
|
||||
|
||||
from typing import List
|
||||
from argparse import ArgumentParser
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
|
||||
from memory import *
|
||||
from led import *
|
||||
|
||||
import i2c
|
||||
import test_i2c
|
||||
|
||||
# To change clock domain of a module:
|
||||
# new_thing = DomainRenamer("new_clock")(MyElaboratable())
|
||||
@ -145,5 +150,11 @@ if __name__ == "__main__":
|
||||
colorlight_i9.Colorlight_i9_Platform().build(SoC(), debug_verilog=args.gen_debug_verilog)
|
||||
|
||||
if args.test:
|
||||
# TODO pass save_vcd arg through
|
||||
run_sim()
|
||||
if args.save_vcd:
|
||||
os.environ.set("TEST_SAVE_VCD")
|
||||
|
||||
# Super hacky... why am I doing this
|
||||
test_modules = [mod for mod in sys.modules if mod.startswith("test_")]
|
||||
for mod in test_modules:
|
||||
unittest.main(module=mod, argv=[sys.argv[0]])
|
||||
|
||||
|
90
gateware/test_i2c.py
Normal file
90
gateware/test_i2c.py
Normal file
@ -0,0 +1,90 @@
|
||||
from amaranth import *
|
||||
from i2c import *
|
||||
from amaranth.lib.io import pin_layout
|
||||
from tests import BaseTestClass, provide_testcase_name
|
||||
|
||||
|
||||
__all__ = ["i2c_layout", "I2CBusSimulator", "TestHarness", "TestCSROperation"]
|
||||
|
||||
|
||||
class TestHarness(Elaboratable):
|
||||
def __init__(self):
|
||||
self.i2c = I2CBusSimulator()
|
||||
|
||||
self.uut = I2C(10_000_000, 100_000, self.i2c.create_interface())
|
||||
self.i2c_target = I2CTarget(self.i2c.create_interface())
|
||||
|
||||
|
||||
def elaborate(self, platform):
|
||||
assert platform is None
|
||||
|
||||
m = Module()
|
||||
|
||||
m.submodules.i2c = self.i2c
|
||||
m.submodules.uut = self.uut
|
||||
m.submodules.i2c_target = self.i2c_target
|
||||
|
||||
m.d.comb += self.i2c_target.address.eq(0xAA >> 1)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class TestCSROperation(BaseTestClass):
|
||||
def setUp(self):
|
||||
self.harness = TestHarness()
|
||||
|
||||
|
||||
@provide_testcase_name
|
||||
def test_send_byte(self, test_name):
|
||||
def test():
|
||||
yield Tick()
|
||||
|
||||
self._run_test(test, test_name)
|
||||
|
||||
i2c_layout = [
|
||||
("sda", pin_layout(1, "io")),
|
||||
("scl", pin_layout(1, "io")),
|
||||
]
|
||||
|
||||
|
||||
class I2CBusSimulator(Elaboratable):
|
||||
def __init__(self):
|
||||
self.interfaces = []
|
||||
self.sda = Signal()
|
||||
self.scl = Signal()
|
||||
|
||||
|
||||
def elaborate(self, target):
|
||||
assert target is None, "This bus simulator should never be used in real hardware!"
|
||||
|
||||
n = len(self.interfaces)
|
||||
|
||||
m = Module()
|
||||
|
||||
m.d.comb += self.sda.eq(1)
|
||||
m.d.comb += self.scl.eq(1)
|
||||
|
||||
# TODO maybe output a bus contention signal?
|
||||
# First interfaces get priority over interfaces added after
|
||||
for i in reversed(range(n)):
|
||||
# Emulate bus drivers
|
||||
with m.If(self.interfaces[i].sda.oe):
|
||||
m.d.comb += self.sda.eq(self.interfaces[i].sda.o)
|
||||
with m.If(self.interfaces[i].scl.oe):
|
||||
m.d.comb += self.scl.eq(self.interfaces[i].scl.o)
|
||||
pass
|
||||
|
||||
# Connect inputs to bus value
|
||||
m.d.comb += [
|
||||
self.interfaces[i].sda.i.eq(self.sda),
|
||||
self.interfaces[i].scl.i.eq(self.scl),
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def create_interface(self) -> Record:
|
||||
new_interface = Record(i2c_layout)
|
||||
self.interfaces.append(new_interface)
|
||||
return new_interface
|
||||
|
42
gateware/tests.py
Normal file
42
gateware/tests.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""
|
||||
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.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.add_sync_process(test)
|
||||
|
||||
|
||||
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
|
Loading…
Reference in New Issue
Block a user