mirror of https://github.com/m-labs/artiq
313 lines
10 KiB
Python
313 lines
10 KiB
Python
import unittest
|
|
from types import SimpleNamespace
|
|
import random
|
|
|
|
from migen import *
|
|
|
|
from artiq.gateware.drtio import *
|
|
from artiq.gateware.drtio import rt_serializer
|
|
from artiq.gateware import rtio
|
|
from artiq.gateware.rtio import rtlink
|
|
from artiq.gateware.rtio.phy import ttl_simple
|
|
from artiq.coredevice.exceptions import *
|
|
|
|
|
|
class DummyTransceiverPair:
|
|
def __init__(self, nwords):
|
|
a2b_k = [Signal() for _ in range(nwords)]
|
|
a2b_d = [Signal(8) for _ in range(nwords)]
|
|
b2a_k = [Signal() for _ in range(nwords)]
|
|
b2a_d = [Signal(8) for _ in range(nwords)]
|
|
|
|
self.alice = SimpleNamespace(
|
|
encoder=SimpleNamespace(k=a2b_k, d=a2b_d),
|
|
decoders=[SimpleNamespace(k=k, d=d) for k, d in zip(b2a_k, b2a_d)],
|
|
rx_ready=1
|
|
)
|
|
self.bob = SimpleNamespace(
|
|
encoder=SimpleNamespace(k=b2a_k, d=b2a_d),
|
|
decoders=[SimpleNamespace(k=k, d=d) for k, d in zip(a2b_k, a2b_d)],
|
|
rx_ready=1
|
|
)
|
|
|
|
|
|
class DummyRXSynchronizer:
|
|
def resync(self, signal):
|
|
return signal
|
|
|
|
|
|
class SimpleIOPHY(Module):
|
|
def __init__(self, o_width, i_width):
|
|
self.rtlink = rtlink.Interface(
|
|
rtlink.OInterface(o_width),
|
|
rtlink.IInterface(i_width, timestamped=True))
|
|
self.received_data = Signal(o_width)
|
|
self.sync.rio_phy += If(self.rtlink.o.stb,
|
|
self.received_data.eq(self.rtlink.o.data))
|
|
|
|
|
|
class DUT(Module):
|
|
def __init__(self, nwords):
|
|
self.ttl0 = Signal()
|
|
self.ttl1 = Signal()
|
|
self.transceivers = DummyTransceiverPair(nwords)
|
|
|
|
self.submodules.tsc_master = rtio.TSC()
|
|
self.submodules.master = DRTIOMaster(self.tsc_master,
|
|
self.transceivers.alice)
|
|
self.submodules.master_ki = rtio.KernelInitiator(self.tsc_master,
|
|
self.master.cri)
|
|
|
|
rx_synchronizer = DummyRXSynchronizer()
|
|
self.submodules.phy0 = ttl_simple.Output(self.ttl0)
|
|
self.submodules.phy1 = ttl_simple.Output(self.ttl1)
|
|
self.submodules.phy2 = SimpleIOPHY(512, 32) # test wide output data
|
|
rtio_channels = [
|
|
rtio.Channel.from_phy(self.phy0),
|
|
rtio.Channel.from_phy(self.phy1),
|
|
rtio.Channel.from_phy(self.phy2),
|
|
]
|
|
self.submodules.tsc_satellite = rtio.TSC()
|
|
self.submodules.satellite = DRTIOSatellite(
|
|
self.tsc_satellite, self.transceivers.bob, rx_synchronizer)
|
|
self.satellite.reset.storage.reset = 0
|
|
self.satellite.reset.storage_full.reset = 0
|
|
self.satellite.reset_phy.storage.reset = 0
|
|
self.satellite.reset_phy.storage_full.reset = 0
|
|
self.submodules.satellite_rtio = SyncRTIO(
|
|
self.tsc_satellite, rtio_channels, lane_count=4, fifo_depth=8)
|
|
self.comb += [
|
|
self.satellite.cri.connect(self.satellite_rtio.cri),
|
|
self.satellite.async_errors.eq(self.satellite_rtio.async_errors),
|
|
]
|
|
|
|
|
|
class OutputsTestbench:
|
|
def __init__(self):
|
|
self.dut = DUT(2)
|
|
self.now = 0
|
|
|
|
def init(self):
|
|
yield from self.dut.master.rt_controller.csrs.underflow_margin.write(100)
|
|
while not (yield from self.dut.master.link_layer.rx_up.read()):
|
|
yield
|
|
yield from self.get_buffer_space()
|
|
|
|
def get_buffer_space(self):
|
|
csrs = self.dut.master.rt_controller.csrs
|
|
yield from csrs.o_get_buffer_space.write(1)
|
|
yield
|
|
while (yield from csrs.o_wait.read()):
|
|
yield
|
|
r = (yield from csrs.o_dbg_buffer_space.read())
|
|
return r
|
|
|
|
def delay(self, dt):
|
|
self.now += dt
|
|
|
|
def sync(self):
|
|
t = self.now + 15
|
|
while (yield self.dut.tsc_master.full_ts_cri) < t:
|
|
yield
|
|
|
|
def write(self, channel, data):
|
|
kcsrs = self.dut.master_ki
|
|
yield from kcsrs.target.write(channel << 8)
|
|
yield from kcsrs.now_hi.write(self.now >> 32)
|
|
yield from kcsrs.now_lo.write(self.now & 0xffffffff)
|
|
yield from kcsrs.o_data.write(data)
|
|
yield
|
|
status = 1
|
|
wlen = 0
|
|
while status:
|
|
status = yield from kcsrs.o_status.read()
|
|
if status & 0x2:
|
|
raise RTIOUnderflow
|
|
if status & 0x4:
|
|
raise RTIODestinationUnreachable
|
|
yield
|
|
wlen += 1
|
|
return wlen
|
|
|
|
@passive
|
|
def check_ttls(self, ttl_changes):
|
|
cycle = 0
|
|
old_ttls = [0, 0]
|
|
while True:
|
|
ttls = [(yield self.dut.ttl0), (yield self.dut.ttl1)]
|
|
for n, (old_ttl, ttl) in enumerate(zip(old_ttls, ttls)):
|
|
if ttl != old_ttl:
|
|
ttl_changes.append((cycle, n))
|
|
old_ttls = ttls
|
|
yield
|
|
cycle += 1
|
|
|
|
|
|
class TestFullStack(unittest.TestCase):
|
|
clocks = {"sys": 8, "rtio_rx": 8,
|
|
"rio": 8, "rio_phy": 8}
|
|
|
|
def test_pulses(self):
|
|
tb = OutputsTestbench()
|
|
ttl_changes = []
|
|
correct_ttl_changes = [
|
|
(208, 0),
|
|
(213, 0),
|
|
(213, 1),
|
|
(219, 1),
|
|
]
|
|
|
|
def test():
|
|
yield from tb.init()
|
|
tb.delay(200)
|
|
yield from tb.write(0, 1)
|
|
tb.delay(5)
|
|
yield from tb.write(0, 0)
|
|
yield from tb.write(1, 1)
|
|
tb.delay(6)
|
|
yield from tb.write(1, 0)
|
|
yield from tb.sync()
|
|
|
|
run_simulation(tb.dut,
|
|
{"sys": [test(), tb.check_ttls(ttl_changes)]}, self.clocks)
|
|
self.assertEqual(ttl_changes, correct_ttl_changes)
|
|
|
|
def test_underflow(self):
|
|
tb = OutputsTestbench()
|
|
|
|
def test():
|
|
yield from tb.init()
|
|
with self.assertRaises(RTIOUnderflow):
|
|
yield from tb.write(0, 0)
|
|
|
|
run_simulation(tb.dut, {"sys": test()}, self.clocks)
|
|
|
|
def test_large_data(self):
|
|
tb = OutputsTestbench()
|
|
|
|
def test():
|
|
yield from tb.init()
|
|
correct_large_data = random.Random(0).randrange(2**512-1)
|
|
self.assertNotEqual((yield tb.dut.phy2.received_data), correct_large_data)
|
|
tb.delay(200)
|
|
yield from tb.write(2, correct_large_data)
|
|
yield from tb.sync()
|
|
self.assertEqual((yield tb.dut.phy2.received_data), correct_large_data)
|
|
|
|
run_simulation(tb.dut, {"sys": test()}, self.clocks)
|
|
|
|
def test_buffer_space(self):
|
|
tb = OutputsTestbench()
|
|
ttl_changes = []
|
|
correct_ttl_changes = [(258 + 40*i, 0) for i in range(10)]
|
|
|
|
def test():
|
|
yield from tb.init()
|
|
tb.delay(250)
|
|
max_wlen = 0
|
|
for i in range(10):
|
|
wlen = yield from tb.write(0, (i + 1) % 2)
|
|
max_wlen = max(max_wlen, wlen)
|
|
tb.delay(40)
|
|
# check that some writes caused buffer space requests
|
|
self.assertGreater(max_wlen, 5)
|
|
yield from tb.sync()
|
|
|
|
run_simulation(tb.dut,
|
|
{"sys": [test(), tb.check_ttls(ttl_changes)]}, self.clocks)
|
|
self.assertEqual(ttl_changes, correct_ttl_changes)
|
|
|
|
def test_write_underflow(self):
|
|
tb = OutputsTestbench()
|
|
|
|
def test():
|
|
saterr = tb.dut.satellite.rt_errors
|
|
csrs = tb.dut.master.rt_controller.csrs
|
|
yield from tb.init()
|
|
errors = yield from saterr.protocol_error.read()
|
|
self.assertEqual(errors, 0)
|
|
yield from csrs.underflow_margin.write(0)
|
|
tb.delay(80)
|
|
yield from tb.write(42, 1)
|
|
for i in range(21):
|
|
yield
|
|
errors = yield from saterr.protocol_error.read()
|
|
underflow_channel = yield from saterr.underflow_channel.read()
|
|
underflow_timestamp_event = yield from saterr.underflow_timestamp_event.read()
|
|
self.assertEqual(errors, 8) # write underflow
|
|
self.assertEqual(underflow_channel, 42)
|
|
self.assertEqual(underflow_timestamp_event, 80)
|
|
yield from saterr.protocol_error.write(errors)
|
|
yield
|
|
errors = yield from saterr.protocol_error.read()
|
|
self.assertEqual(errors, 0)
|
|
|
|
run_simulation(tb.dut, {"sys": test()}, self.clocks)
|
|
|
|
def test_inputs(self):
|
|
dut = DUT(2)
|
|
kcsrs = dut.master_ki
|
|
|
|
def get_input(timeout):
|
|
yield from kcsrs.target.write(2 << 8)
|
|
yield from kcsrs.i_timeout.write(10)
|
|
yield
|
|
status = yield from kcsrs.i_status.read()
|
|
while status & 0x4:
|
|
yield
|
|
status = yield from kcsrs.i_status.read()
|
|
if status & 0x1:
|
|
return "timeout"
|
|
if status & 0x2:
|
|
return "overflow"
|
|
if status & 0x8:
|
|
return "destination unreachable"
|
|
return ((yield from kcsrs.i_data.read()),
|
|
(yield from kcsrs.i_timestamp.read()))
|
|
|
|
def test():
|
|
while not (yield from dut.master.link_layer.rx_up.read()):
|
|
yield
|
|
|
|
i1 = yield from get_input(10)
|
|
i2 = yield from get_input(20)
|
|
self.assertEqual(i1, (0x600d1dea, 6))
|
|
self.assertEqual(i2, "timeout")
|
|
|
|
def generate_input():
|
|
for i in range(5):
|
|
yield
|
|
yield dut.phy2.rtlink.i.data.eq(0x600d1dea)
|
|
yield dut.phy2.rtlink.i.stb.eq(1)
|
|
yield
|
|
yield dut.phy2.rtlink.i.data.eq(0)
|
|
yield dut.phy2.rtlink.i.stb.eq(0)
|
|
|
|
run_simulation(dut,
|
|
{"sys": [test(), generate_input()]}, self.clocks)
|
|
|
|
def test_echo(self):
|
|
dut = DUT(2)
|
|
packet = dut.master.rt_packet
|
|
|
|
def test():
|
|
while not (yield from dut.master.link_layer.rx_up.read()):
|
|
yield
|
|
|
|
self.assertEqual((yield dut.master.rt_packet.packet_cnt_tx), 0)
|
|
self.assertEqual((yield dut.master.rt_packet.packet_cnt_rx), 0)
|
|
|
|
yield dut.master.rt_packet.echo_stb.eq(1)
|
|
yield
|
|
while not (yield dut.master.rt_packet.echo_ack):
|
|
yield
|
|
yield dut.master.rt_packet.echo_stb.eq(0)
|
|
|
|
for i in range(17):
|
|
yield
|
|
|
|
self.assertEqual((yield dut.master.rt_packet.packet_cnt_tx), 1)
|
|
self.assertEqual((yield dut.master.rt_packet.packet_cnt_rx), 1)
|
|
|
|
run_simulation(dut, test(), self.clocks)
|