150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
from amaranth import *
|
|
from i2c import *
|
|
from amlib.io.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())
|
|
|
|
self.start_latch = Signal()
|
|
self.clear_start = Signal()
|
|
|
|
|
|
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)
|
|
# Always ACK
|
|
m.d.comb += self.i2c_target.ack_o.eq(1)
|
|
|
|
with m.If(self.i2c_target.start):
|
|
m.d.sync += self.start_latch.eq(self.i2c_target.start)
|
|
|
|
with m.If(self.clear_start):
|
|
m.d.sync += self.start_latch.eq(0)
|
|
|
|
return m
|
|
|
|
|
|
class TestCSROperation(BaseTestClass):
|
|
def setUp(self):
|
|
self.harness = TestHarness()
|
|
|
|
|
|
# NOTE So ideally there are more test cases... but the initiator itself is well tested,
|
|
# and we only really need it to work for a limited set of use cases, so exhaustive testing
|
|
# isn't a huge deal. As well, we can cover all valid uses of the signals with one test.
|
|
@provide_testcase_name
|
|
def test_operation(self, test_name):
|
|
def test():
|
|
#send start (and set ACK)
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 + (1 << 4) + (1 << 5))
|
|
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy, require_edge=True)
|
|
|
|
# Set data
|
|
yield from self._write_csr(self.harness.uut.bus, 2, 0xAA)
|
|
# Write data
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 << 2)
|
|
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# First byte has been written
|
|
did_start = yield self.harness.start_latch
|
|
self.assertTrue(did_start)
|
|
|
|
did_ack = yield self.harness.uut._initiator.ack_o
|
|
self.assertTrue(did_ack)
|
|
|
|
# Write data again
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 << 2)
|
|
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# Repeated start
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1)
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# Write read thing
|
|
yield from self._write_csr(self.harness.uut.bus, 2, 0xAB) # Set R/W bit for a read
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 << 2)
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# Read
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 << 3)
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# Stop
|
|
yield from self._write_csr(self.harness.uut.bus, 0, 1 << 1)
|
|
yield from self._wait_for_signal(self.harness.uut._initiator.busy)
|
|
|
|
# I just feel weird seeing it cut out *right* at the end
|
|
for i in range(500):
|
|
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
|
|
|