mirror of https://github.com/m-labs/artiq.git
drtio: add repeater (WIP, write only)
This commit is contained in:
parent
6057cb797c
commit
078c862618
|
@ -1,2 +1,2 @@
|
||||||
from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster
|
from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster, DRTIORepeater
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from artiq.gateware.drtio.rx_synchronizer import GenericRXSynchronizer
|
||||||
|
|
||||||
__all__ = ["ChannelInterface", "TransceiverInterface",
|
__all__ = ["ChannelInterface", "TransceiverInterface",
|
||||||
"SyncRTIO",
|
"SyncRTIO",
|
||||||
"DRTIOSatellite", "DRTIOMaster"]
|
"DRTIOSatellite", "DRTIOMaster", "DRTIORepeater"]
|
||||||
|
|
||||||
|
|
||||||
class ChannelInterface:
|
class ChannelInterface:
|
||||||
|
@ -180,3 +180,25 @@ class DRTIOMaster(Module):
|
||||||
@property
|
@property
|
||||||
def cri(self):
|
def cri(self):
|
||||||
return self.rt_controller.cri
|
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
|
||||||
|
|
|
@ -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")
|
||||||
|
)
|
||||||
|
)
|
|
@ -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
|
|
@ -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)
|
|
@ -3,91 +3,23 @@ from types import SimpleNamespace
|
||||||
|
|
||||||
from migen import *
|
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
|
from artiq.gateware.drtio.rt_packet_satellite import RTPacketSatellite
|
||||||
|
|
||||||
|
|
||||||
class PacketInterface:
|
def create_dut(nwords):
|
||||||
def __init__(self, direction, ws):
|
pt = PacketInterface("m2s", nwords*8)
|
||||||
if direction == "m2s":
|
pr = PacketInterface("s2m", nwords*8)
|
||||||
self.plm = get_m2s_layouts(ws)
|
dut = RTPacketSatellite(SimpleNamespace(
|
||||||
elif direction == "s2m":
|
rx_rt_frame=pt.frame, rx_rt_data=pt.data,
|
||||||
self.plm = get_s2m_layouts(ws)
|
tx_rt_frame=pr.frame, tx_rt_data=pr.data))
|
||||||
else:
|
return pt, pr, dut
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class TestSatellite(unittest.TestCase):
|
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):
|
def test_echo(self):
|
||||||
for nwords in range(1, 8):
|
for nwords in range(1, 8):
|
||||||
pt, pr, dut = self.create_dut(nwords)
|
pt, pr, dut = create_dut(nwords)
|
||||||
completed = False
|
completed = False
|
||||||
def send():
|
def send():
|
||||||
yield from pt.send("echo_request")
|
yield from pt.send("echo_request")
|
||||||
|
@ -102,7 +34,7 @@ class TestSatellite(unittest.TestCase):
|
||||||
|
|
||||||
def test_set_time(self):
|
def test_set_time(self):
|
||||||
for nwords in range(1, 8):
|
for nwords in range(1, 8):
|
||||||
pt, _, dut = self.create_dut(nwords)
|
pt, _, dut = create_dut(nwords)
|
||||||
tx_times = [0x12345678aabbccdd, 0x0102030405060708,
|
tx_times = [0x12345678aabbccdd, 0x0102030405060708,
|
||||||
0xaabbccddeeff1122]
|
0xaabbccddeeff1122]
|
||||||
def send():
|
def send():
|
||||||
|
|
Loading…
Reference in New Issue