From 078c862618d2c13fb25dca1203e140de4276cb8d Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 1 Sep 2018 21:07:55 +0800 Subject: [PATCH] drtio: add repeater (WIP, write only) --- artiq/gateware/drtio/__init__.py | 2 +- artiq/gateware/drtio/core.py | 24 ++++- artiq/gateware/drtio/rt_packet_repeater.py | 92 +++++++++++++++++++ artiq/gateware/test/drtio/packet_interface.py | 72 +++++++++++++++ .../test/drtio/test_rt_packet_repeater.py | 62 +++++++++++++ .../test/drtio/test_rt_packet_satellite.py | 88 ++---------------- 6 files changed, 260 insertions(+), 80 deletions(-) create mode 100644 artiq/gateware/drtio/rt_packet_repeater.py create mode 100644 artiq/gateware/test/drtio/packet_interface.py create mode 100644 artiq/gateware/test/drtio/test_rt_packet_repeater.py diff --git a/artiq/gateware/drtio/__init__.py b/artiq/gateware/drtio/__init__.py index 7b83c1934..76cb979c7 100644 --- a/artiq/gateware/drtio/__init__.py +++ b/artiq/gateware/drtio/__init__.py @@ -1,2 +1,2 @@ -from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster +from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster, DRTIORepeater diff --git a/artiq/gateware/drtio/core.py b/artiq/gateware/drtio/core.py index e4f69d1e5..209a3b58d 100644 --- a/artiq/gateware/drtio/core.py +++ b/artiq/gateware/drtio/core.py @@ -16,7 +16,7 @@ from artiq.gateware.drtio.rx_synchronizer import GenericRXSynchronizer __all__ = ["ChannelInterface", "TransceiverInterface", "SyncRTIO", - "DRTIOSatellite", "DRTIOMaster"] + "DRTIOSatellite", "DRTIOMaster", "DRTIORepeater"] class ChannelInterface: @@ -180,3 +180,25 @@ class DRTIOMaster(Module): @property def cri(self): return self.rt_controller.cri + + +class DRTIORepeater(Module): + def __init__(self, chanif): + self.submodules.link_layer = link_layer.LinkLayer( + chanif.encoder, chanif.decoders) + self.comb += self.link_layer.rx_ready.eq(chanif.rx_ready) + + self.submodules.link_stats = link_layer.LinkLayerStats(self.link_layer, "rtio_rx") + self.submodules.rt_packet = rt_packet_repeater.RTPacketRepeater(self.link_layer) + + self.submodules.aux_controller = aux_controller.AuxController( + self.link_layer) + + def get_csrs(self): + return (self.link_layer.get_csrs() + + self.link_stats.get_csrs() + + self.aux_controller.get_csrs()) + + @property + def cri(self): + return self.rt_packet.cri diff --git a/artiq/gateware/drtio/rt_packet_repeater.py b/artiq/gateware/drtio/rt_packet_repeater.py new file mode 100644 index 000000000..0eb9edfb3 --- /dev/null +++ b/artiq/gateware/drtio/rt_packet_repeater.py @@ -0,0 +1,92 @@ +from migen import * +from migen.genlib.fsm import * + +from artiq.gateware.rtio import cri +from artiq.gateware.drtio.rt_serializer import * + + +class RTPacketRepeater(Module): + def __init__(self, link_layer): + self.cri = cri.Interface() + + # RX/TX datapath + assert len(link_layer.tx_rt_data) == len(link_layer.rx_rt_data) + assert len(link_layer.tx_rt_data) % 8 == 0 + ws = len(link_layer.tx_rt_data) + tx_plm = get_m2s_layouts(ws) + tx_dp = ClockDomainsRenamer("rtio")(TransmitDatapath( + link_layer.tx_rt_frame, link_layer.tx_rt_data, tx_plm)) + self.submodules += tx_dp + rx_plm = get_s2m_layouts(ws) + rx_dp = ClockDomainsRenamer("rtio_rx")(ReceiveDatapath( + link_layer.rx_rt_frame, link_layer.rx_rt_data, rx_plm)) + self.submodules += rx_dp + + # Write buffer and extra data count + wb_timestamp = Signal(64) + wb_channel = Signal(16) + wb_address = Signal(16) + wb_data = Signal(512) + self.sync.rtio += If(self.cri.cmd == cri.commands["write"], + wb_timestamp.eq(self.cri.timestamp), + wb_channel.eq(self.cri.chan_sel), + wb_address.eq(self.cri.o_address), + wb_data.eq(self.cri.o_data)) + + wb_extra_data_cnt = Signal(8) + short_data_len = tx_plm.field_length("write", "short_data") + wb_extra_data_a = Signal(512) + self.comb += wb_extra_data_a.eq(self.cri.o_data[short_data_len:]) + for i in range(512//ws): + self.sync.rtio += If(self.cri.cmd == cri.commands["write"], + If(wb_extra_data_a[ws*i:ws*(i+1)] != 0, wb_extra_data_cnt.eq(i+1))) + + wb_extra_data = Signal(512) + self.sync.rtio += If(self.cri.cmd == cri.commands["write"], + wb_extra_data.eq(wb_extra_data_a)) + + extra_data_ce = Signal() + extra_data_last = Signal() + extra_data_counter = Signal(max=512//ws+1) + self.comb += [ + Case(extra_data_counter, + {i+1: tx_dp.raw_data.eq(wb_extra_data[i*ws:(i+1)*ws]) + for i in range(512//ws)}), + extra_data_last.eq(extra_data_counter == wb_extra_data_cnt) + ] + self.sync.rtio += \ + If(extra_data_ce, + extra_data_counter.eq(extra_data_counter + 1), + ).Else( + extra_data_counter.eq(1) + ) + + # TX FSM + tx_fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="IDLE")) + self.submodules += tx_fsm + + tx_fsm.act("IDLE", + If(self.cri.cmd == cri.commands["write"], NextState("WRITE")) + ) + tx_fsm.act("WRITE", + tx_dp.send("write", + timestamp=wb_timestamp, + channel=wb_channel, + address=wb_address, + extra_data_cnt=wb_extra_data_cnt, + short_data=wb_data[:short_data_len]), + If(tx_dp.packet_last, + If(wb_extra_data_cnt == 0, + NextState("IDLE") + ).Else( + NextState("WRITE_EXTRA") + ) + ) + ) + tx_fsm.act("WRITE_EXTRA", + tx_dp.raw_stb.eq(1), + extra_data_ce.eq(1), + If(extra_data_last, + NextState("IDLE") + ) + ) diff --git a/artiq/gateware/test/drtio/packet_interface.py b/artiq/gateware/test/drtio/packet_interface.py new file mode 100644 index 000000000..c6d2979a2 --- /dev/null +++ b/artiq/gateware/test/drtio/packet_interface.py @@ -0,0 +1,72 @@ +from migen import * + +from artiq.gateware.drtio.rt_serializer import * + + +class PacketInterface: + def __init__(self, direction, ws): + if direction == "m2s": + self.plm = get_m2s_layouts(ws) + elif direction == "s2m": + self.plm = get_s2m_layouts(ws) + else: + raise ValueError + self.frame = Signal() + self.data = Signal(ws) + + def send(self, ty, **kwargs): + idx = 8 + value = self.plm.types[ty] + for field_name, field_size in self.plm.layouts[ty][1:]: + try: + fvalue = kwargs[field_name] + del kwargs[field_name] + except KeyError: + fvalue = 0 + value = value | (fvalue << idx) + idx += field_size + if kwargs: + raise ValueError + + ws = len(self.data) + yield self.frame.eq(1) + for i in range(idx//ws): + yield self.data.eq(value) + value >>= ws + yield + yield self.frame.eq(0) + yield + + @passive + def receive(self, callback): + previous_frame = 0 + frame_words = [] + while True: + frame = yield self.frame + if frame: + frame_words.append((yield self.data)) + if previous_frame and not frame: + packet_type = self.plm.type_names[frame_words[0] & 0xff] + packet_nwords = layout_len(self.plm.layouts[packet_type]) \ + //len(self.data) + packet, trailer = frame_words[:packet_nwords], \ + frame_words[packet_nwords:] + + n = 0 + packet_int = 0 + for w in packet: + packet_int |= (w << n) + n += len(self.data) + + field_dict = dict() + idx = 0 + for field_name, field_size in self.plm.layouts[packet_type]: + v = (packet_int >> idx) & (2**field_size - 1) + field_dict[field_name] = v + idx += field_size + + callback(packet_type, field_dict, trailer) + + frame_words = [] + previous_frame = frame + yield diff --git a/artiq/gateware/test/drtio/test_rt_packet_repeater.py b/artiq/gateware/test/drtio/test_rt_packet_repeater.py new file mode 100644 index 000000000..760a7a9ac --- /dev/null +++ b/artiq/gateware/test/drtio/test_rt_packet_repeater.py @@ -0,0 +1,62 @@ +import unittest +from types import SimpleNamespace + +from migen import * + +from artiq.gateware.rtio import cri +from artiq.gateware.test.drtio.packet_interface import PacketInterface +from artiq.gateware.drtio.rt_packet_repeater import RTPacketRepeater + + +def create_dut(nwords): + pt = PacketInterface("s2m", nwords*8) + pr = PacketInterface("m2s", nwords*8) + dut = ClockDomainsRenamer({"rtio": "sys", "rtio_rx": "sys"})( + RTPacketRepeater(SimpleNamespace( + rx_rt_frame=pt.frame, rx_rt_data=pt.data, + tx_rt_frame=pr.frame, tx_rt_data=pr.data))) + return pt, pr, dut + + +class TestRepeater(unittest.TestCase): + def test_output(self): + test_writes = [ + (1, 10, 21, 0x42), + (2, 11, 34, 0x2342), + (3, 12, 83, 0x2345566633), + (4, 13, 25, 0x98da14959a19498ae1), + (5, 14, 75, 0x3998a1883ae14f828ae24958ea2479) + ] + + for nwords in range(1, 8): + pt, pr, dut = create_dut(nwords) + + def send(): + for channel, timestamp, address, data in test_writes: + yield dut.cri.chan_sel.eq(channel) + yield dut.cri.timestamp.eq(timestamp) + yield dut.cri.o_address.eq(address) + yield dut.cri.o_data.eq(data) + yield dut.cri.cmd.eq(cri.commands["write"]) + yield + yield dut.cri.cmd.eq(cri.commands["nop"]) + yield + for i in range(30): + yield + for i in range(50): + yield + + short_data_len = pr.plm.field_length("write", "short_data") + + received = [] + def receive(packet_type, field_dict, trailer): + self.assertEqual(packet_type, "write") + self.assertEqual(len(trailer), field_dict["extra_data_cnt"]) + data = field_dict["short_data"] + for n, te in enumerate(trailer): + data |= te << (n*nwords*8 + short_data_len) + received.append((field_dict["channel"], field_dict["timestamp"], + field_dict["address"], data)) + + run_simulation(dut, [send(), pr.receive(receive)]) + self.assertEqual(test_writes, received) diff --git a/artiq/gateware/test/drtio/test_rt_packet_satellite.py b/artiq/gateware/test/drtio/test_rt_packet_satellite.py index 5bbdb44f7..1d934b8df 100644 --- a/artiq/gateware/test/drtio/test_rt_packet_satellite.py +++ b/artiq/gateware/test/drtio/test_rt_packet_satellite.py @@ -3,91 +3,23 @@ from types import SimpleNamespace from migen import * -from artiq.gateware.drtio.rt_serializer import * +from artiq.gateware.test.drtio.packet_interface import PacketInterface from artiq.gateware.drtio.rt_packet_satellite import RTPacketSatellite -class PacketInterface: - def __init__(self, direction, ws): - if direction == "m2s": - self.plm = get_m2s_layouts(ws) - elif direction == "s2m": - self.plm = get_s2m_layouts(ws) - else: - raise ValueError - self.frame = Signal() - self.data = Signal(ws) - - def send(self, ty, **kwargs): - idx = 8 - value = self.plm.types[ty] - for field_name, field_size in self.plm.layouts[ty][1:]: - try: - fvalue = kwargs[field_name] - del kwargs[field_name] - except KeyError: - fvalue = 0 - value = value | (fvalue << idx) - idx += field_size - if kwargs: - raise ValueError - - ws = len(self.data) - yield self.frame.eq(1) - for i in range(idx//ws): - yield self.data.eq(value) - value >>= ws - yield - yield self.frame.eq(0) - yield - - @passive - def receive(self, callback): - previous_frame = 0 - frame_words = [] - while True: - frame = yield self.frame - if frame: - frame_words.append((yield self.data)) - if previous_frame and not frame: - packet_type = self.plm.type_names[frame_words[0] & 0xff] - packet_nwords = layout_len(self.plm.layouts[packet_type]) \ - //len(self.data) - packet, trailer = frame_words[:packet_nwords], \ - frame_words[packet_nwords:] - - n = 0 - packet_int = 0 - for w in packet: - packet_int |= (w << n) - n += len(self.data) - - field_dict = dict() - idx = 0 - for field_name, field_size in self.plm.layouts[packet_type]: - v = (packet_int >> idx) & (2**field_size - 1) - field_dict[field_name] = v - idx += field_size - - callback(packet_type, field_dict, trailer) - - frame_words = [] - previous_frame = frame - yield +def create_dut(nwords): + pt = PacketInterface("m2s", nwords*8) + pr = PacketInterface("s2m", nwords*8) + dut = RTPacketSatellite(SimpleNamespace( + rx_rt_frame=pt.frame, rx_rt_data=pt.data, + tx_rt_frame=pr.frame, tx_rt_data=pr.data)) + return pt, pr, dut class TestSatellite(unittest.TestCase): - def create_dut(self, nwords): - pt = PacketInterface("m2s", nwords*8) - pr = PacketInterface("s2m", nwords*8) - dut = RTPacketSatellite(SimpleNamespace( - rx_rt_frame=pt.frame, rx_rt_data=pt.data, - tx_rt_frame=pr.frame, tx_rt_data=pr.data)) - return pt, pr, dut - def test_echo(self): for nwords in range(1, 8): - pt, pr, dut = self.create_dut(nwords) + pt, pr, dut = create_dut(nwords) completed = False def send(): yield from pt.send("echo_request") @@ -102,7 +34,7 @@ class TestSatellite(unittest.TestCase): def test_set_time(self): for nwords in range(1, 8): - pt, _, dut = self.create_dut(nwords) + pt, _, dut = create_dut(nwords) tx_times = [0x12345678aabbccdd, 0x0102030405060708, 0xaabbccddeeff1122] def send():