drtio: add repeater (WIP, write only)

This commit is contained in:
Sebastien Bourdeauducq 2018-09-01 21:07:55 +08:00
parent 6057cb797c
commit 078c862618
6 changed files with 260 additions and 80 deletions

View File

@ -1,2 +1,2 @@
from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster
from artiq.gateware.drtio.core import SyncRTIO, DRTIOSatellite, DRTIOMaster, DRTIORepeater

View File

@ -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

View File

@ -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")
)
)

View File

@ -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

View File

@ -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)

View File

@ -3,81 +3,11 @@ 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
class TestSatellite(unittest.TestCase):
def create_dut(self, nwords):
def create_dut(nwords):
pt = PacketInterface("m2s", nwords*8)
pr = PacketInterface("s2m", nwords*8)
dut = RTPacketSatellite(SimpleNamespace(
@ -85,9 +15,11 @@ class TestSatellite(unittest.TestCase):
tx_rt_frame=pr.frame, tx_rt_data=pr.data))
return pt, pr, dut
class TestSatellite(unittest.TestCase):
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():