forked from M-Labs/artiq
Merge branch 'drtio'
This commit is contained in:
commit
88ad054ab6
|
@ -0,0 +1,148 @@
|
||||||
|
# This is an example device database that needs to be adapted to your setup.
|
||||||
|
# The RTIO channel numbers here are for NIST CLOCK on KC705.
|
||||||
|
# The list of devices here is not exhaustive.
|
||||||
|
|
||||||
|
{
|
||||||
|
"comm": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.comm_tcp",
|
||||||
|
"class": "Comm",
|
||||||
|
"arguments": {"host": "kc705.lab.m-labs.hk"}
|
||||||
|
},
|
||||||
|
"core": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.core",
|
||||||
|
"class": "Core",
|
||||||
|
"arguments": {"ref_period": 2e-9}
|
||||||
|
},
|
||||||
|
"core_cache": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.cache",
|
||||||
|
"class": "CoreCache"
|
||||||
|
},
|
||||||
|
|
||||||
|
"rled0": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0},
|
||||||
|
},
|
||||||
|
"rled1": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 1},
|
||||||
|
},
|
||||||
|
"rled2": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 2},
|
||||||
|
},
|
||||||
|
"rled3": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 3},
|
||||||
|
},
|
||||||
|
"rled4": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 4},
|
||||||
|
},
|
||||||
|
"rled5": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 5},
|
||||||
|
},
|
||||||
|
"rled6": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 6},
|
||||||
|
},
|
||||||
|
"rled7": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 7},
|
||||||
|
},
|
||||||
|
|
||||||
|
"rsmap": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 8}
|
||||||
|
},
|
||||||
|
"rsman": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 9}
|
||||||
|
},
|
||||||
|
|
||||||
|
"led0": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010000},
|
||||||
|
},
|
||||||
|
"led1": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010001},
|
||||||
|
},
|
||||||
|
"led2": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010002},
|
||||||
|
},
|
||||||
|
"led3": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010003},
|
||||||
|
},
|
||||||
|
"led4": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010004},
|
||||||
|
},
|
||||||
|
"led5": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010005},
|
||||||
|
},
|
||||||
|
"led6": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010006},
|
||||||
|
},
|
||||||
|
"led7": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010007},
|
||||||
|
},
|
||||||
|
|
||||||
|
"smap": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010008}
|
||||||
|
},
|
||||||
|
"sman": {
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.ttl",
|
||||||
|
"class": "TTLOut",
|
||||||
|
"arguments": {"channel": 0x010009}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
from artiq.experiment import *
|
||||||
|
|
||||||
|
|
||||||
|
class BlinkForever(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
self.setattr_device("core")
|
||||||
|
self.rleds = [self.get_device("rled" + str(i)) for i in range(8)]
|
||||||
|
self.leds = [self.get_device("led" + str(i)) for i in range(8)]
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
self.core.reset()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
with parallel:
|
||||||
|
for led in self.leds:
|
||||||
|
led.pulse(250*ms)
|
||||||
|
for led in self.rleds:
|
||||||
|
led.pulse(250*ms)
|
||||||
|
t = now_mu()
|
||||||
|
for led in self.leds:
|
||||||
|
at_mu(t)
|
||||||
|
led.pulse(500*ms)
|
||||||
|
for led in self.rleds:
|
||||||
|
at_mu(t)
|
||||||
|
led.pulse(500*ms)
|
||||||
|
delay(250*ms)
|
|
@ -0,0 +1,25 @@
|
||||||
|
from artiq.experiment import *
|
||||||
|
|
||||||
|
|
||||||
|
class PulseRate(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
self.setattr_device("core")
|
||||||
|
self.setattr_device("rsmap")
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
self.core.reset()
|
||||||
|
|
||||||
|
dt = self.core.seconds_to_mu(300*ns)
|
||||||
|
while True:
|
||||||
|
for i in range(10000):
|
||||||
|
try:
|
||||||
|
self.rsmap.pulse_mu(dt)
|
||||||
|
delay_mu(dt)
|
||||||
|
except RTIOUnderflow:
|
||||||
|
dt += 1
|
||||||
|
self.core.break_realtime()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(self.core.mu_to_seconds(dt))
|
||||||
|
return
|
|
@ -0,0 +1,2 @@
|
||||||
|
from artiq.gateware.drtio.core import DRTIOSatellite, DRTIOMaster
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.fhdl.simplify import FullMemoryWE
|
||||||
|
from migen.genlib.cdc import MultiReg, PulseSynchronizer
|
||||||
|
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
from misoc.interconnect import stream
|
||||||
|
from misoc.interconnect import wishbone
|
||||||
|
|
||||||
|
|
||||||
|
max_packet = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class Transmitter(Module, AutoCSR):
|
||||||
|
def __init__(self, link_layer, min_mem_dw):
|
||||||
|
ll_dw = len(link_layer.tx_aux_data)
|
||||||
|
mem_dw = max(min_mem_dw, ll_dw)
|
||||||
|
|
||||||
|
self.aux_tx_length = CSRStorage(bits_for(max_packet),
|
||||||
|
alignment_bits=log2_int(mem_dw//8))
|
||||||
|
self.aux_tx = CSR()
|
||||||
|
self.specials.mem = Memory(mem_dw, max_packet//(mem_dw//8))
|
||||||
|
|
||||||
|
converter = stream.Converter(mem_dw, ll_dw)
|
||||||
|
self.submodules += converter
|
||||||
|
|
||||||
|
# when continuously fed, the Converter outputs data continuously
|
||||||
|
self.comb += [
|
||||||
|
converter.source.ack.eq(link_layer.tx_aux_ack),
|
||||||
|
link_layer.tx_aux_frame.eq(converter.source.stb),
|
||||||
|
link_layer.tx_aux_data.eq(converter.source.data)
|
||||||
|
]
|
||||||
|
|
||||||
|
seen_eop_rst = Signal()
|
||||||
|
frame_r = Signal()
|
||||||
|
seen_eop = Signal()
|
||||||
|
self.sync.rtio += [
|
||||||
|
If(link_layer.tx_aux_ack,
|
||||||
|
frame_r.eq(link_layer.tx_aux_frame),
|
||||||
|
If(frame_r & ~link_layer.tx_aux_frame, seen_eop.eq(1))
|
||||||
|
),
|
||||||
|
If(seen_eop_rst, seen_eop.eq(0))
|
||||||
|
]
|
||||||
|
|
||||||
|
mem_port = self.mem.get_port(clock_domain="rtio")
|
||||||
|
self.specials += mem_port
|
||||||
|
|
||||||
|
self.aux_tx_length.storage.attr.add("no_retiming")
|
||||||
|
tx_length = Signal(bits_for(max_packet))
|
||||||
|
self.specials += MultiReg(self.aux_tx_length.storage, tx_length, "rtio")
|
||||||
|
|
||||||
|
frame_counter_nbits = bits_for(max_packet) - log2_int(mem_dw//8)
|
||||||
|
frame_counter = Signal(frame_counter_nbits)
|
||||||
|
frame_counter_next = Signal(frame_counter_nbits)
|
||||||
|
frame_counter_ce = Signal()
|
||||||
|
frame_counter_rst = Signal()
|
||||||
|
self.comb += [
|
||||||
|
frame_counter_next.eq(frame_counter),
|
||||||
|
If(frame_counter_rst,
|
||||||
|
frame_counter_next.eq(0)
|
||||||
|
).Elif(frame_counter_ce,
|
||||||
|
frame_counter_next.eq(frame_counter + 1)
|
||||||
|
),
|
||||||
|
mem_port.adr.eq(frame_counter_next),
|
||||||
|
converter.sink.data.eq(mem_port.dat_r)
|
||||||
|
]
|
||||||
|
self.sync.rtio += frame_counter.eq(frame_counter_next)
|
||||||
|
|
||||||
|
start_tx = PulseSynchronizer("sys", "rtio")
|
||||||
|
tx_done = PulseSynchronizer("rtio", "sys")
|
||||||
|
self.submodules += start_tx, tx_done
|
||||||
|
self.comb += start_tx.i.eq(self.aux_tx.re)
|
||||||
|
self.sync += [
|
||||||
|
If(tx_done.o, self.aux_tx.w.eq(0)),
|
||||||
|
If(self.aux_tx.re, self.aux_tx.w.eq(1))
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="IDLE"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
frame_counter_rst.eq(1),
|
||||||
|
seen_eop_rst.eq(1),
|
||||||
|
If(start_tx.o, NextState("TRANSMIT"))
|
||||||
|
)
|
||||||
|
fsm.act("TRANSMIT",
|
||||||
|
converter.sink.stb.eq(1),
|
||||||
|
If(converter.sink.ack,
|
||||||
|
frame_counter_ce.eq(1)
|
||||||
|
),
|
||||||
|
If(frame_counter_next == tx_length, NextState("WAIT_INTERFRAME"))
|
||||||
|
)
|
||||||
|
fsm.act("WAIT_INTERFRAME",
|
||||||
|
If(seen_eop,
|
||||||
|
tx_done.i.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Receiver(Module, AutoCSR):
|
||||||
|
def __init__(self, link_layer, min_mem_dw):
|
||||||
|
self.aux_rx_length = CSRStatus(bits_for(max_packet))
|
||||||
|
self.aux_rx_present = CSR()
|
||||||
|
self.aux_rx_error = CSR()
|
||||||
|
|
||||||
|
ll_dw = len(link_layer.rx_aux_data)
|
||||||
|
mem_dw = max(min_mem_dw, ll_dw)
|
||||||
|
self.specials.mem = Memory(mem_dw, max_packet//(mem_dw//8))
|
||||||
|
|
||||||
|
converter = stream.Converter(ll_dw, mem_dw)
|
||||||
|
self.submodules += converter
|
||||||
|
|
||||||
|
# when continuously drained, the Converter accepts data continuously
|
||||||
|
frame_r = Signal()
|
||||||
|
self.sync.rtio_rx += [
|
||||||
|
If(link_layer.rx_aux_stb,
|
||||||
|
frame_r.eq(link_layer.rx_aux_frame),
|
||||||
|
converter.sink.data.eq(link_layer.rx_aux_data)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.comb += [
|
||||||
|
converter.sink.stb.eq(link_layer.rx_aux_stb & frame_r),
|
||||||
|
converter.sink.eop.eq(converter.sink.stb & ~link_layer.rx_aux_frame)
|
||||||
|
]
|
||||||
|
|
||||||
|
mem_port = self.mem.get_port(write_capable=True, clock_domain="rtio_rx")
|
||||||
|
self.specials += mem_port
|
||||||
|
|
||||||
|
frame_counter_nbits = bits_for(max_packet) - log2_int(mem_dw//8)
|
||||||
|
frame_counter = Signal(frame_counter_nbits)
|
||||||
|
self.comb += [
|
||||||
|
mem_port.adr.eq(frame_counter),
|
||||||
|
mem_port.dat_w.eq(converter.source.data),
|
||||||
|
converter.source.ack.eq(1)
|
||||||
|
]
|
||||||
|
|
||||||
|
frame_counter.attr.add("no_retiming")
|
||||||
|
frame_counter_sys = Signal(frame_counter_nbits)
|
||||||
|
self.specials += MultiReg(frame_counter, frame_counter_sys)
|
||||||
|
self.comb += self.aux_rx_length.status.eq(frame_counter_sys << log2_int(mem_dw//8))
|
||||||
|
|
||||||
|
signal_frame = PulseSynchronizer("rtio_rx", "sys")
|
||||||
|
frame_ack = PulseSynchronizer("sys", "rtio_rx")
|
||||||
|
signal_error = PulseSynchronizer("rtio_rx", "sys")
|
||||||
|
self.submodules += signal_frame, frame_ack, signal_error
|
||||||
|
self.sync += [
|
||||||
|
If(self.aux_rx_present.re, self.aux_rx_present.w.eq(0)),
|
||||||
|
If(signal_frame.o, self.aux_rx_present.w.eq(1)),
|
||||||
|
If(self.aux_rx_error.re, self.aux_rx_error.w.eq(0)),
|
||||||
|
If(signal_error.o, self.aux_rx_error.w.eq(1))
|
||||||
|
]
|
||||||
|
self.comb += frame_ack.i.eq(self.aux_rx_present.re)
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rtio_rx")(FSM(reset_state="IDLE"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
sop = Signal(reset=1)
|
||||||
|
self.sync.rtio_rx += \
|
||||||
|
If(converter.source.stb,
|
||||||
|
If(converter.source.eop,
|
||||||
|
sop.eq(1)
|
||||||
|
).Else(
|
||||||
|
sop.eq(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(converter.source.stb & sop,
|
||||||
|
NextValue(frame_counter, frame_counter + 1),
|
||||||
|
mem_port.we.eq(1),
|
||||||
|
If(converter.source.eop,
|
||||||
|
NextState("SIGNAL_FRAME")
|
||||||
|
).Else(
|
||||||
|
NextState("FRAME")
|
||||||
|
)
|
||||||
|
).Else(
|
||||||
|
NextValue(frame_counter, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("FRAME",
|
||||||
|
If(converter.source.stb,
|
||||||
|
NextValue(frame_counter, frame_counter + 1),
|
||||||
|
mem_port.we.eq(1),
|
||||||
|
If(frame_counter == max_packet,
|
||||||
|
mem_port.we.eq(0),
|
||||||
|
signal_error.i.eq(1),
|
||||||
|
NextState("IDLE") # discard the rest of the frame
|
||||||
|
),
|
||||||
|
If(converter.source.eop,
|
||||||
|
NextState("SIGNAL_FRAME")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("SIGNAL_FRAME",
|
||||||
|
signal_frame.i.eq(1),
|
||||||
|
NextState("WAIT_ACK"),
|
||||||
|
If(converter.source.stb, signal_error.i.eq(1))
|
||||||
|
)
|
||||||
|
fsm.act("WAIT_ACK",
|
||||||
|
If(frame_ack.o,
|
||||||
|
NextValue(frame_counter, 0),
|
||||||
|
NextState("IDLE")
|
||||||
|
),
|
||||||
|
If(converter.source.stb, signal_error.i.eq(1))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: FullMemoryWE should be applied by migen.build
|
||||||
|
@FullMemoryWE()
|
||||||
|
class AuxController(Module):
|
||||||
|
def __init__(self, link_layer):
|
||||||
|
self.bus = wishbone.Interface()
|
||||||
|
self.submodules.transmitter = Transmitter(link_layer, len(self.bus.dat_w))
|
||||||
|
self.submodules.receiver = Receiver(link_layer, len(self.bus.dat_w))
|
||||||
|
|
||||||
|
tx_sdram_if = wishbone.SRAM(self.transmitter.mem, read_only=False)
|
||||||
|
rx_sdram_if = wishbone.SRAM(self.receiver.mem, read_only=True)
|
||||||
|
wsb = log2_int(len(self.bus.dat_w)//8)
|
||||||
|
decoder = wishbone.Decoder(self.bus,
|
||||||
|
[(lambda a: a[log2_int(max_packet)-wsb] == 0, tx_sdram_if.bus),
|
||||||
|
(lambda a: a[log2_int(max_packet)-wsb] == 1, rx_sdram_if.bus)],
|
||||||
|
register=True)
|
||||||
|
self.submodules += tx_sdram_if, rx_sdram_if, decoder
|
||||||
|
|
||||||
|
def get_csrs(self):
|
||||||
|
return self.transmitter.get_csrs() + self.receiver.get_csrs()
|
|
@ -0,0 +1,68 @@
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio import link_layer, rt_packets, iot, rt_controller, aux_controller
|
||||||
|
|
||||||
|
|
||||||
|
class DRTIOSatellite(Module):
|
||||||
|
def __init__(self, transceiver, rx_synchronizer, channels, fine_ts_width=3, full_ts_width=63):
|
||||||
|
self.submodules.link_layer = link_layer.LinkLayer(
|
||||||
|
transceiver.encoder, transceiver.decoders)
|
||||||
|
self.comb += self.link_layer.rx_ready.eq(transceiver.rx_ready)
|
||||||
|
|
||||||
|
link_layer_sync = SimpleNamespace(
|
||||||
|
tx_aux_frame=self.link_layer.tx_aux_frame,
|
||||||
|
tx_aux_data=self.link_layer.tx_aux_data,
|
||||||
|
tx_aux_ack=self.link_layer.tx_aux_ack,
|
||||||
|
tx_rt_frame=self.link_layer.tx_rt_frame,
|
||||||
|
tx_rt_data=self.link_layer.tx_rt_data,
|
||||||
|
|
||||||
|
rx_aux_stb=rx_synchronizer.resync(self.link_layer.rx_aux_stb),
|
||||||
|
rx_aux_frame=rx_synchronizer.resync(self.link_layer.rx_aux_frame),
|
||||||
|
rx_aux_data=rx_synchronizer.resync(self.link_layer.rx_aux_data),
|
||||||
|
rx_rt_frame=rx_synchronizer.resync(self.link_layer.rx_rt_frame),
|
||||||
|
rx_rt_data=rx_synchronizer.resync(self.link_layer.rx_rt_data)
|
||||||
|
)
|
||||||
|
self.submodules.rt_packets = ClockDomainsRenamer("rtio")(
|
||||||
|
rt_packets.RTPacketSatellite(link_layer_sync))
|
||||||
|
|
||||||
|
self.submodules.iot = iot.IOT(
|
||||||
|
self.rt_packets, channels, fine_ts_width, full_ts_width)
|
||||||
|
|
||||||
|
self.clock_domains.cd_rio = ClockDomain()
|
||||||
|
self.clock_domains.cd_rio_phy = ClockDomain()
|
||||||
|
self.comb += [
|
||||||
|
self.cd_rio.clk.eq(ClockSignal("rtio")),
|
||||||
|
self.cd_rio.rst.eq(self.rt_packets.reset),
|
||||||
|
self.cd_rio_phy.clk.eq(ClockSignal("rtio")),
|
||||||
|
self.cd_rio_phy.rst.eq(self.rt_packets.reset_phy),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.submodules.aux_controller = aux_controller.AuxController(
|
||||||
|
self.link_layer)
|
||||||
|
|
||||||
|
def get_csrs(self):
|
||||||
|
return self.aux_controller.get_csrs()
|
||||||
|
|
||||||
|
|
||||||
|
class DRTIOMaster(Module):
|
||||||
|
def __init__(self, transceiver, channel_count=1024, fine_ts_width=3):
|
||||||
|
self.submodules.link_layer = link_layer.LinkLayer(
|
||||||
|
transceiver.encoder, transceiver.decoders)
|
||||||
|
self.comb += self.link_layer.rx_ready.eq(transceiver.rx_ready)
|
||||||
|
|
||||||
|
self.submodules.rt_packets = rt_packets.RTPacketMaster(self.link_layer)
|
||||||
|
self.submodules.rt_controller = rt_controller.RTController(
|
||||||
|
self.rt_packets, channel_count, fine_ts_width)
|
||||||
|
self.submodules.rt_manager = rt_controller.RTManager(self.rt_packets)
|
||||||
|
self.cri = self.rt_controller.cri
|
||||||
|
|
||||||
|
self.submodules.aux_controller = aux_controller.AuxController(
|
||||||
|
self.link_layer)
|
||||||
|
|
||||||
|
def get_csrs(self):
|
||||||
|
return (self.link_layer.get_csrs() +
|
||||||
|
self.rt_controller.get_csrs() +
|
||||||
|
self.rt_manager.get_csrs() +
|
||||||
|
self.aux_controller.get_csrs())
|
|
@ -0,0 +1,86 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.fifo import SyncFIFOBuffered
|
||||||
|
from migen.genlib.record import *
|
||||||
|
|
||||||
|
from artiq.gateware.rtio import rtlink
|
||||||
|
|
||||||
|
|
||||||
|
class IOT(Module):
|
||||||
|
def __init__(self, rt_packets, channels, max_fine_ts_width, full_ts_width):
|
||||||
|
tsc = Signal(full_ts_width - max_fine_ts_width)
|
||||||
|
self.sync.rtio += \
|
||||||
|
If(rt_packets.tsc_load,
|
||||||
|
tsc.eq(rt_packets.tsc_value)
|
||||||
|
).Else(
|
||||||
|
tsc.eq(tsc + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
for n, channel in enumerate(channels):
|
||||||
|
interface = channel.interface.o
|
||||||
|
data_width = rtlink.get_data_width(interface)
|
||||||
|
address_width = rtlink.get_address_width(interface)
|
||||||
|
fine_ts_width = rtlink.get_fine_ts_width(interface)
|
||||||
|
assert fine_ts_width <= max_fine_ts_width
|
||||||
|
|
||||||
|
# FIFO
|
||||||
|
ev_layout = []
|
||||||
|
if data_width:
|
||||||
|
ev_layout.append(("data", data_width))
|
||||||
|
if address_width:
|
||||||
|
ev_layout.append(("address", address_width))
|
||||||
|
ev_layout.append(("timestamp", len(tsc) + fine_ts_width))
|
||||||
|
|
||||||
|
fifo = ClockDomainsRenamer("rio")(
|
||||||
|
SyncFIFOBuffered(layout_len(ev_layout), channel.ofifo_depth))
|
||||||
|
self.submodules += fifo
|
||||||
|
fifo_in = Record(ev_layout)
|
||||||
|
fifo_out = Record(ev_layout)
|
||||||
|
self.comb += [
|
||||||
|
fifo.din.eq(fifo_in.raw_bits()),
|
||||||
|
fifo_out.raw_bits().eq(fifo.dout)
|
||||||
|
]
|
||||||
|
|
||||||
|
# FIFO level
|
||||||
|
self.sync.rio += \
|
||||||
|
If(rt_packets.fifo_space_update &
|
||||||
|
(rt_packets.fifo_space_channel == n),
|
||||||
|
rt_packets.fifo_space.eq(channel.ofifo_depth - fifo.level))
|
||||||
|
|
||||||
|
# FIFO write
|
||||||
|
self.comb += fifo.we.eq(rt_packets.write_stb
|
||||||
|
& (rt_packets.write_channel == n))
|
||||||
|
self.sync.rio += [
|
||||||
|
If(rt_packets.write_overflow_ack,
|
||||||
|
rt_packets.write_overflow.eq(0)),
|
||||||
|
If(rt_packets.write_underflow_ack,
|
||||||
|
rt_packets.write_underflow.eq(0)),
|
||||||
|
If(fifo.we,
|
||||||
|
If(~fifo.writable, rt_packets.write_overflow.eq(1)),
|
||||||
|
If(rt_packets.write_timestamp[max_fine_ts_width:] < (tsc + 4),
|
||||||
|
rt_packets.write_underflow.eq(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if data_width:
|
||||||
|
self.comb += fifo_in.data.eq(rt_packets.write_data)
|
||||||
|
if address_width:
|
||||||
|
self.comb += fifo_in.address.eq(rt_packets.write_address)
|
||||||
|
self.comb += fifo_in.timestamp.eq(
|
||||||
|
rt_packets.write_timestamp[max_fine_ts_width-fine_ts_width:])
|
||||||
|
|
||||||
|
# FIFO read
|
||||||
|
self.sync.rio += [
|
||||||
|
fifo.re.eq(0),
|
||||||
|
interface.stb.eq(0),
|
||||||
|
If(fifo.readable &
|
||||||
|
(fifo_out.timestamp[fine_ts_width:] == tsc),
|
||||||
|
fifo.re.eq(1),
|
||||||
|
interface.stb.eq(1)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if data_width:
|
||||||
|
self.sync.rio += interface.data.eq(fifo_out.data)
|
||||||
|
if address_width:
|
||||||
|
self.sync.rio += interface.address.eq(fifo_out.address)
|
||||||
|
if fine_ts_width:
|
||||||
|
self.sync.rio += interface.fine_ts.eq(fifo_out.timestamp[:fine_ts_width])
|
|
@ -0,0 +1,278 @@
|
||||||
|
from functools import reduce
|
||||||
|
from operator import xor, or_
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.fsm import *
|
||||||
|
from migen.genlib.cdc import MultiReg
|
||||||
|
from migen.genlib.misc import WaitTimer
|
||||||
|
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
|
||||||
|
|
||||||
|
class Scrambler(Module):
|
||||||
|
def __init__(self, n_io1, n_io2, n_state=23, taps=[17, 22]):
|
||||||
|
self.i1 = Signal(n_io1)
|
||||||
|
self.o1 = Signal(n_io1)
|
||||||
|
self.i2 = Signal(n_io2)
|
||||||
|
self.o2 = Signal(n_io2)
|
||||||
|
self.sel = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
state = Signal(n_state, reset=1)
|
||||||
|
|
||||||
|
stmts1 = []
|
||||||
|
stmts2 = []
|
||||||
|
for stmts, si, so in ((stmts1, self.i1, self.o1),
|
||||||
|
(stmts2, self.i2, self.o2)):
|
||||||
|
curval = [state[i] for i in range(n_state)]
|
||||||
|
for i in reversed(range(len(si))):
|
||||||
|
out = si[i] ^ reduce(xor, [curval[tap] for tap in taps])
|
||||||
|
stmts += [so[i].eq(out)]
|
||||||
|
curval.insert(0, out)
|
||||||
|
curval.pop()
|
||||||
|
|
||||||
|
stmts += [state.eq(Cat(*curval[:n_state]))]
|
||||||
|
|
||||||
|
self.sync += If(self.sel, stmts2).Else(stmts1)
|
||||||
|
|
||||||
|
|
||||||
|
class Descrambler(Module):
|
||||||
|
def __init__(self, n_io1, n_io2, n_state=23, taps=[17, 22]):
|
||||||
|
self.i1 = Signal(n_io1)
|
||||||
|
self.o1 = Signal(n_io1)
|
||||||
|
self.i2 = Signal(n_io2)
|
||||||
|
self.o2 = Signal(n_io2)
|
||||||
|
self.sel = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
state = Signal(n_state, reset=1)
|
||||||
|
|
||||||
|
stmts1 = []
|
||||||
|
stmts2 = []
|
||||||
|
for stmts, si, so in ((stmts1, self.i1, self.o1),
|
||||||
|
(stmts2, self.i2, self.o2)):
|
||||||
|
curval = [state[i] for i in range(n_state)]
|
||||||
|
for i in reversed(range(len(si))):
|
||||||
|
flip = reduce(xor, [curval[tap] for tap in taps])
|
||||||
|
stmts += [so[i].eq(si[i] ^ flip)]
|
||||||
|
curval.insert(0, si[i])
|
||||||
|
curval.pop()
|
||||||
|
|
||||||
|
stmts += [state.eq(Cat(*curval[:n_state]))]
|
||||||
|
|
||||||
|
self.sync += If(self.sel, stmts2).Else(stmts1)
|
||||||
|
|
||||||
|
|
||||||
|
def K(x, y):
|
||||||
|
return (y << 5) | x
|
||||||
|
|
||||||
|
|
||||||
|
aux_coding_comma = [
|
||||||
|
K(28, 5),
|
||||||
|
K(28, 0),
|
||||||
|
K(28, 1),
|
||||||
|
K(28, 2),
|
||||||
|
K(23, 7),
|
||||||
|
K(27, 7),
|
||||||
|
K(29, 7),
|
||||||
|
K(30, 7),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
aux_coding_nocomma = [
|
||||||
|
K(28, 0),
|
||||||
|
K(28, 2),
|
||||||
|
K(28, 3),
|
||||||
|
K(28, 4),
|
||||||
|
K(23, 7),
|
||||||
|
K(27, 7),
|
||||||
|
K(29, 7),
|
||||||
|
K(30, 7),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class LinkLayerTX(Module):
|
||||||
|
def __init__(self, encoder):
|
||||||
|
nwords = len(encoder.k)
|
||||||
|
# nwords must be a power of 2
|
||||||
|
assert nwords & (nwords - 1) == 0
|
||||||
|
|
||||||
|
self.aux_frame = Signal()
|
||||||
|
self.aux_data = Signal(2*nwords)
|
||||||
|
self.aux_ack = Signal()
|
||||||
|
|
||||||
|
self.rt_frame = Signal()
|
||||||
|
self.rt_data = Signal(8*nwords)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# Idle and auxiliary traffic use special characters defined in the
|
||||||
|
# aux_coding_* tables.
|
||||||
|
# The first (or only) character uses aux_coding_comma which guarantees
|
||||||
|
# that commas appear regularly in the absence of traffic.
|
||||||
|
# The subsequent characters, if any (depending on the transceiver
|
||||||
|
# serialization ratio) use aux_coding_nocomma which does not contain
|
||||||
|
# commas. This permits aligning the comma to the first character at
|
||||||
|
# the receiver.
|
||||||
|
#
|
||||||
|
# A set of 8 special characters is chosen using a 3-bit control word.
|
||||||
|
# This control word is scrambled to reduce EMI. The control words have
|
||||||
|
# the following meanings:
|
||||||
|
# 100 idle/auxiliary framing
|
||||||
|
# 0AB 2 bits of auxiliary data
|
||||||
|
#
|
||||||
|
# RT traffic uses D characters and is also scrambled. The aux and RT
|
||||||
|
# scramblers are multiplicative and share the same state so that idle
|
||||||
|
# or aux traffic can synchronize the RT descrambler.
|
||||||
|
|
||||||
|
scrambler = Scrambler(3*nwords, 8*nwords)
|
||||||
|
self.submodules += scrambler
|
||||||
|
|
||||||
|
# scrambler input
|
||||||
|
aux_data_ctl = []
|
||||||
|
for i in range(nwords):
|
||||||
|
aux_data_ctl.append(self.aux_data[i*2:i*2+2])
|
||||||
|
aux_data_ctl.append(0)
|
||||||
|
self.comb += [
|
||||||
|
If(self.aux_frame,
|
||||||
|
scrambler.i1.eq(Cat(*aux_data_ctl))
|
||||||
|
).Else(
|
||||||
|
scrambler.i1.eq(Replicate(0b100, nwords))
|
||||||
|
),
|
||||||
|
scrambler.i2.eq(self.rt_data),
|
||||||
|
scrambler.sel.eq(self.rt_frame),
|
||||||
|
self.aux_ack.eq(~self.rt_frame)
|
||||||
|
]
|
||||||
|
|
||||||
|
# compensate for scrambler latency
|
||||||
|
rt_frame_r = Signal()
|
||||||
|
self.sync += rt_frame_r.eq(self.rt_frame)
|
||||||
|
|
||||||
|
# scrambler output
|
||||||
|
for i in range(nwords):
|
||||||
|
scrambled_ctl = scrambler.o1[i*3:i*3+3]
|
||||||
|
if i:
|
||||||
|
aux_coding = aux_coding_nocomma
|
||||||
|
else:
|
||||||
|
aux_coding = aux_coding_comma
|
||||||
|
self.sync += [
|
||||||
|
encoder.k[i].eq(1),
|
||||||
|
encoder.d[i].eq(Array(aux_coding)[scrambled_ctl])
|
||||||
|
]
|
||||||
|
self.sync += \
|
||||||
|
If(rt_frame_r,
|
||||||
|
[k.eq(0) for k in encoder.k],
|
||||||
|
[d.eq(scrambler.o2[i*8:i*8+8]) for i, d in enumerate(encoder.d)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkLayerRX(Module):
|
||||||
|
def __init__(self, decoders):
|
||||||
|
nwords = len(decoders)
|
||||||
|
# nwords must be a power of 2
|
||||||
|
assert nwords & (nwords - 1) == 0
|
||||||
|
|
||||||
|
self.aux_stb = Signal()
|
||||||
|
self.aux_frame = Signal()
|
||||||
|
self.aux_data = Signal(2*nwords)
|
||||||
|
|
||||||
|
self.rt_frame = Signal()
|
||||||
|
self.rt_data = Signal(8*nwords)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
descrambler = Descrambler(3*nwords, 8*nwords)
|
||||||
|
self.submodules += descrambler
|
||||||
|
|
||||||
|
# scrambler input
|
||||||
|
all_decoded_aux = []
|
||||||
|
for i, d in enumerate(decoders):
|
||||||
|
decoded_aux = Signal(3)
|
||||||
|
all_decoded_aux.append(decoded_aux)
|
||||||
|
|
||||||
|
if i:
|
||||||
|
aux_coding = aux_coding_nocomma
|
||||||
|
else:
|
||||||
|
aux_coding = aux_coding_comma
|
||||||
|
|
||||||
|
cases = {code: decoded_aux.eq(i) for i, code in enumerate(aux_coding)}
|
||||||
|
self.comb += Case(d.d, cases).makedefault()
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
descrambler.i1.eq(Cat(*all_decoded_aux)),
|
||||||
|
descrambler.i2.eq(Cat(*[d.d for d in decoders])),
|
||||||
|
descrambler.sel.eq(~decoders[0].k)
|
||||||
|
]
|
||||||
|
|
||||||
|
# scrambler output
|
||||||
|
self.comb += [
|
||||||
|
self.aux_frame.eq(~descrambler.o1[2]),
|
||||||
|
self.aux_data.eq(
|
||||||
|
Cat(*[descrambler.o1[3*i:3*i+2] for i in range(nwords)])),
|
||||||
|
self.rt_data.eq(descrambler.o2)
|
||||||
|
]
|
||||||
|
self.sync += [
|
||||||
|
self.aux_stb.eq(decoders[0].k),
|
||||||
|
self.rt_frame.eq(~decoders[0].k)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LinkLayer(Module, AutoCSR):
|
||||||
|
def __init__(self, encoder, decoders):
|
||||||
|
self.link_status = CSRStatus()
|
||||||
|
|
||||||
|
# receiver locked, comma aligned, receiving valid 8b10b symbols
|
||||||
|
self.rx_ready = Signal()
|
||||||
|
|
||||||
|
tx = ClockDomainsRenamer("rtio")(LinkLayerTX(encoder))
|
||||||
|
rx = ClockDomainsRenamer("rtio_rx")(LinkLayerRX(decoders))
|
||||||
|
self.submodules += tx, rx
|
||||||
|
|
||||||
|
# in rtio clock domain
|
||||||
|
self.tx_aux_frame = tx.aux_frame
|
||||||
|
self.tx_aux_data = tx.aux_data
|
||||||
|
self.tx_aux_ack = tx.aux_ack
|
||||||
|
self.tx_rt_frame = tx.rt_frame
|
||||||
|
self.tx_rt_data = tx.rt_data
|
||||||
|
|
||||||
|
# in rtio_rx clock domain
|
||||||
|
self.rx_aux_stb = rx.aux_stb
|
||||||
|
self.rx_aux_frame = Signal()
|
||||||
|
self.rx_aux_data = rx.aux_data
|
||||||
|
self.rx_rt_frame = Signal()
|
||||||
|
self.rx_rt_data = rx.rt_data
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
ready = Signal()
|
||||||
|
ready_r = Signal()
|
||||||
|
self.sync.rtio += ready_r.eq(ready)
|
||||||
|
ready_rx = Signal()
|
||||||
|
ready_r.attr.add("no_retiming")
|
||||||
|
self.specials += MultiReg(ready_r, ready_rx, "rtio_rx")
|
||||||
|
self.comb += [
|
||||||
|
self.rx_aux_frame.eq(rx.aux_frame & ready_rx),
|
||||||
|
self.rx_rt_frame.eq(rx.rt_frame & ready_rx),
|
||||||
|
]
|
||||||
|
self.specials += MultiReg(ready_r, self.link_status.status)
|
||||||
|
|
||||||
|
wait_scrambler = ClockDomainsRenamer("rtio")(WaitTimer(15))
|
||||||
|
self.submodules += wait_scrambler
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="WAIT_RX_READY"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("WAIT_RX_READY",
|
||||||
|
If(self.rx_ready, NextState("WAIT_SCRAMBLER_SYNC"))
|
||||||
|
)
|
||||||
|
fsm.act("WAIT_SCRAMBLER_SYNC",
|
||||||
|
wait_scrambler.wait.eq(1),
|
||||||
|
If(wait_scrambler.done, NextState("READY"))
|
||||||
|
)
|
||||||
|
fsm.act("READY",
|
||||||
|
ready.eq(1),
|
||||||
|
If(~self.rx_ready, NextState("WAIT_RX_READY"))
|
||||||
|
)
|
|
@ -0,0 +1,233 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.cdc import MultiReg
|
||||||
|
from migen.genlib.misc import WaitTimer
|
||||||
|
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
|
||||||
|
from artiq.gateware.rtio.cdc import RTIOCounter
|
||||||
|
from artiq.gateware.rtio import cri
|
||||||
|
|
||||||
|
|
||||||
|
class _CSRs(AutoCSR):
|
||||||
|
def __init__(self):
|
||||||
|
self.chan_sel_override = CSRStorage(16)
|
||||||
|
self.chan_sel_override_en = CSRStorage()
|
||||||
|
|
||||||
|
self.tsc_correction = CSRStorage(64)
|
||||||
|
self.set_time = CSR()
|
||||||
|
self.underflow_margin = CSRStorage(16, reset=200)
|
||||||
|
|
||||||
|
self.o_get_fifo_space = CSR()
|
||||||
|
self.o_dbg_fifo_space = CSRStatus(16)
|
||||||
|
self.o_dbg_last_timestamp = CSRStatus(64)
|
||||||
|
self.o_reset_channel_status = CSR()
|
||||||
|
self.o_wait = CSRStatus()
|
||||||
|
self.o_fifo_space_timeout = CSR()
|
||||||
|
|
||||||
|
|
||||||
|
class RTController(Module):
|
||||||
|
def __init__(self, rt_packets, channel_count, fine_ts_width):
|
||||||
|
self.csrs = _CSRs()
|
||||||
|
self.cri = cri.Interface()
|
||||||
|
self.comb += self.cri.arb_gnt.eq(1)
|
||||||
|
|
||||||
|
# channel selection
|
||||||
|
chan_sel = Signal(16)
|
||||||
|
self.comb += chan_sel.eq(
|
||||||
|
Mux(self.csrs.chan_sel_override_en.storage,
|
||||||
|
self.csrs.chan_sel_override.storage,
|
||||||
|
self.cri.chan_sel[:16]))
|
||||||
|
|
||||||
|
# master RTIO counter and counter synchronization
|
||||||
|
self.submodules.counter = RTIOCounter(64-fine_ts_width)
|
||||||
|
self.comb += self.cri.counter.eq(self.counter.value_sys << fine_ts_width)
|
||||||
|
tsc_correction = Signal(64)
|
||||||
|
self.csrs.tsc_correction.storage.attr.add("no_retiming")
|
||||||
|
self.specials += MultiReg(self.csrs.tsc_correction.storage, tsc_correction)
|
||||||
|
self.comb += [
|
||||||
|
rt_packets.tsc_value.eq(
|
||||||
|
self.counter.value_rtio + tsc_correction),
|
||||||
|
self.csrs.set_time.w.eq(rt_packets.set_time_stb)
|
||||||
|
]
|
||||||
|
self.sync += [
|
||||||
|
If(rt_packets.set_time_ack, rt_packets.set_time_stb.eq(0)),
|
||||||
|
If(self.csrs.set_time.re, rt_packets.set_time_stb.eq(1))
|
||||||
|
]
|
||||||
|
|
||||||
|
# reset
|
||||||
|
self.sync += [
|
||||||
|
If(rt_packets.reset_ack, rt_packets.reset_stb.eq(0)),
|
||||||
|
If(self.cri.cmd == cri.commands["reset"],
|
||||||
|
rt_packets.reset_stb.eq(1),
|
||||||
|
rt_packets.reset_phy.eq(0)
|
||||||
|
),
|
||||||
|
If(self.cri.cmd == cri.commands["reset_phy"],
|
||||||
|
rt_packets.reset_stb.eq(1),
|
||||||
|
rt_packets.reset_phy.eq(1)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# remote channel status cache
|
||||||
|
fifo_spaces_mem = Memory(16, channel_count)
|
||||||
|
fifo_spaces = fifo_spaces_mem.get_port(write_capable=True)
|
||||||
|
self.specials += fifo_spaces_mem, fifo_spaces
|
||||||
|
last_timestamps_mem = Memory(64, channel_count)
|
||||||
|
last_timestamps = last_timestamps_mem.get_port(write_capable=True)
|
||||||
|
self.specials += last_timestamps_mem, last_timestamps
|
||||||
|
|
||||||
|
# common packet fields
|
||||||
|
rt_packets_fifo_request = Signal()
|
||||||
|
self.comb += [
|
||||||
|
fifo_spaces.adr.eq(chan_sel),
|
||||||
|
last_timestamps.adr.eq(chan_sel),
|
||||||
|
last_timestamps.dat_w.eq(self.cri.o_timestamp),
|
||||||
|
rt_packets.write_channel.eq(chan_sel),
|
||||||
|
rt_packets.write_address.eq(self.cri.o_address),
|
||||||
|
rt_packets.write_data.eq(self.cri.o_data),
|
||||||
|
If(rt_packets_fifo_request,
|
||||||
|
rt_packets.write_timestamp.eq(0xffff000000000000)
|
||||||
|
).Else(
|
||||||
|
rt_packets.write_timestamp.eq(self.cri.o_timestamp)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = FSM()
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
status_wait = Signal()
|
||||||
|
status_underflow = Signal()
|
||||||
|
status_sequence_error = Signal()
|
||||||
|
self.comb += [
|
||||||
|
self.cri.o_status.eq(Cat(
|
||||||
|
status_wait, status_underflow, status_sequence_error)),
|
||||||
|
self.csrs.o_wait.status.eq(status_wait)
|
||||||
|
]
|
||||||
|
sequence_error_set = Signal()
|
||||||
|
underflow_set = Signal()
|
||||||
|
self.sync += [
|
||||||
|
If(self.cri.cmd == cri.commands["o_underflow_reset"], status_underflow.eq(0)),
|
||||||
|
If(self.cri.cmd == cri.commands["o_sequence_error_reset"], status_sequence_error.eq(0)),
|
||||||
|
If(underflow_set, status_underflow.eq(1)),
|
||||||
|
If(sequence_error_set, status_sequence_error.eq(1)),
|
||||||
|
]
|
||||||
|
|
||||||
|
signal_fifo_space_timeout = Signal()
|
||||||
|
self.sync += [
|
||||||
|
If(self.csrs.o_fifo_space_timeout.re, self.csrs.o_fifo_space_timeout.w.eq(0)),
|
||||||
|
If(signal_fifo_space_timeout, self.csrs.o_fifo_space_timeout.w.eq(1))
|
||||||
|
]
|
||||||
|
timeout_counter = WaitTimer(8191)
|
||||||
|
self.submodules += timeout_counter
|
||||||
|
|
||||||
|
# TODO: collision, replace, busy
|
||||||
|
cond_sequence_error = self.cri.o_timestamp < last_timestamps.dat_r
|
||||||
|
cond_underflow = ((self.cri.o_timestamp[fine_ts_width:]
|
||||||
|
- self.csrs.underflow_margin.storage[fine_ts_width:]) < self.counter.value_sys)
|
||||||
|
cond_fifo_emptied = ((last_timestamps.dat_r[fine_ts_width:] < self.counter.value_sys)
|
||||||
|
& (last_timestamps.dat_r != 0))
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(self.cri.cmd == cri.commands["write"],
|
||||||
|
If(cond_sequence_error,
|
||||||
|
sequence_error_set.eq(1)
|
||||||
|
).Elif(cond_underflow,
|
||||||
|
underflow_set.eq(1)
|
||||||
|
).Else(
|
||||||
|
NextState("WRITE")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
If(self.csrs.o_get_fifo_space.re,
|
||||||
|
NextState("GET_FIFO_SPACE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WRITE",
|
||||||
|
status_wait.eq(1),
|
||||||
|
rt_packets.write_stb.eq(1),
|
||||||
|
If(rt_packets.write_ack,
|
||||||
|
fifo_spaces.we.eq(1),
|
||||||
|
If(cond_fifo_emptied,
|
||||||
|
fifo_spaces.dat_w.eq(1),
|
||||||
|
).Else(
|
||||||
|
fifo_spaces.dat_w.eq(fifo_spaces.dat_r - 1)
|
||||||
|
),
|
||||||
|
last_timestamps.we.eq(1),
|
||||||
|
If(~cond_fifo_emptied & (fifo_spaces.dat_r <= 1),
|
||||||
|
NextState("GET_FIFO_SPACE")
|
||||||
|
).Else(
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("GET_FIFO_SPACE",
|
||||||
|
status_wait.eq(1),
|
||||||
|
rt_packets_fifo_request.eq(1),
|
||||||
|
rt_packets.write_stb.eq(1),
|
||||||
|
If(rt_packets.write_ack,
|
||||||
|
NextState("GET_FIFO_SPACE_REPLY")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("GET_FIFO_SPACE_REPLY",
|
||||||
|
status_wait.eq(1),
|
||||||
|
fifo_spaces.dat_w.eq(rt_packets.fifo_space),
|
||||||
|
fifo_spaces.we.eq(1),
|
||||||
|
rt_packets.fifo_space_not_ack.eq(1),
|
||||||
|
If(rt_packets.fifo_space_not,
|
||||||
|
If(rt_packets.fifo_space > 0,
|
||||||
|
NextState("IDLE")
|
||||||
|
).Else(
|
||||||
|
NextState("GET_FIFO_SPACE")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
timeout_counter.wait.eq(1),
|
||||||
|
If(timeout_counter.done,
|
||||||
|
signal_fifo_space_timeout.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# channel state access
|
||||||
|
self.comb += [
|
||||||
|
self.csrs.o_dbg_fifo_space.status.eq(fifo_spaces.dat_r),
|
||||||
|
self.csrs.o_dbg_last_timestamp.status.eq(last_timestamps.dat_r),
|
||||||
|
If(self.csrs.o_reset_channel_status.re,
|
||||||
|
fifo_spaces.dat_w.eq(0),
|
||||||
|
fifo_spaces.we.eq(1),
|
||||||
|
last_timestamps.dat_w.eq(0),
|
||||||
|
last_timestamps.we.eq(1)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_csrs(self):
|
||||||
|
return self.csrs.get_csrs()
|
||||||
|
|
||||||
|
|
||||||
|
class RTManager(Module, AutoCSR):
|
||||||
|
def __init__(self, rt_packets):
|
||||||
|
self.request_echo = CSR()
|
||||||
|
|
||||||
|
self.packet_err_present = CSR()
|
||||||
|
self.packet_err_code = CSRStatus(8)
|
||||||
|
|
||||||
|
self.update_packet_cnt = CSR()
|
||||||
|
self.packet_cnt_tx = CSRStatus(32)
|
||||||
|
self.packet_cnt_rx = CSRStatus(32)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
self.comb += self.request_echo.w.eq(rt_packets.echo_stb)
|
||||||
|
self.sync += [
|
||||||
|
If(rt_packets.echo_ack, rt_packets.echo_stb.eq(0)),
|
||||||
|
If(self.request_echo.re, rt_packets.echo_stb.eq(1))
|
||||||
|
]
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.packet_err_present.w.eq(rt_packets.error_not),
|
||||||
|
rt_packets.error_not_ack.eq(self.packet_err_present.re),
|
||||||
|
self.packet_err_code.status.eq(rt_packets.error_code)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.sync += \
|
||||||
|
If(self.update_packet_cnt.re,
|
||||||
|
self.packet_cnt_tx.status.eq(rt_packets.packet_cnt_tx),
|
||||||
|
self.packet_cnt_rx.status.eq(rt_packets.packet_cnt_rx)
|
||||||
|
)
|
|
@ -0,0 +1,731 @@
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.fsm import *
|
||||||
|
from migen.genlib.fifo import AsyncFIFO
|
||||||
|
from migen.genlib.cdc import PulseSynchronizer
|
||||||
|
|
||||||
|
from artiq.gateware.rtio.cdc import GrayCodeTransfer
|
||||||
|
|
||||||
|
|
||||||
|
def layout_len(l):
|
||||||
|
return sum(e[1] for e in l)
|
||||||
|
|
||||||
|
|
||||||
|
class PacketLayoutManager:
|
||||||
|
def __init__(self, alignment):
|
||||||
|
self.alignment = alignment
|
||||||
|
self.layouts = dict()
|
||||||
|
self.types = dict()
|
||||||
|
self.type_names = dict()
|
||||||
|
|
||||||
|
def add_type(self, name, *fields, pad=True):
|
||||||
|
type_n = len(self.types)
|
||||||
|
self.types[name] = type_n
|
||||||
|
self.type_names[type_n] = name
|
||||||
|
layout = [("ty", 8)] + list(fields)
|
||||||
|
misalignment = layout_len(layout) % self.alignment
|
||||||
|
if misalignment:
|
||||||
|
layout.append(("packet_pad", self.alignment - misalignment))
|
||||||
|
self.layouts[name] = layout
|
||||||
|
|
||||||
|
def field_length(self, type_name, field_name):
|
||||||
|
layout = self.layouts[type_name]
|
||||||
|
for name, length in layout:
|
||||||
|
if name == field_name:
|
||||||
|
return length
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
|
def get_m2s_layouts(alignment):
|
||||||
|
if alignment > 128:
|
||||||
|
short_data_len = alignment - 128 + 16
|
||||||
|
else:
|
||||||
|
short_data_len = 16
|
||||||
|
|
||||||
|
plm = PacketLayoutManager(alignment)
|
||||||
|
plm.add_type("echo_request")
|
||||||
|
plm.add_type("set_time", ("timestamp", 64))
|
||||||
|
plm.add_type("reset", ("phy", 1))
|
||||||
|
plm.add_type("write", ("timestamp", 64),
|
||||||
|
("channel", 16),
|
||||||
|
("address", 16),
|
||||||
|
("extra_data_cnt", 8),
|
||||||
|
("short_data", short_data_len))
|
||||||
|
plm.add_type("fifo_space_request", ("channel", 16))
|
||||||
|
return plm
|
||||||
|
|
||||||
|
|
||||||
|
def get_s2m_layouts(alignment):
|
||||||
|
plm = PacketLayoutManager(alignment)
|
||||||
|
plm.add_type("error", ("code", 8))
|
||||||
|
plm.add_type("echo_reply")
|
||||||
|
plm.add_type("fifo_space_reply", ("space", 16))
|
||||||
|
return plm
|
||||||
|
|
||||||
|
|
||||||
|
error_codes = {
|
||||||
|
"unknown_type_local": 0,
|
||||||
|
"unknown_type_remote": 1,
|
||||||
|
# The transmitter is normally responsible for avoiding
|
||||||
|
# overflows and underflows. Those error reports are only
|
||||||
|
# for diagnosing internal ARTIQ bugs.
|
||||||
|
"write_overflow": 2,
|
||||||
|
"write_underflow": 3
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveDatapath(Module):
|
||||||
|
def __init__(self, frame, data, plm):
|
||||||
|
ws = len(data)
|
||||||
|
|
||||||
|
# control
|
||||||
|
self.packet_buffer_load = Signal()
|
||||||
|
|
||||||
|
# outputs
|
||||||
|
self.frame_r = Signal()
|
||||||
|
self.data_r = Signal(ws)
|
||||||
|
self.packet_type = Signal(8)
|
||||||
|
self.packet_last = Signal()
|
||||||
|
self.packet_as = dict()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# input pipeline stage - determine packet length based on type
|
||||||
|
lastword_per_type = [layout_len(plm.layouts[plm.type_names[i]])//ws - 1
|
||||||
|
for i in range(len(plm.layouts))]
|
||||||
|
packet_last_n = Signal(max=max(lastword_per_type)+1)
|
||||||
|
self.sync += [
|
||||||
|
self.frame_r.eq(frame),
|
||||||
|
self.data_r.eq(data),
|
||||||
|
If(frame & ~self.frame_r,
|
||||||
|
self.packet_type.eq(data[:8]),
|
||||||
|
packet_last_n.eq(Array(lastword_per_type)[data[:8]])
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# bufferize packet
|
||||||
|
packet_buffer = Signal(max(layout_len(l)
|
||||||
|
for l in plm.layouts.values()))
|
||||||
|
w_in_packet = len(packet_buffer)//ws
|
||||||
|
packet_buffer_count = Signal(max=w_in_packet+1)
|
||||||
|
self.sync += \
|
||||||
|
If(self.packet_buffer_load,
|
||||||
|
Case(packet_buffer_count,
|
||||||
|
{i: packet_buffer[i*ws:(i+1)*ws].eq(self.data_r)
|
||||||
|
for i in range(w_in_packet)}),
|
||||||
|
packet_buffer_count.eq(packet_buffer_count + 1)
|
||||||
|
).Else(
|
||||||
|
packet_buffer_count.eq(0)
|
||||||
|
)
|
||||||
|
self.comb += self.packet_last.eq(packet_buffer_count == packet_last_n)
|
||||||
|
|
||||||
|
# dissect packet
|
||||||
|
for name, layout in plm.layouts.items():
|
||||||
|
fields = SimpleNamespace()
|
||||||
|
idx = 0
|
||||||
|
for field_name, field_size in layout:
|
||||||
|
setattr(fields, field_name, packet_buffer[idx:idx+field_size])
|
||||||
|
idx += field_size
|
||||||
|
self.packet_as[name] = fields
|
||||||
|
|
||||||
|
|
||||||
|
class TransmitDatapath(Module):
|
||||||
|
def __init__(self, frame, data, plm):
|
||||||
|
ws = len(data)
|
||||||
|
assert ws % 8 == 0
|
||||||
|
self.ws = ws
|
||||||
|
self.plm = plm
|
||||||
|
|
||||||
|
self.packet_buffer = Signal(max(layout_len(l)
|
||||||
|
for l in plm.layouts.values()))
|
||||||
|
w_in_packet = len(self.packet_buffer)//ws
|
||||||
|
self.packet_last_n = Signal(max=max(w_in_packet, 2))
|
||||||
|
self.packet_stb = Signal()
|
||||||
|
self.packet_last = Signal()
|
||||||
|
|
||||||
|
self.raw_stb = Signal()
|
||||||
|
self.raw_data = Signal(ws)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
self.sync += frame.eq(0)
|
||||||
|
|
||||||
|
if w_in_packet > 1:
|
||||||
|
packet_buffer_count = Signal(max=w_in_packet)
|
||||||
|
self.comb += self.packet_last.eq(packet_buffer_count == self.packet_last_n)
|
||||||
|
self.sync += [
|
||||||
|
packet_buffer_count.eq(0),
|
||||||
|
If(self.packet_stb,
|
||||||
|
frame.eq(1),
|
||||||
|
Case(packet_buffer_count,
|
||||||
|
{i: data.eq(self.packet_buffer[i*ws:(i+1)*ws])
|
||||||
|
for i in range(w_in_packet)}),
|
||||||
|
packet_buffer_count.eq(packet_buffer_count + 1)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.comb += self.packet_last.eq(1)
|
||||||
|
self.sync += \
|
||||||
|
If(self.packet_stb,
|
||||||
|
frame.eq(1),
|
||||||
|
data.eq(self.packet_buffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.sync += [
|
||||||
|
If(self.raw_stb,
|
||||||
|
frame.eq(1),
|
||||||
|
data.eq(self.raw_data)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
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
|
||||||
|
return [
|
||||||
|
self.packet_stb.eq(1),
|
||||||
|
self.packet_buffer.eq(value),
|
||||||
|
self.packet_last_n.eq(idx//self.ws-1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RTPacketSatellite(Module):
|
||||||
|
def __init__(self, link_layer):
|
||||||
|
self.tsc_load = Signal()
|
||||||
|
self.tsc_value = Signal(64)
|
||||||
|
|
||||||
|
self.reset = Signal(reset=1)
|
||||||
|
self.reset_phy = Signal(reset=1)
|
||||||
|
|
||||||
|
self.fifo_space_channel = Signal(16)
|
||||||
|
self.fifo_space_update = Signal()
|
||||||
|
self.fifo_space = Signal(16)
|
||||||
|
|
||||||
|
self.write_stb = Signal()
|
||||||
|
self.write_timestamp = Signal(64)
|
||||||
|
self.write_channel = Signal(16)
|
||||||
|
self.write_address = Signal(16)
|
||||||
|
self.write_data = Signal(512)
|
||||||
|
self.write_overflow = Signal()
|
||||||
|
self.write_overflow_ack = Signal()
|
||||||
|
self.write_underflow = Signal()
|
||||||
|
self.write_underflow_ack = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
rx_plm = get_m2s_layouts(ws)
|
||||||
|
rx_dp = ReceiveDatapath(
|
||||||
|
link_layer.rx_rt_frame, link_layer.rx_rt_data, rx_plm)
|
||||||
|
self.submodules += rx_dp
|
||||||
|
tx_plm = get_s2m_layouts(ws)
|
||||||
|
tx_dp = TransmitDatapath(
|
||||||
|
link_layer.tx_rt_frame, link_layer.tx_rt_data, tx_plm)
|
||||||
|
self.submodules += tx_dp
|
||||||
|
|
||||||
|
# RX write data buffer
|
||||||
|
write_data_buffer_load = Signal()
|
||||||
|
write_data_buffer_cnt = Signal(max=512//ws+1)
|
||||||
|
write_data_buffer = Signal(512)
|
||||||
|
self.sync += \
|
||||||
|
If(write_data_buffer_load,
|
||||||
|
Case(write_data_buffer_cnt,
|
||||||
|
{i: write_data_buffer[i*ws:(i+1)*ws].eq(rx_dp.data_r)
|
||||||
|
for i in range(512//ws)}),
|
||||||
|
write_data_buffer_cnt.eq(write_data_buffer_cnt + 1)
|
||||||
|
).Else(
|
||||||
|
write_data_buffer_cnt.eq(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RX->TX
|
||||||
|
echo_req = Signal()
|
||||||
|
err_set = Signal()
|
||||||
|
err_req = Signal()
|
||||||
|
err_ack = Signal()
|
||||||
|
fifo_space_set = Signal()
|
||||||
|
fifo_space_req = Signal()
|
||||||
|
fifo_space_ack = Signal()
|
||||||
|
self.sync += [
|
||||||
|
If(err_ack, err_req.eq(0)),
|
||||||
|
If(err_set, err_req.eq(1)),
|
||||||
|
If(fifo_space_ack, fifo_space_req.eq(0)),
|
||||||
|
If(fifo_space_set, fifo_space_req.eq(1)),
|
||||||
|
]
|
||||||
|
err_code = Signal(max=len(error_codes)+1)
|
||||||
|
|
||||||
|
# RX FSM
|
||||||
|
self.comb += [
|
||||||
|
self.tsc_value.eq(
|
||||||
|
rx_dp.packet_as["set_time"].timestamp),
|
||||||
|
self.fifo_space_channel.eq(
|
||||||
|
rx_dp.packet_as["fifo_space_request"].channel),
|
||||||
|
self.write_timestamp.eq(
|
||||||
|
rx_dp.packet_as["write"].timestamp),
|
||||||
|
self.write_channel.eq(
|
||||||
|
rx_dp.packet_as["write"].channel),
|
||||||
|
self.write_address.eq(
|
||||||
|
rx_dp.packet_as["write"].address),
|
||||||
|
self.write_data.eq(
|
||||||
|
Cat(rx_dp.packet_as["write"].short_data, write_data_buffer))
|
||||||
|
]
|
||||||
|
|
||||||
|
reset = Signal()
|
||||||
|
reset_phy = Signal()
|
||||||
|
self.sync += [
|
||||||
|
self.reset.eq(reset),
|
||||||
|
self.reset_phy.eq(reset_phy)
|
||||||
|
]
|
||||||
|
|
||||||
|
rx_fsm = FSM(reset_state="INPUT")
|
||||||
|
self.submodules += rx_fsm
|
||||||
|
|
||||||
|
rx_fsm.act("INPUT",
|
||||||
|
If(rx_dp.frame_r,
|
||||||
|
rx_dp.packet_buffer_load.eq(1),
|
||||||
|
If(rx_dp.packet_last,
|
||||||
|
Case(rx_dp.packet_type, {
|
||||||
|
# echo must have fixed latency, so there is no memory
|
||||||
|
# mechanism
|
||||||
|
rx_plm.types["echo_request"]: echo_req.eq(1),
|
||||||
|
rx_plm.types["set_time"]: NextState("SET_TIME"),
|
||||||
|
rx_plm.types["reset"]: NextState("RESET"),
|
||||||
|
rx_plm.types["write"]: NextState("WRITE"),
|
||||||
|
rx_plm.types["fifo_space_request"]:
|
||||||
|
NextState("FIFO_SPACE"),
|
||||||
|
"default": [
|
||||||
|
err_set.eq(1),
|
||||||
|
NextValue(err_code, error_codes["unknown_type_remote"])]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rx_fsm.act("SET_TIME",
|
||||||
|
self.tsc_load.eq(1),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
rx_fsm.act("RESET",
|
||||||
|
If(rx_dp.packet_as["reset"].phy,
|
||||||
|
reset_phy.eq(1)
|
||||||
|
).Else(
|
||||||
|
reset.eq(1)
|
||||||
|
),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
rx_fsm.act("WRITE",
|
||||||
|
write_data_buffer_load.eq(1),
|
||||||
|
If(write_data_buffer_cnt == rx_dp.packet_as["write"].extra_data_cnt,
|
||||||
|
self.write_stb.eq(1),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rx_fsm.act("FIFO_SPACE",
|
||||||
|
fifo_space_set.eq(1),
|
||||||
|
self.fifo_space_update.eq(1),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
|
||||||
|
# TX FSM
|
||||||
|
tx_fsm = FSM(reset_state="IDLE")
|
||||||
|
self.submodules += tx_fsm
|
||||||
|
|
||||||
|
tx_fsm.act("IDLE",
|
||||||
|
If(echo_req, NextState("ECHO")),
|
||||||
|
If(fifo_space_req, NextState("FIFO_SPACE")),
|
||||||
|
If(self.write_overflow, NextState("ERROR_WRITE_OVERFLOW")),
|
||||||
|
If(self.write_underflow, NextState("ERROR_WRITE_UNDERFLOW")),
|
||||||
|
If(err_req, NextState("ERROR"))
|
||||||
|
)
|
||||||
|
tx_fsm.act("ECHO",
|
||||||
|
tx_dp.send("echo_reply"),
|
||||||
|
If(tx_dp.packet_last, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
tx_fsm.act("FIFO_SPACE",
|
||||||
|
fifo_space_ack.eq(1),
|
||||||
|
tx_dp.send("fifo_space_reply", space=self.fifo_space),
|
||||||
|
If(tx_dp.packet_last, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
tx_fsm.act("ERROR_WRITE_OVERFLOW",
|
||||||
|
self.write_overflow_ack.eq(1),
|
||||||
|
tx_dp.send("error", code=error_codes["write_overflow"]),
|
||||||
|
If(tx_dp.packet_last, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
tx_fsm.act("ERROR_WRITE_UNDERFLOW",
|
||||||
|
self.write_underflow_ack.eq(1),
|
||||||
|
tx_dp.send("error", code=error_codes["write_underflow"]),
|
||||||
|
If(tx_dp.packet_last, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
tx_fsm.act("ERROR",
|
||||||
|
err_ack.eq(1),
|
||||||
|
tx_dp.send("error", code=err_code),
|
||||||
|
If(tx_dp.packet_last, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _CrossDomainRequest(Module):
|
||||||
|
def __init__(self, domain,
|
||||||
|
req_stb, req_ack, req_data,
|
||||||
|
srv_stb, srv_ack, srv_data):
|
||||||
|
dsync = getattr(self.sync, domain)
|
||||||
|
|
||||||
|
request = PulseSynchronizer("sys", domain)
|
||||||
|
reply = PulseSynchronizer(domain, "sys")
|
||||||
|
self.submodules += request, reply
|
||||||
|
|
||||||
|
ongoing = Signal()
|
||||||
|
self.comb += request.i.eq(~ongoing & req_stb)
|
||||||
|
self.sync += [
|
||||||
|
req_ack.eq(reply.o),
|
||||||
|
If(req_stb, ongoing.eq(1)),
|
||||||
|
If(req_ack, ongoing.eq(0))
|
||||||
|
]
|
||||||
|
if req_data is not None:
|
||||||
|
req_data_r = Signal.like(req_data)
|
||||||
|
req_data_r.attr.add("no_retiming")
|
||||||
|
self.sync += If(req_stb, req_data_r.eq(req_data))
|
||||||
|
dsync += [
|
||||||
|
If(request.o, srv_stb.eq(1)),
|
||||||
|
If(srv_ack, srv_stb.eq(0))
|
||||||
|
]
|
||||||
|
if req_data is not None:
|
||||||
|
dsync += If(request.o, srv_data.eq(req_data_r))
|
||||||
|
self.comb += reply.i.eq(srv_stb & srv_ack)
|
||||||
|
|
||||||
|
|
||||||
|
class _CrossDomainNotification(Module):
|
||||||
|
def __init__(self, domain,
|
||||||
|
emi_stb, emi_data,
|
||||||
|
rec_stb, rec_ack, rec_data):
|
||||||
|
emi_data_r = Signal.like(emi_data)
|
||||||
|
emi_data_r.attr.add("no_retiming")
|
||||||
|
dsync = getattr(self.sync, domain)
|
||||||
|
dsync += If(emi_stb, emi_data_r.eq(emi_data))
|
||||||
|
|
||||||
|
ps = PulseSynchronizer(domain, "sys")
|
||||||
|
self.submodules += ps
|
||||||
|
self.comb += ps.i.eq(emi_stb)
|
||||||
|
self.sync += [
|
||||||
|
If(rec_ack, rec_stb.eq(0)),
|
||||||
|
If(ps.o,
|
||||||
|
rec_data.eq(emi_data_r),
|
||||||
|
rec_stb.eq(1)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RTPacketMaster(Module):
|
||||||
|
def __init__(self, link_layer, write_fifo_depth=4):
|
||||||
|
# all interface signals in sys domain unless otherwise specified
|
||||||
|
|
||||||
|
# write interface, optimized for throughput
|
||||||
|
self.write_stb = Signal()
|
||||||
|
self.write_ack = Signal()
|
||||||
|
self.write_timestamp = Signal(64)
|
||||||
|
self.write_channel = Signal(16)
|
||||||
|
self.write_address = Signal(16)
|
||||||
|
self.write_data = Signal(512)
|
||||||
|
|
||||||
|
# fifo space interface
|
||||||
|
# write with timestamp[48:] == 0xffff to make a fifo space request
|
||||||
|
# (space requests have to be ordered wrt writes)
|
||||||
|
self.fifo_space_not = Signal()
|
||||||
|
self.fifo_space_not_ack = Signal()
|
||||||
|
self.fifo_space = Signal(16)
|
||||||
|
|
||||||
|
# echo interface
|
||||||
|
self.echo_stb = Signal()
|
||||||
|
self.echo_ack = Signal()
|
||||||
|
self.echo_sent_now = Signal() # in rtio domain
|
||||||
|
self.echo_received_now = Signal() # in rtio_rx domain
|
||||||
|
|
||||||
|
# set_time interface
|
||||||
|
self.set_time_stb = Signal()
|
||||||
|
self.set_time_ack = Signal()
|
||||||
|
# in rtio domain, must be valid all time while there is
|
||||||
|
# a set_time request pending
|
||||||
|
self.tsc_value = Signal(64)
|
||||||
|
|
||||||
|
# reset interface
|
||||||
|
self.reset_stb = Signal()
|
||||||
|
self.reset_ack = Signal()
|
||||||
|
self.reset_phy = Signal()
|
||||||
|
|
||||||
|
# errors
|
||||||
|
self.error_not = Signal()
|
||||||
|
self.error_not_ack = Signal()
|
||||||
|
self.error_code = Signal(8)
|
||||||
|
|
||||||
|
# packet counters
|
||||||
|
self.packet_cnt_tx = Signal(32)
|
||||||
|
self.packet_cnt_rx = Signal(32)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# 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 FIFO and extra data count
|
||||||
|
wfifo = ClockDomainsRenamer({"write": "sys", "read": "rtio"})(
|
||||||
|
AsyncFIFO(64+16+16+512, write_fifo_depth))
|
||||||
|
self.submodules += wfifo
|
||||||
|
write_timestamp_d = Signal(64)
|
||||||
|
write_channel_d = Signal(16)
|
||||||
|
write_address_d = Signal(16)
|
||||||
|
write_data_d = Signal(512)
|
||||||
|
self.comb += [
|
||||||
|
wfifo.we.eq(self.write_stb),
|
||||||
|
self.write_ack.eq(wfifo.writable),
|
||||||
|
wfifo.din.eq(Cat(self.write_timestamp, self.write_channel,
|
||||||
|
self.write_address, self.write_data)),
|
||||||
|
Cat(write_timestamp_d, write_channel_d,
|
||||||
|
write_address_d, write_data_d).eq(wfifo.dout)
|
||||||
|
]
|
||||||
|
|
||||||
|
wfb_readable = Signal()
|
||||||
|
wfb_re = Signal()
|
||||||
|
|
||||||
|
self.comb += wfifo.re.eq(wfifo.readable & (~wfb_readable | wfb_re))
|
||||||
|
self.sync.rtio += \
|
||||||
|
If(wfifo.re,
|
||||||
|
wfb_readable.eq(1),
|
||||||
|
).Elif(wfb_re,
|
||||||
|
wfb_readable.eq(0),
|
||||||
|
)
|
||||||
|
|
||||||
|
write_timestamp = Signal(64)
|
||||||
|
write_channel = Signal(16)
|
||||||
|
write_address = Signal(16)
|
||||||
|
write_extra_data_cnt = Signal(8)
|
||||||
|
write_data = Signal(512)
|
||||||
|
|
||||||
|
self.sync.rtio += If(wfifo.re,
|
||||||
|
write_timestamp.eq(write_timestamp_d),
|
||||||
|
write_channel.eq(write_channel_d),
|
||||||
|
write_address.eq(write_address_d),
|
||||||
|
write_data.eq(write_data_d))
|
||||||
|
|
||||||
|
short_data_len = tx_plm.field_length("write", "short_data")
|
||||||
|
write_extra_data_d = Signal(512)
|
||||||
|
self.comb += write_extra_data_d.eq(write_data_d[short_data_len:])
|
||||||
|
for i in range(512//ws):
|
||||||
|
self.sync.rtio += If(wfifo.re,
|
||||||
|
If(write_extra_data_d[ws*i:ws*(i+1)] != 0, write_extra_data_cnt.eq(i+1)))
|
||||||
|
|
||||||
|
write_extra_data = Signal(512)
|
||||||
|
self.sync.rtio += If(wfifo.re, write_extra_data.eq(write_extra_data_d))
|
||||||
|
|
||||||
|
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(write_extra_data[i*ws:(i+1)*ws])
|
||||||
|
for i in range(512//ws)}),
|
||||||
|
extra_data_last.eq(extra_data_counter == write_extra_data_cnt)
|
||||||
|
]
|
||||||
|
self.sync.rtio += \
|
||||||
|
If(extra_data_ce,
|
||||||
|
extra_data_counter.eq(extra_data_counter + 1),
|
||||||
|
).Else(
|
||||||
|
extra_data_counter.eq(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# CDC
|
||||||
|
fifo_space_not = Signal()
|
||||||
|
fifo_space = Signal(16)
|
||||||
|
self.submodules += _CrossDomainNotification("rtio_rx",
|
||||||
|
fifo_space_not, fifo_space,
|
||||||
|
self.fifo_space_not, self.fifo_space_not_ack, self.fifo_space)
|
||||||
|
|
||||||
|
set_time_stb = Signal()
|
||||||
|
set_time_ack = Signal()
|
||||||
|
self.submodules += _CrossDomainRequest("rtio",
|
||||||
|
self.set_time_stb, self.set_time_ack, None,
|
||||||
|
set_time_stb, set_time_ack, None)
|
||||||
|
|
||||||
|
reset_stb = Signal()
|
||||||
|
reset_ack = Signal()
|
||||||
|
reset_phy = Signal()
|
||||||
|
self.submodules += _CrossDomainRequest("rtio",
|
||||||
|
self.reset_stb, self.reset_ack, self.reset_phy,
|
||||||
|
reset_stb, reset_ack, reset_phy)
|
||||||
|
|
||||||
|
echo_stb = Signal()
|
||||||
|
echo_ack = Signal()
|
||||||
|
self.submodules += _CrossDomainRequest("rtio",
|
||||||
|
self.echo_stb, self.echo_ack, None,
|
||||||
|
echo_stb, echo_ack, None)
|
||||||
|
|
||||||
|
error_not = Signal()
|
||||||
|
error_code = Signal(8)
|
||||||
|
self.submodules += _CrossDomainNotification("rtio_rx",
|
||||||
|
error_not, error_code,
|
||||||
|
self.error_not, self.error_not_ack, self.error_code)
|
||||||
|
|
||||||
|
# TX FSM
|
||||||
|
tx_fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="IDLE"))
|
||||||
|
self.submodules += tx_fsm
|
||||||
|
|
||||||
|
echo_sent_now = Signal()
|
||||||
|
self.sync.rtio += self.echo_sent_now.eq(echo_sent_now)
|
||||||
|
tsc_value = Signal(64)
|
||||||
|
tsc_value_load = Signal()
|
||||||
|
self.sync.rtio += If(tsc_value_load, tsc_value.eq(self.tsc_value))
|
||||||
|
|
||||||
|
tx_fsm.act("IDLE",
|
||||||
|
If(wfb_readable,
|
||||||
|
If(write_timestamp[48:] == 0xffff,
|
||||||
|
NextState("FIFO_SPACE")
|
||||||
|
).Else(
|
||||||
|
NextState("WRITE")
|
||||||
|
)
|
||||||
|
).Else(
|
||||||
|
If(echo_stb,
|
||||||
|
echo_sent_now.eq(1),
|
||||||
|
NextState("ECHO")
|
||||||
|
).Elif(set_time_stb,
|
||||||
|
tsc_value_load.eq(1),
|
||||||
|
NextState("SET_TIME")
|
||||||
|
).Elif(reset_stb,
|
||||||
|
NextState("RESET")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tx_fsm.act("WRITE",
|
||||||
|
tx_dp.send("write",
|
||||||
|
timestamp=write_timestamp,
|
||||||
|
channel=write_channel,
|
||||||
|
address=write_address,
|
||||||
|
extra_data_cnt=write_extra_data_cnt,
|
||||||
|
short_data=write_data[:short_data_len]),
|
||||||
|
If(tx_dp.packet_last,
|
||||||
|
If(write_extra_data_cnt == 0,
|
||||||
|
wfb_re.eq(1),
|
||||||
|
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,
|
||||||
|
wfb_re.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tx_fsm.act("FIFO_SPACE",
|
||||||
|
tx_dp.send("fifo_space_request", channel=write_channel),
|
||||||
|
If(tx_dp.packet_last,
|
||||||
|
wfb_re.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tx_fsm.act("ECHO",
|
||||||
|
tx_dp.send("echo_request"),
|
||||||
|
If(tx_dp.packet_last,
|
||||||
|
echo_ack.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tx_fsm.act("SET_TIME",
|
||||||
|
tx_dp.send("set_time", timestamp=tsc_value),
|
||||||
|
If(tx_dp.packet_last,
|
||||||
|
set_time_ack.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tx_fsm.act("RESET",
|
||||||
|
tx_dp.send("reset", phy=reset_phy),
|
||||||
|
If(tx_dp.packet_last,
|
||||||
|
reset_ack.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RX FSM
|
||||||
|
rx_fsm = ClockDomainsRenamer("rtio_rx")(FSM(reset_state="INPUT"))
|
||||||
|
self.submodules += rx_fsm
|
||||||
|
|
||||||
|
echo_received_now = Signal()
|
||||||
|
self.sync.rtio_rx += self.echo_received_now.eq(echo_received_now)
|
||||||
|
|
||||||
|
rx_fsm.act("INPUT",
|
||||||
|
If(rx_dp.frame_r,
|
||||||
|
rx_dp.packet_buffer_load.eq(1),
|
||||||
|
If(rx_dp.packet_last,
|
||||||
|
Case(rx_dp.packet_type, {
|
||||||
|
rx_plm.types["error"]: NextState("ERROR"),
|
||||||
|
rx_plm.types["echo_reply"]: echo_received_now.eq(1),
|
||||||
|
rx_plm.types["fifo_space_reply"]: NextState("FIFO_SPACE"),
|
||||||
|
"default": [
|
||||||
|
error_not.eq(1),
|
||||||
|
error_code.eq(error_codes["unknown_type_local"])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rx_fsm.act("ERROR",
|
||||||
|
error_not.eq(1),
|
||||||
|
error_code.eq(rx_dp.packet_as["error"].code),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
rx_fsm.act("FIFO_SPACE",
|
||||||
|
fifo_space_not.eq(1),
|
||||||
|
fifo_space.eq(rx_dp.packet_as["fifo_space_reply"].space),
|
||||||
|
NextState("INPUT")
|
||||||
|
)
|
||||||
|
|
||||||
|
# packet counters
|
||||||
|
tx_frame_r = Signal()
|
||||||
|
packet_cnt_tx = Signal(32)
|
||||||
|
self.sync.rtio += [
|
||||||
|
tx_frame_r.eq(link_layer.tx_rt_frame),
|
||||||
|
If(link_layer.tx_rt_frame & ~tx_frame_r,
|
||||||
|
packet_cnt_tx.eq(packet_cnt_tx + 1))
|
||||||
|
]
|
||||||
|
cdc_packet_cnt_tx = GrayCodeTransfer(32)
|
||||||
|
self.submodules += cdc_packet_cnt_tx
|
||||||
|
self.comb += [
|
||||||
|
cdc_packet_cnt_tx.i.eq(packet_cnt_tx),
|
||||||
|
self.packet_cnt_tx.eq(cdc_packet_cnt_tx.o)
|
||||||
|
]
|
||||||
|
|
||||||
|
rx_frame_r = Signal()
|
||||||
|
packet_cnt_rx = Signal(32)
|
||||||
|
self.sync.rtio_rx += [
|
||||||
|
rx_frame_r.eq(link_layer.rx_rt_frame),
|
||||||
|
If(link_layer.rx_rt_frame & ~rx_frame_r,
|
||||||
|
packet_cnt_rx.eq(packet_cnt_rx + 1))
|
||||||
|
]
|
||||||
|
cdc_packet_cnt_rx = ClockDomainsRenamer({"rtio": "rtio_rx"})(
|
||||||
|
GrayCodeTransfer(32))
|
||||||
|
self.submodules += cdc_packet_cnt_rx
|
||||||
|
self.comb += [
|
||||||
|
cdc_packet_cnt_rx.i.eq(packet_cnt_rx),
|
||||||
|
self.packet_cnt_rx.eq(cdc_packet_cnt_rx.o)
|
||||||
|
]
|
|
@ -0,0 +1,265 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||||
|
|
||||||
|
from misoc.cores.code_8b10b import Encoder, Decoder
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio.transceiver.gtx_7series_init import *
|
||||||
|
|
||||||
|
|
||||||
|
class GTX_20X(Module):
|
||||||
|
# The transceiver clock on clock_pads must be at the RTIO clock
|
||||||
|
# frequency when clock_div2=False, and 2x that frequency when
|
||||||
|
# clock_div2=True.
|
||||||
|
def __init__(self, clock_pads, tx_pads, rx_pads, sys_clk_freq,
|
||||||
|
clock_div2=False):
|
||||||
|
self.submodules.encoder = ClockDomainsRenamer("rtio")(
|
||||||
|
Encoder(2, True))
|
||||||
|
self.decoders = [ClockDomainsRenamer("rtio_rx")(
|
||||||
|
Decoder(True)) for _ in range(2)]
|
||||||
|
self.submodules += self.decoders
|
||||||
|
|
||||||
|
self.rx_ready = Signal()
|
||||||
|
|
||||||
|
# transceiver direct clock outputs
|
||||||
|
# useful to specify clock constraints in a way palatable to Vivado
|
||||||
|
self.txoutclk = Signal()
|
||||||
|
self.rxoutclk = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
refclk = Signal()
|
||||||
|
if clock_div2:
|
||||||
|
self.specials += Instance("IBUFDS_GTE2",
|
||||||
|
i_CEB=0,
|
||||||
|
i_I=clock_pads.p,
|
||||||
|
i_IB=clock_pads.n,
|
||||||
|
o_ODIV2=refclk
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.specials += Instance("IBUFDS_GTE2",
|
||||||
|
i_CEB=0,
|
||||||
|
i_I=clock_pads.p,
|
||||||
|
i_IB=clock_pads.n,
|
||||||
|
o_O=refclk
|
||||||
|
)
|
||||||
|
|
||||||
|
cplllock = Signal()
|
||||||
|
# TX generates RTIO clock, init must be in system domain
|
||||||
|
tx_init = GTXInit(sys_clk_freq, False)
|
||||||
|
# RX receives restart commands from RTIO domain
|
||||||
|
rx_init = ClockDomainsRenamer("rtio")(
|
||||||
|
GTXInit(self.rtio_clk_freq, True))
|
||||||
|
self.submodules += tx_init, rx_init
|
||||||
|
self.comb += tx_init.cplllock.eq(cplllock), \
|
||||||
|
rx_init.cplllock.eq(cplllock)
|
||||||
|
|
||||||
|
txdata = Signal(20)
|
||||||
|
rxdata = Signal(20)
|
||||||
|
self.specials += \
|
||||||
|
Instance("GTXE2_CHANNEL",
|
||||||
|
# PMA Attributes
|
||||||
|
p_PMA_RSV=0x00018480,
|
||||||
|
p_PMA_RSV2=0x2050,
|
||||||
|
p_PMA_RSV3=0,
|
||||||
|
p_PMA_RSV4=0,
|
||||||
|
p_RX_BIAS_CFG=0b100,
|
||||||
|
p_RX_CM_TRIM=0b010,
|
||||||
|
p_RX_OS_CFG=0b10000000,
|
||||||
|
p_RX_CLK25_DIV=5,
|
||||||
|
p_TX_CLK25_DIV=5,
|
||||||
|
|
||||||
|
# Power-Down Attributes
|
||||||
|
p_PD_TRANS_TIME_FROM_P2=0x3c,
|
||||||
|
p_PD_TRANS_TIME_NONE_P2=0x3c,
|
||||||
|
p_PD_TRANS_TIME_TO_P2=0x64,
|
||||||
|
|
||||||
|
# CPLL
|
||||||
|
p_CPLL_CFG=0xBC07DC,
|
||||||
|
p_CPLL_FBDIV=4,
|
||||||
|
p_CPLL_FBDIV_45=5,
|
||||||
|
p_CPLL_REFCLK_DIV=1,
|
||||||
|
p_RXOUT_DIV=2,
|
||||||
|
p_TXOUT_DIV=2,
|
||||||
|
o_CPLLLOCK=cplllock,
|
||||||
|
i_CPLLLOCKEN=1,
|
||||||
|
i_CPLLREFCLKSEL=0b001,
|
||||||
|
i_TSTIN=2**20-1,
|
||||||
|
i_GTREFCLK0=refclk,
|
||||||
|
|
||||||
|
# TX clock
|
||||||
|
p_TXBUF_EN="FALSE",
|
||||||
|
p_TX_XCLK_SEL="TXUSR",
|
||||||
|
o_TXOUTCLK=self.txoutclk,
|
||||||
|
i_TXSYSCLKSEL=0b00,
|
||||||
|
i_TXOUTCLKSEL=0b11,
|
||||||
|
|
||||||
|
# TX Startup/Reset
|
||||||
|
i_GTTXRESET=tx_init.gtXxreset,
|
||||||
|
o_TXRESETDONE=tx_init.Xxresetdone,
|
||||||
|
i_TXDLYSRESET=tx_init.Xxdlysreset,
|
||||||
|
o_TXDLYSRESETDONE=tx_init.Xxdlysresetdone,
|
||||||
|
o_TXPHALIGNDONE=tx_init.Xxphaligndone,
|
||||||
|
i_TXUSERRDY=tx_init.Xxuserrdy,
|
||||||
|
|
||||||
|
# TX data
|
||||||
|
p_TX_DATA_WIDTH=20,
|
||||||
|
p_TX_INT_DATAWIDTH=0,
|
||||||
|
i_TXCHARDISPMODE=Cat(txdata[9], txdata[19]),
|
||||||
|
i_TXCHARDISPVAL=Cat(txdata[8], txdata[18]),
|
||||||
|
i_TXDATA=Cat(txdata[:8], txdata[10:18]),
|
||||||
|
i_TXUSRCLK=ClockSignal("rtio"),
|
||||||
|
i_TXUSRCLK2=ClockSignal("rtio"),
|
||||||
|
|
||||||
|
# TX electrical
|
||||||
|
i_TXBUFDIFFCTRL=0b100,
|
||||||
|
i_TXDIFFCTRL=0b1000,
|
||||||
|
|
||||||
|
# RX Startup/Reset
|
||||||
|
i_GTRXRESET=rx_init.gtXxreset,
|
||||||
|
o_RXRESETDONE=rx_init.Xxresetdone,
|
||||||
|
i_RXDLYSRESET=rx_init.Xxdlysreset,
|
||||||
|
o_RXDLYSRESETDONE=rx_init.Xxdlysresetdone,
|
||||||
|
o_RXPHALIGNDONE=rx_init.Xxphaligndone,
|
||||||
|
i_RXUSERRDY=rx_init.Xxuserrdy,
|
||||||
|
|
||||||
|
# RX AFE
|
||||||
|
p_RX_DFE_XYD_CFG=0,
|
||||||
|
i_RXDFEXYDEN=1,
|
||||||
|
i_RXDFEXYDHOLD=0,
|
||||||
|
i_RXDFEXYDOVRDEN=0,
|
||||||
|
i_RXLPMEN=0,
|
||||||
|
|
||||||
|
# RX clock
|
||||||
|
p_RXBUF_EN="FALSE",
|
||||||
|
p_RX_XCLK_SEL="RXUSR",
|
||||||
|
i_RXDDIEN=1,
|
||||||
|
i_RXSYSCLKSEL=0b00,
|
||||||
|
i_RXOUTCLKSEL=0b010,
|
||||||
|
o_RXOUTCLK=self.rxoutclk,
|
||||||
|
i_RXUSRCLK=ClockSignal("rtio_rx"),
|
||||||
|
i_RXUSRCLK2=ClockSignal("rtio_rx"),
|
||||||
|
p_RXCDR_CFG=0x03000023FF10100020,
|
||||||
|
|
||||||
|
# RX Clock Correction Attributes
|
||||||
|
p_CLK_CORRECT_USE="FALSE",
|
||||||
|
p_CLK_COR_SEQ_1_1=0b0100000000,
|
||||||
|
p_CLK_COR_SEQ_2_1=0b0100000000,
|
||||||
|
p_CLK_COR_SEQ_1_ENABLE=0b1111,
|
||||||
|
p_CLK_COR_SEQ_2_ENABLE=0b1111,
|
||||||
|
|
||||||
|
# RX data
|
||||||
|
p_RX_DATA_WIDTH=20,
|
||||||
|
p_RX_INT_DATAWIDTH=0,
|
||||||
|
o_RXDISPERR=Cat(rxdata[9], rxdata[19]),
|
||||||
|
o_RXCHARISK=Cat(rxdata[8], rxdata[18]),
|
||||||
|
o_RXDATA=Cat(rxdata[:8], rxdata[10:18]),
|
||||||
|
|
||||||
|
# Pads
|
||||||
|
i_GTXRXP=rx_pads.p,
|
||||||
|
i_GTXRXN=rx_pads.n,
|
||||||
|
o_GTXTXP=tx_pads.p,
|
||||||
|
o_GTXTXN=tx_pads.n,
|
||||||
|
)
|
||||||
|
|
||||||
|
tx_reset_deglitched = Signal()
|
||||||
|
tx_reset_deglitched.attr.add("no_retiming")
|
||||||
|
self.sync += tx_reset_deglitched.eq(~tx_init.done)
|
||||||
|
self.clock_domains.cd_rtio = ClockDomain()
|
||||||
|
self.specials += [
|
||||||
|
Instance("BUFG", i_I=self.txoutclk, o_O=self.cd_rtio.clk),
|
||||||
|
AsyncResetSynchronizer(self.cd_rtio, tx_reset_deglitched)
|
||||||
|
]
|
||||||
|
rx_reset_deglitched = Signal()
|
||||||
|
rx_reset_deglitched.attr.add("no_retiming")
|
||||||
|
self.sync.rtio += rx_reset_deglitched.eq(~rx_init.done)
|
||||||
|
self.clock_domains.cd_rtio_rx = ClockDomain()
|
||||||
|
self.specials += [
|
||||||
|
Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_rtio_rx.clk),
|
||||||
|
AsyncResetSynchronizer(self.cd_rtio_rx, rx_reset_deglitched)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1])),
|
||||||
|
self.decoders[0].input.eq(rxdata[:10]),
|
||||||
|
self.decoders[1].input.eq(rxdata[10:])
|
||||||
|
]
|
||||||
|
|
||||||
|
clock_aligner = BruteforceClockAligner(0b0101111100, self.rtio_clk_freq)
|
||||||
|
self.submodules += clock_aligner
|
||||||
|
self.comb += [
|
||||||
|
clock_aligner.rxdata.eq(rxdata),
|
||||||
|
rx_init.restart.eq(clock_aligner.restart),
|
||||||
|
self.rx_ready.eq(clock_aligner.ready)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class GTX_1000BASE_BX10(GTX_20X):
|
||||||
|
rtio_clk_freq = 62.5e6
|
||||||
|
|
||||||
|
|
||||||
|
class GTX_3G(GTX_20X):
|
||||||
|
rtio_clk_freq = 150e6
|
||||||
|
|
||||||
|
|
||||||
|
class RXSynchronizer(Module, AutoCSR):
|
||||||
|
"""Delays the data received in the rtio_rx by a configurable amount
|
||||||
|
so that it meets s/h in the rtio domain, and recapture it in the rtio
|
||||||
|
domain. This has fixed latency.
|
||||||
|
|
||||||
|
Since Xilinx doesn't provide decent on-chip delay lines, we implement the
|
||||||
|
delay with MMCM that provides a clock and a finely configurable phase, used
|
||||||
|
to resample the data.
|
||||||
|
|
||||||
|
The phase has to be determined either empirically or by making sense of the
|
||||||
|
Xilinx scriptures (when existent) and should be constant for a given design
|
||||||
|
placement.
|
||||||
|
"""
|
||||||
|
def __init__(self, rtio_clk_freq, initial_phase=0.0):
|
||||||
|
self.phase_shift = CSR()
|
||||||
|
self.phase_shift_done = CSRStatus()
|
||||||
|
|
||||||
|
self.clock_domains.cd_rtio_delayed = ClockDomain()
|
||||||
|
|
||||||
|
mmcm_output = Signal()
|
||||||
|
mmcm_fb = Signal()
|
||||||
|
mmcm_locked = Signal()
|
||||||
|
# maximize VCO frequency to maximize phase shift resolution
|
||||||
|
mmcm_mult = 1200e6//rtio_clk_freq
|
||||||
|
self.specials += [
|
||||||
|
Instance("MMCME2_ADV",
|
||||||
|
p_CLKIN1_PERIOD=1e9/rtio_clk_freq,
|
||||||
|
i_CLKIN1=ClockSignal("rtio_rx"),
|
||||||
|
i_RST=ResetSignal("rtio_rx"),
|
||||||
|
i_CLKINSEL=1, # yes, 1=CLKIN1 0=CLKIN2
|
||||||
|
|
||||||
|
p_CLKFBOUT_MULT_F=mmcm_mult,
|
||||||
|
p_CLKOUT0_DIVIDE_F=mmcm_mult,
|
||||||
|
p_CLKOUT0_PHASE=initial_phase,
|
||||||
|
p_DIVCLK_DIVIDE=1,
|
||||||
|
|
||||||
|
# According to Xilinx, there is no guarantee of input/output
|
||||||
|
# phase relationship when using internal feedback. We assume
|
||||||
|
# here that the input/ouput skew is constant to save BUFGs.
|
||||||
|
o_CLKFBOUT=mmcm_fb,
|
||||||
|
i_CLKFBIN=mmcm_fb,
|
||||||
|
|
||||||
|
p_CLKOUT0_USE_FINE_PS="TRUE",
|
||||||
|
o_CLKOUT0=mmcm_output,
|
||||||
|
o_LOCKED=mmcm_locked,
|
||||||
|
|
||||||
|
i_PSCLK=ClockSignal(),
|
||||||
|
i_PSEN=self.phase_shift.re,
|
||||||
|
i_PSINCDEC=self.phase_shift.r,
|
||||||
|
o_PSDONE=self.phase_shift_done.status,
|
||||||
|
),
|
||||||
|
Instance("BUFR", i_I=mmcm_output, o_O=self.cd_rtio_delayed.clk),
|
||||||
|
AsyncResetSynchronizer(self.cd_rtio_delayed, ~mmcm_locked)
|
||||||
|
]
|
||||||
|
|
||||||
|
def resync(self, signal):
|
||||||
|
delayed = Signal.like(signal, related=signal)
|
||||||
|
synchronized = Signal.like(signal, related=signal)
|
||||||
|
self.sync.rtio_delayed += delayed.eq(signal)
|
||||||
|
self.sync.rtio += synchronized.eq(delayed)
|
||||||
|
return synchronized
|
|
@ -0,0 +1,226 @@
|
||||||
|
from math import ceil
|
||||||
|
from functools import reduce
|
||||||
|
from operator import add
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.cdc import MultiReg, PulseSynchronizer
|
||||||
|
from migen.genlib.misc import WaitTimer
|
||||||
|
from migen.genlib.fsm import FSM
|
||||||
|
|
||||||
|
|
||||||
|
class GTXInit(Module):
|
||||||
|
# Based on LiteSATA by Enjoy-Digital
|
||||||
|
def __init__(self, sys_clk_freq, rx):
|
||||||
|
self.done = Signal()
|
||||||
|
self.restart = Signal()
|
||||||
|
|
||||||
|
# GTX signals
|
||||||
|
self.cplllock = Signal()
|
||||||
|
self.gtXxreset = Signal()
|
||||||
|
self.Xxresetdone = Signal()
|
||||||
|
self.Xxdlysreset = Signal()
|
||||||
|
self.Xxdlysresetdone = Signal()
|
||||||
|
self.Xxphaligndone = Signal()
|
||||||
|
self.Xxuserrdy = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# Double-latch transceiver asynch outputs
|
||||||
|
cplllock = Signal()
|
||||||
|
Xxresetdone = Signal()
|
||||||
|
Xxdlysresetdone = Signal()
|
||||||
|
Xxphaligndone = Signal()
|
||||||
|
self.specials += [
|
||||||
|
MultiReg(self.cplllock, cplllock),
|
||||||
|
MultiReg(self.Xxresetdone, Xxresetdone),
|
||||||
|
MultiReg(self.Xxdlysresetdone, Xxdlysresetdone),
|
||||||
|
MultiReg(self.Xxphaligndone, Xxphaligndone),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Deglitch FSM outputs driving transceiver asynch inputs
|
||||||
|
gtXxreset = Signal()
|
||||||
|
Xxdlysreset = Signal()
|
||||||
|
Xxuserrdy = Signal()
|
||||||
|
self.sync += [
|
||||||
|
self.gtXxreset.eq(gtXxreset),
|
||||||
|
self.Xxdlysreset.eq(Xxdlysreset),
|
||||||
|
self.Xxuserrdy.eq(Xxuserrdy)
|
||||||
|
]
|
||||||
|
|
||||||
|
# After configuration, transceiver resets have to stay low for
|
||||||
|
# at least 500ns (see AR43482)
|
||||||
|
startup_cycles = ceil(500*sys_clk_freq/1000000000)
|
||||||
|
startup_timer = WaitTimer(startup_cycles)
|
||||||
|
self.submodules += startup_timer
|
||||||
|
|
||||||
|
startup_fsm = FSM(reset_state="INITIAL")
|
||||||
|
self.submodules += startup_fsm
|
||||||
|
|
||||||
|
if rx:
|
||||||
|
cdr_stable_timer = WaitTimer(1024)
|
||||||
|
self.submodules += cdr_stable_timer
|
||||||
|
|
||||||
|
Xxphaligndone_r = Signal(reset=1)
|
||||||
|
Xxphaligndone_rising = Signal()
|
||||||
|
self.sync += Xxphaligndone_r.eq(Xxphaligndone)
|
||||||
|
self.comb += Xxphaligndone_rising.eq(Xxphaligndone & ~Xxphaligndone_r)
|
||||||
|
|
||||||
|
startup_fsm.act("INITIAL",
|
||||||
|
startup_timer.wait.eq(1),
|
||||||
|
If(startup_timer.done, NextState("RESET_GTX"))
|
||||||
|
)
|
||||||
|
startup_fsm.act("RESET_GTX",
|
||||||
|
gtXxreset.eq(1),
|
||||||
|
NextState("WAIT_CPLL")
|
||||||
|
)
|
||||||
|
startup_fsm.act("WAIT_CPLL",
|
||||||
|
gtXxreset.eq(1),
|
||||||
|
If(cplllock, NextState("RELEASE_RESET"))
|
||||||
|
)
|
||||||
|
# Release GTX reset and wait for GTX resetdone
|
||||||
|
# (from UG476, GTX is reset on falling edge
|
||||||
|
# of gttxreset)
|
||||||
|
if rx:
|
||||||
|
startup_fsm.act("RELEASE_RESET",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
cdr_stable_timer.wait.eq(1),
|
||||||
|
If(Xxresetdone & cdr_stable_timer.done, NextState("ALIGN"))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
startup_fsm.act("RELEASE_RESET",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
If(Xxresetdone, NextState("ALIGN"))
|
||||||
|
)
|
||||||
|
# Start delay alignment (pulse)
|
||||||
|
startup_fsm.act("ALIGN",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
Xxdlysreset.eq(1),
|
||||||
|
NextState("WAIT_ALIGN")
|
||||||
|
)
|
||||||
|
# Wait for delay alignment
|
||||||
|
startup_fsm.act("WAIT_ALIGN",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
If(Xxdlysresetdone, NextState("WAIT_FIRST_ALIGN_DONE"))
|
||||||
|
)
|
||||||
|
# Wait 2 rising edges of rxphaligndone
|
||||||
|
# (from UG476 in buffer bypass config)
|
||||||
|
startup_fsm.act("WAIT_FIRST_ALIGN_DONE",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
If(Xxphaligndone_rising, NextState("WAIT_SECOND_ALIGN_DONE"))
|
||||||
|
)
|
||||||
|
startup_fsm.act("WAIT_SECOND_ALIGN_DONE",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
If(Xxphaligndone_rising, NextState("READY"))
|
||||||
|
)
|
||||||
|
startup_fsm.act("READY",
|
||||||
|
Xxuserrdy.eq(1),
|
||||||
|
self.done.eq(1),
|
||||||
|
If(self.restart, NextState("RESET_GTX"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Changes the phase of the transceiver RX clock to align the comma to
|
||||||
|
# the LSBs of RXDATA, fixing the latency.
|
||||||
|
#
|
||||||
|
# This is implemented by repeatedly resetting the transceiver until it
|
||||||
|
# gives out the correct phase. Each reset gives a random phase.
|
||||||
|
#
|
||||||
|
# If Xilinx had designed the GTX transceiver correctly, RXSLIDE_MODE=PMA
|
||||||
|
# would achieve this faster and in a cleaner way. But:
|
||||||
|
# * the phase jumps are of 2 UI at every second RXSLIDE pulse, instead
|
||||||
|
# of 1 UI at every pulse. It is unclear what the latency becomes.
|
||||||
|
# * RXSLIDE_MODE=PMA cannot be used with the RX buffer bypassed.
|
||||||
|
# Those design flaws make RXSLIDE_MODE=PMA yet another broken and useless
|
||||||
|
# transceiver "feature".
|
||||||
|
#
|
||||||
|
# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped
|
||||||
|
# compared to the usual 8b10b binary representation.
|
||||||
|
class BruteforceClockAligner(Module):
|
||||||
|
def __init__(self, comma, rtio_clk_freq, check_period=6e-3):
|
||||||
|
self.rxdata = Signal(20)
|
||||||
|
self.restart = Signal()
|
||||||
|
|
||||||
|
self.ready = Signal()
|
||||||
|
|
||||||
|
check_max_val = ceil(check_period*rtio_clk_freq)
|
||||||
|
check_counter = Signal(max=check_max_val+1)
|
||||||
|
check = Signal()
|
||||||
|
reset_check_counter = Signal()
|
||||||
|
self.sync.rtio += [
|
||||||
|
check.eq(0),
|
||||||
|
If(reset_check_counter,
|
||||||
|
check_counter.eq(check_max_val)
|
||||||
|
).Else(
|
||||||
|
If(check_counter == 0,
|
||||||
|
check.eq(1),
|
||||||
|
check_counter.eq(check_max_val)
|
||||||
|
).Else(
|
||||||
|
check_counter.eq(check_counter-1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
checks_reset = PulseSynchronizer("rtio", "rtio_rx")
|
||||||
|
self.submodules += checks_reset
|
||||||
|
|
||||||
|
comma_n = ~comma & 0b1111111111
|
||||||
|
comma_seen_rxclk = Signal()
|
||||||
|
comma_seen = Signal()
|
||||||
|
comma_seen_rxclk.attr.add("no_retiming")
|
||||||
|
self.specials += MultiReg(comma_seen_rxclk, comma_seen)
|
||||||
|
self.sync.rtio_rx += \
|
||||||
|
If(checks_reset.o,
|
||||||
|
comma_seen_rxclk.eq(0)
|
||||||
|
).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n),
|
||||||
|
comma_seen_rxclk.eq(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
error_seen_rxclk = Signal()
|
||||||
|
error_seen = Signal()
|
||||||
|
error_seen_rxclk.attr.add("no_retiming")
|
||||||
|
self.specials += MultiReg(error_seen_rxclk, error_seen)
|
||||||
|
rx1cnt = Signal(max=11)
|
||||||
|
self.sync.rtio_rx += [
|
||||||
|
rx1cnt.eq(reduce(add, [self.rxdata[i] for i in range(10)])),
|
||||||
|
If(checks_reset.o,
|
||||||
|
error_seen_rxclk.eq(0)
|
||||||
|
).Elif((rx1cnt != 4) & (rx1cnt != 5) & (rx1cnt != 6),
|
||||||
|
error_seen_rxclk.eq(1)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="WAIT_COMMA"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("WAIT_COMMA",
|
||||||
|
If(check,
|
||||||
|
# Errors are still OK at this stage, as the transceiver
|
||||||
|
# has just been reset and may output garbage data.
|
||||||
|
If(comma_seen,
|
||||||
|
NextState("WAIT_NOERROR")
|
||||||
|
).Else(
|
||||||
|
self.restart.eq(1)
|
||||||
|
),
|
||||||
|
checks_reset.i.eq(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WAIT_NOERROR",
|
||||||
|
If(check,
|
||||||
|
If(comma_seen & ~error_seen,
|
||||||
|
NextState("READY")
|
||||||
|
).Else(
|
||||||
|
self.restart.eq(1),
|
||||||
|
NextState("WAIT_COMMA")
|
||||||
|
),
|
||||||
|
checks_reset.i.eq(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("READY",
|
||||||
|
reset_check_counter.eq(1),
|
||||||
|
self.ready.eq(1),
|
||||||
|
If(error_seen,
|
||||||
|
checks_reset.i.eq(1),
|
||||||
|
self.restart.eq(1),
|
||||||
|
NextState("WAIT_COMMA")
|
||||||
|
)
|
||||||
|
)
|
|
@ -1,3 +1,5 @@
|
||||||
from artiq.gateware.rtio.core import Channel, LogChannel, RTIO
|
from artiq.gateware.rtio.cri import KernelInitiator, CRIInterconnectShared
|
||||||
|
from artiq.gateware.rtio.core import Channel, LogChannel, Core
|
||||||
from artiq.gateware.rtio.analyzer import Analyzer
|
from artiq.gateware.rtio.analyzer import Analyzer
|
||||||
from artiq.gateware.rtio.moninj import MonInj
|
from artiq.gateware.rtio.moninj import MonInj
|
||||||
|
from artiq.gateware.rtio.dma import DMA
|
||||||
|
|
|
@ -42,7 +42,7 @@ assert layout_len(stopped_layout) == message_len
|
||||||
|
|
||||||
|
|
||||||
class MessageEncoder(Module, AutoCSR):
|
class MessageEncoder(Module, AutoCSR):
|
||||||
def __init__(self, rtio_core, enable):
|
def __init__(self, kcsrs, rtio_counter, enable):
|
||||||
self.source = stream.Endpoint([("data", message_len)])
|
self.source = stream.Endpoint([("data", message_len)])
|
||||||
|
|
||||||
self.overflow = CSRStatus()
|
self.overflow = CSRStatus()
|
||||||
|
@ -50,27 +50,15 @@ class MessageEncoder(Module, AutoCSR):
|
||||||
|
|
||||||
# # #
|
# # #
|
||||||
|
|
||||||
kcsrs = rtio_core.kcsrs
|
|
||||||
|
|
||||||
input_output_stb = Signal()
|
input_output_stb = Signal()
|
||||||
input_output = Record(input_output_layout)
|
input_output = Record(input_output_layout)
|
||||||
if hasattr(kcsrs, "o_data"):
|
o_data = kcsrs.o_data.storage
|
||||||
o_data = kcsrs.o_data.storage
|
o_address = kcsrs.o_address.storage
|
||||||
else:
|
i_data = kcsrs.i_data.status
|
||||||
o_data = 0
|
|
||||||
if hasattr(kcsrs, "o_address"):
|
|
||||||
o_address = kcsrs.o_address.storage
|
|
||||||
else:
|
|
||||||
o_address = 0
|
|
||||||
if hasattr(kcsrs, "i_data"):
|
|
||||||
i_data = kcsrs.i_data.status
|
|
||||||
else:
|
|
||||||
i_data = 0
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
input_output.channel.eq(kcsrs.chan_sel.storage),
|
input_output.channel.eq(kcsrs.chan_sel.storage),
|
||||||
input_output.address_padding.eq(o_address),
|
input_output.address_padding.eq(o_address),
|
||||||
input_output.rtio_counter.eq(
|
input_output.rtio_counter.eq(rtio_counter),
|
||||||
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
|
|
||||||
If(kcsrs.o_we.re,
|
If(kcsrs.o_we.re,
|
||||||
input_output.message_type.eq(MessageType.output.value),
|
input_output.message_type.eq(MessageType.output.value),
|
||||||
input_output.timestamp.eq(kcsrs.o_timestamp.storage),
|
input_output.timestamp.eq(kcsrs.o_timestamp.storage),
|
||||||
|
@ -88,10 +76,10 @@ class MessageEncoder(Module, AutoCSR):
|
||||||
self.comb += [
|
self.comb += [
|
||||||
exception.message_type.eq(MessageType.exception.value),
|
exception.message_type.eq(MessageType.exception.value),
|
||||||
exception.channel.eq(kcsrs.chan_sel.storage),
|
exception.channel.eq(kcsrs.chan_sel.storage),
|
||||||
exception.rtio_counter.eq(
|
exception.rtio_counter.eq(rtio_counter),
|
||||||
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
|
|
||||||
]
|
]
|
||||||
for ename in ("o_underflow_reset", "o_sequence_error_reset",
|
for ename in ("reset", "reset_phy",
|
||||||
|
"o_underflow_reset", "o_sequence_error_reset",
|
||||||
"o_collision_reset", "i_overflow_reset"):
|
"o_collision_reset", "i_overflow_reset"):
|
||||||
self.comb += \
|
self.comb += \
|
||||||
If(getattr(kcsrs, ename).re,
|
If(getattr(kcsrs, ename).re,
|
||||||
|
@ -99,28 +87,11 @@ class MessageEncoder(Module, AutoCSR):
|
||||||
exception.exception_type.eq(
|
exception.exception_type.eq(
|
||||||
getattr(ExceptionType, ename).value)
|
getattr(ExceptionType, ename).value)
|
||||||
)
|
)
|
||||||
for rname in "reset", "reset_phy":
|
|
||||||
r_d = Signal(reset=1)
|
|
||||||
r = getattr(kcsrs, rname).storage
|
|
||||||
self.sync += r_d.eq(r)
|
|
||||||
self.comb += [
|
|
||||||
If(r & ~r_d,
|
|
||||||
exception_stb.eq(1),
|
|
||||||
exception.exception_type.eq(
|
|
||||||
getattr(ExceptionType, rname+"_rising").value)
|
|
||||||
),
|
|
||||||
If(~r & r_d,
|
|
||||||
exception_stb.eq(1),
|
|
||||||
exception.exception_type.eq(
|
|
||||||
getattr(ExceptionType, rname+"_falling").value)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
stopped = Record(stopped_layout)
|
stopped = Record(stopped_layout)
|
||||||
self.comb += [
|
self.comb += [
|
||||||
stopped.message_type.eq(MessageType.stopped.value),
|
stopped.message_type.eq(MessageType.stopped.value),
|
||||||
stopped.rtio_counter.eq(
|
stopped.rtio_counter.eq(rtio_counter),
|
||||||
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
enable_r = Signal()
|
enable_r = Signal()
|
||||||
|
@ -210,13 +181,13 @@ class DMAWriter(Module, AutoCSR):
|
||||||
|
|
||||||
|
|
||||||
class Analyzer(Module, AutoCSR):
|
class Analyzer(Module, AutoCSR):
|
||||||
def __init__(self, rtio_core, membus, fifo_depth=128):
|
def __init__(self, kcsrs, rtio_counter, membus, fifo_depth=128):
|
||||||
# shutdown procedure: set enable to 0, wait until busy=0
|
# shutdown procedure: set enable to 0, wait until busy=0
|
||||||
self.enable = CSRStorage()
|
self.enable = CSRStorage()
|
||||||
self.busy = CSRStatus()
|
self.busy = CSRStatus()
|
||||||
|
|
||||||
self.submodules.message_encoder = MessageEncoder(
|
self.submodules.message_encoder = MessageEncoder(
|
||||||
rtio_core, self.enable.storage)
|
kcsrs, rtio_counter, self.enable.storage)
|
||||||
self.submodules.fifo = stream.SyncFIFO(
|
self.submodules.fifo = stream.SyncFIFO(
|
||||||
[("data", message_len)], fifo_depth, True)
|
[("data", message_len)], fifo_depth, True)
|
||||||
self.submodules.converter = stream.Converter(
|
self.submodules.converter = stream.Converter(
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.cdc import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["GrayCodeTransfer", "RTIOCounter", "BlindTransfer"]
|
||||||
|
|
||||||
|
|
||||||
|
# note: transfer is in rtio/sys domains and not affected by the reset CSRs
|
||||||
|
class GrayCodeTransfer(Module):
|
||||||
|
def __init__(self, width):
|
||||||
|
self.i = Signal(width) # in rtio domain
|
||||||
|
self.o = Signal(width) # in sys domain
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# convert to Gray code
|
||||||
|
value_gray_rtio = Signal(width)
|
||||||
|
self.sync.rtio += value_gray_rtio.eq(self.i ^ self.i[1:])
|
||||||
|
# transfer to system clock domain
|
||||||
|
value_gray_sys = Signal(width)
|
||||||
|
value_gray_rtio.attr.add("no_retiming")
|
||||||
|
self.specials += MultiReg(value_gray_rtio, value_gray_sys)
|
||||||
|
# convert back to binary
|
||||||
|
value_sys = Signal(width)
|
||||||
|
self.comb += value_sys[-1].eq(value_gray_sys[-1])
|
||||||
|
for i in reversed(range(width-1)):
|
||||||
|
self.comb += value_sys[i].eq(value_sys[i+1] ^ value_gray_sys[i])
|
||||||
|
self.sync += self.o.eq(value_sys)
|
||||||
|
|
||||||
|
|
||||||
|
class RTIOCounter(Module):
|
||||||
|
def __init__(self, width):
|
||||||
|
self.width = width
|
||||||
|
# Timestamp counter in RTIO domain
|
||||||
|
self.value_rtio = Signal(width)
|
||||||
|
# Timestamp counter resynchronized to sys domain
|
||||||
|
# Lags behind value_rtio, monotonic and glitch-free
|
||||||
|
self.value_sys = Signal(width)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# note: counter is in rtio domain and never affected by the reset CSRs
|
||||||
|
self.sync.rtio += self.value_rtio.eq(self.value_rtio + 1)
|
||||||
|
gt = GrayCodeTransfer(width)
|
||||||
|
self.submodules += gt
|
||||||
|
self.comb += gt.i.eq(self.value_rtio), self.value_sys.eq(gt.o)
|
||||||
|
|
||||||
|
|
||||||
|
class BlindTransfer(Module):
|
||||||
|
def __init__(self):
|
||||||
|
self.i = Signal()
|
||||||
|
self.o = Signal()
|
||||||
|
|
||||||
|
ps = PulseSynchronizer("rio", "rsys")
|
||||||
|
ps_ack = PulseSynchronizer("rsys", "rio")
|
||||||
|
self.submodules += ps, ps_ack
|
||||||
|
blind = Signal()
|
||||||
|
self.sync.rio += [
|
||||||
|
If(self.i, blind.eq(1)),
|
||||||
|
If(ps_ack.o, blind.eq(0))
|
||||||
|
]
|
||||||
|
self.comb += [
|
||||||
|
ps.i.eq(self.i & ~blind),
|
||||||
|
ps_ack.i.eq(ps.o),
|
||||||
|
self.o.eq(ps.o)
|
||||||
|
]
|
|
@ -3,73 +3,11 @@ from operator import and_
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
from migen.genlib.record import Record
|
from migen.genlib.record import Record
|
||||||
from migen.genlib.cdc import *
|
|
||||||
from migen.genlib.fifo import AsyncFIFO
|
from migen.genlib.fifo import AsyncFIFO
|
||||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||||
from misoc.interconnect.csr import *
|
|
||||||
|
|
||||||
from artiq.gateware.rtio import rtlink
|
from artiq.gateware.rtio import cri, rtlink
|
||||||
|
from artiq.gateware.rtio.cdc import *
|
||||||
|
|
||||||
# note: transfer is in rtio/sys domains and not affected by the reset CSRs
|
|
||||||
class _GrayCodeTransfer(Module):
|
|
||||||
def __init__(self, width):
|
|
||||||
self.i = Signal(width) # in rtio domain
|
|
||||||
self.o = Signal(width) # in sys domain
|
|
||||||
|
|
||||||
# # #
|
|
||||||
|
|
||||||
# convert to Gray code
|
|
||||||
value_gray_rtio = Signal(width)
|
|
||||||
self.sync.rtio += value_gray_rtio.eq(self.i ^ self.i[1:])
|
|
||||||
# transfer to system clock domain
|
|
||||||
value_gray_sys = Signal(width)
|
|
||||||
value_gray_rtio.attr.add("no_retiming")
|
|
||||||
self.specials += MultiReg(value_gray_rtio, value_gray_sys)
|
|
||||||
# convert back to binary
|
|
||||||
value_sys = Signal(width)
|
|
||||||
self.comb += value_sys[-1].eq(value_gray_sys[-1])
|
|
||||||
for i in reversed(range(width-1)):
|
|
||||||
self.comb += value_sys[i].eq(value_sys[i+1] ^ value_gray_sys[i])
|
|
||||||
self.sync += self.o.eq(value_sys)
|
|
||||||
|
|
||||||
|
|
||||||
class _RTIOCounter(Module):
|
|
||||||
def __init__(self, width):
|
|
||||||
self.width = width
|
|
||||||
# Timestamp counter in RTIO domain
|
|
||||||
self.value_rtio = Signal(width)
|
|
||||||
# Timestamp counter resynchronized to sys domain
|
|
||||||
# Lags behind value_rtio, monotonic and glitch-free
|
|
||||||
self.value_sys = Signal(width)
|
|
||||||
|
|
||||||
# # #
|
|
||||||
|
|
||||||
# note: counter is in rtio domain and never affected by the reset CSRs
|
|
||||||
self.sync.rtio += self.value_rtio.eq(self.value_rtio + 1)
|
|
||||||
gt = _GrayCodeTransfer(width)
|
|
||||||
self.submodules += gt
|
|
||||||
self.comb += gt.i.eq(self.value_rtio), self.value_sys.eq(gt.o)
|
|
||||||
|
|
||||||
|
|
||||||
class _BlindTransfer(Module):
|
|
||||||
def __init__(self):
|
|
||||||
self.i = Signal()
|
|
||||||
self.o = Signal()
|
|
||||||
|
|
||||||
ps = PulseSynchronizer("rio", "rsys")
|
|
||||||
ps_ack = PulseSynchronizer("rsys", "rio")
|
|
||||||
self.submodules += ps, ps_ack
|
|
||||||
blind = Signal()
|
|
||||||
self.sync.rio += [
|
|
||||||
If(self.i, blind.eq(1)),
|
|
||||||
If(ps_ack.o, blind.eq(0))
|
|
||||||
]
|
|
||||||
self.comb += [
|
|
||||||
ps.i.eq(self.i & ~blind),
|
|
||||||
ps_ack.i.eq(ps.o),
|
|
||||||
self.o.eq(ps.o)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# CHOOSING A GUARD TIME
|
# CHOOSING A GUARD TIME
|
||||||
|
@ -227,7 +165,7 @@ class _OutputManager(Module):
|
||||||
interface.stb.eq(dout_stb & dout_ack)
|
interface.stb.eq(dout_stb & dout_ack)
|
||||||
]
|
]
|
||||||
|
|
||||||
busy_transfer = _BlindTransfer()
|
busy_transfer = BlindTransfer()
|
||||||
self.submodules += busy_transfer
|
self.submodules += busy_transfer
|
||||||
self.comb += [
|
self.comb += [
|
||||||
busy_transfer.i.eq(interface.stb & interface.busy),
|
busy_transfer.i.eq(interface.stb & interface.busy),
|
||||||
|
@ -289,7 +227,7 @@ class _InputManager(Module):
|
||||||
fifo.re.eq(self.re)
|
fifo.re.eq(self.re)
|
||||||
]
|
]
|
||||||
|
|
||||||
overflow_transfer = _BlindTransfer()
|
overflow_transfer = BlindTransfer()
|
||||||
self.submodules += overflow_transfer
|
self.submodules += overflow_transfer
|
||||||
self.comb += [
|
self.comb += [
|
||||||
overflow_transfer.i.eq(fifo.we & ~fifo.writable),
|
overflow_transfer.i.eq(fifo.we & ~fifo.writable),
|
||||||
|
@ -326,81 +264,47 @@ class LogChannel:
|
||||||
self.overrides = []
|
self.overrides = []
|
||||||
|
|
||||||
|
|
||||||
class _KernelCSRs(AutoCSR):
|
class Core(Module):
|
||||||
def __init__(self, chan_sel_width,
|
def __init__(self, channels, fine_ts_width=None, guard_io_cycles=20):
|
||||||
data_width, address_width, full_ts_width):
|
if fine_ts_width is None:
|
||||||
self.reset = CSRStorage(reset=1)
|
fine_ts_width = max(rtlink.get_fine_ts_width(c.interface)
|
||||||
self.reset_phy = CSRStorage(reset=1)
|
for c in channels)
|
||||||
self.chan_sel = CSRStorage(chan_sel_width)
|
|
||||||
|
|
||||||
if data_width:
|
self.cri = cri.Interface()
|
||||||
self.o_data = CSRStorage(data_width, write_from_dev=True)
|
self.comb += self.cri.arb_gnt.eq(1)
|
||||||
if address_width:
|
|
||||||
self.o_address = CSRStorage(address_width, write_from_dev=True)
|
|
||||||
self.o_timestamp = CSRStorage(full_ts_width)
|
|
||||||
self.o_we = CSR()
|
|
||||||
self.o_status = CSRStatus(5)
|
|
||||||
self.o_underflow_reset = CSR()
|
|
||||||
self.o_sequence_error_reset = CSR()
|
|
||||||
self.o_collision_reset = CSR()
|
|
||||||
self.o_busy_reset = CSR()
|
|
||||||
|
|
||||||
if data_width:
|
|
||||||
self.i_data = CSRStatus(data_width)
|
|
||||||
self.i_timestamp = CSRStatus(full_ts_width)
|
|
||||||
self.i_re = CSR()
|
|
||||||
self.i_status = CSRStatus(2)
|
|
||||||
self.i_overflow_reset = CSR()
|
|
||||||
|
|
||||||
self.counter = CSRStatus(full_ts_width)
|
|
||||||
self.counter_update = CSR()
|
|
||||||
|
|
||||||
|
|
||||||
class RTIO(Module):
|
|
||||||
def __init__(self, channels, full_ts_width=63, guard_io_cycles=20):
|
|
||||||
data_width = max(rtlink.get_data_width(c.interface)
|
|
||||||
for c in channels)
|
|
||||||
address_width = max(rtlink.get_address_width(c.interface)
|
|
||||||
for c in channels)
|
|
||||||
fine_ts_width = max(rtlink.get_fine_ts_width(c.interface)
|
|
||||||
for c in channels)
|
|
||||||
|
|
||||||
self.data_width = data_width
|
|
||||||
self.address_width = address_width
|
|
||||||
self.fine_ts_width = fine_ts_width
|
|
||||||
|
|
||||||
# CSRs
|
|
||||||
self.kcsrs = _KernelCSRs(bits_for(len(channels)-1),
|
|
||||||
data_width, address_width,
|
|
||||||
full_ts_width)
|
|
||||||
|
|
||||||
# Clocking/Reset
|
# Clocking/Reset
|
||||||
# Create rsys, rio and rio_phy domains based on sys and rtio
|
# Create rsys, rio and rio_phy domains based on sys and rtio
|
||||||
# with reset controlled by CSR.
|
# with reset controlled by CRI.
|
||||||
|
cmd_reset = Signal(reset=1)
|
||||||
|
cmd_reset_phy = Signal(reset=1)
|
||||||
|
self.sync += [
|
||||||
|
cmd_reset.eq(self.cri.cmd == cri.commands["reset"]),
|
||||||
|
cmd_reset_phy.eq(self.cri.cmd == cri.commands["reset_phy"])
|
||||||
|
]
|
||||||
|
cmd_reset.attr.add("no_retiming")
|
||||||
|
cmd_reset_phy.attr.add("no_retiming")
|
||||||
|
|
||||||
self.clock_domains.cd_rsys = ClockDomain()
|
self.clock_domains.cd_rsys = ClockDomain()
|
||||||
self.clock_domains.cd_rio = ClockDomain()
|
self.clock_domains.cd_rio = ClockDomain()
|
||||||
self.clock_domains.cd_rio_phy = ClockDomain()
|
self.clock_domains.cd_rio_phy = ClockDomain()
|
||||||
self.comb += [
|
self.comb += [
|
||||||
self.cd_rsys.clk.eq(ClockSignal()),
|
self.cd_rsys.clk.eq(ClockSignal()),
|
||||||
self.cd_rsys.rst.eq(self.kcsrs.reset.storage)
|
self.cd_rsys.rst.eq(cmd_reset)
|
||||||
]
|
]
|
||||||
self.comb += self.cd_rio.clk.eq(ClockSignal("rtio"))
|
self.comb += self.cd_rio.clk.eq(ClockSignal("rtio"))
|
||||||
self.specials += AsyncResetSynchronizer(
|
self.specials += AsyncResetSynchronizer(
|
||||||
self.cd_rio,
|
self.cd_rio, cmd_reset)
|
||||||
self.kcsrs.reset.storage | ResetSignal("rtio",
|
|
||||||
allow_reset_less=True))
|
|
||||||
self.comb += self.cd_rio_phy.clk.eq(ClockSignal("rtio"))
|
self.comb += self.cd_rio_phy.clk.eq(ClockSignal("rtio"))
|
||||||
self.specials += AsyncResetSynchronizer(
|
self.specials += AsyncResetSynchronizer(
|
||||||
self.cd_rio_phy,
|
self.cd_rio_phy, cmd_reset_phy)
|
||||||
self.kcsrs.reset_phy.storage | ResetSignal("rtio",
|
|
||||||
allow_reset_less=True))
|
|
||||||
|
|
||||||
# Managers
|
# Managers
|
||||||
self.submodules.counter = _RTIOCounter(full_ts_width - fine_ts_width)
|
self.submodules.counter = RTIOCounter(len(self.cri.o_timestamp) - fine_ts_width)
|
||||||
|
|
||||||
i_datas, i_timestamps = [], []
|
i_datas, i_timestamps = [], []
|
||||||
o_statuses, i_statuses = [], []
|
o_statuses, i_statuses = [], []
|
||||||
sel = self.kcsrs.chan_sel.storage
|
sel = self.cri.chan_sel[:16]
|
||||||
for n, channel in enumerate(channels):
|
for n, channel in enumerate(channels):
|
||||||
if isinstance(channel, LogChannel):
|
if isinstance(channel, LogChannel):
|
||||||
i_datas.append(0)
|
i_datas.append(0)
|
||||||
|
@ -416,30 +320,26 @@ class RTIO(Module):
|
||||||
self.submodules += o_manager
|
self.submodules += o_manager
|
||||||
|
|
||||||
if hasattr(o_manager.ev, "data"):
|
if hasattr(o_manager.ev, "data"):
|
||||||
self.comb += o_manager.ev.data.eq(
|
self.comb += o_manager.ev.data.eq(self.cri.o_data)
|
||||||
self.kcsrs.o_data.storage)
|
|
||||||
if hasattr(o_manager.ev, "address"):
|
if hasattr(o_manager.ev, "address"):
|
||||||
self.comb += o_manager.ev.address.eq(
|
self.comb += o_manager.ev.address.eq(self.cri.o_address)
|
||||||
self.kcsrs.o_address.storage)
|
ts_shift = len(self.cri.o_timestamp) - len(o_manager.ev.timestamp)
|
||||||
ts_shift = (len(self.kcsrs.o_timestamp.storage)
|
self.comb += o_manager.ev.timestamp.eq(self.cri.o_timestamp[ts_shift:])
|
||||||
- len(o_manager.ev.timestamp))
|
|
||||||
self.comb += o_manager.ev.timestamp.eq(
|
|
||||||
self.kcsrs.o_timestamp.storage[ts_shift:])
|
|
||||||
|
|
||||||
self.comb += o_manager.we.eq(selected & self.kcsrs.o_we.re)
|
self.comb += o_manager.we.eq(selected & (self.cri.cmd == cri.commands["write"]))
|
||||||
|
|
||||||
underflow = Signal()
|
underflow = Signal()
|
||||||
sequence_error = Signal()
|
sequence_error = Signal()
|
||||||
collision = Signal()
|
collision = Signal()
|
||||||
busy = Signal()
|
busy = Signal()
|
||||||
self.sync.rsys += [
|
self.sync.rsys += [
|
||||||
If(selected & self.kcsrs.o_underflow_reset.re,
|
If(selected & (self.cri.cmd == cri.commands["o_underflow_reset"]),
|
||||||
underflow.eq(0)),
|
underflow.eq(0)),
|
||||||
If(selected & self.kcsrs.o_sequence_error_reset.re,
|
If(selected & (self.cri.cmd == cri.commands["o_sequence_error_reset"]),
|
||||||
sequence_error.eq(0)),
|
sequence_error.eq(0)),
|
||||||
If(selected & self.kcsrs.o_collision_reset.re,
|
If(selected & (self.cri.cmd == cri.commands["o_collision_reset"]),
|
||||||
collision.eq(0)),
|
collision.eq(0)),
|
||||||
If(selected & self.kcsrs.o_busy_reset.re,
|
If(selected & (self.cri.cmd == cri.commands["o_busy_reset"]),
|
||||||
busy.eq(0)),
|
busy.eq(0)),
|
||||||
If(o_manager.underflow, underflow.eq(1)),
|
If(o_manager.underflow, underflow.eq(1)),
|
||||||
If(o_manager.sequence_error, sequence_error.eq(1)),
|
If(o_manager.sequence_error, sequence_error.eq(1)),
|
||||||
|
@ -462,17 +362,16 @@ class RTIO(Module):
|
||||||
else:
|
else:
|
||||||
i_datas.append(0)
|
i_datas.append(0)
|
||||||
if channel.interface.i.timestamped:
|
if channel.interface.i.timestamped:
|
||||||
ts_shift = (len(self.kcsrs.i_timestamp.status)
|
ts_shift = (len(self.cri.i_timestamp) - len(i_manager.ev.timestamp))
|
||||||
- len(i_manager.ev.timestamp))
|
|
||||||
i_timestamps.append(i_manager.ev.timestamp << ts_shift)
|
i_timestamps.append(i_manager.ev.timestamp << ts_shift)
|
||||||
else:
|
else:
|
||||||
i_timestamps.append(0)
|
i_timestamps.append(0)
|
||||||
|
|
||||||
self.comb += i_manager.re.eq(selected & self.kcsrs.i_re.re)
|
self.comb += i_manager.re.eq(selected & (self.cri.cmd == cri.commands["read"]))
|
||||||
|
|
||||||
overflow = Signal()
|
overflow = Signal()
|
||||||
self.sync.rsys += [
|
self.sync.rsys += [
|
||||||
If(selected & self.kcsrs.i_overflow_reset.re,
|
If(selected & (self.cri.cmd == cri.commands["i_overflow_reset"]),
|
||||||
overflow.eq(0)),
|
overflow.eq(0)),
|
||||||
If(i_manager.overflow,
|
If(i_manager.overflow,
|
||||||
overflow.eq(1))
|
overflow.eq(1))
|
||||||
|
@ -483,28 +382,11 @@ class RTIO(Module):
|
||||||
i_datas.append(0)
|
i_datas.append(0)
|
||||||
i_timestamps.append(0)
|
i_timestamps.append(0)
|
||||||
i_statuses.append(0)
|
i_statuses.append(0)
|
||||||
if data_width:
|
|
||||||
self.comb += self.kcsrs.i_data.status.eq(Array(i_datas)[sel])
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
self.kcsrs.i_timestamp.status.eq(Array(i_timestamps)[sel]),
|
self.cri.i_data.eq(Array(i_datas)[sel]),
|
||||||
self.kcsrs.o_status.status.eq(Array(o_statuses)[sel]),
|
self.cri.i_timestamp.eq(Array(i_timestamps)[sel]),
|
||||||
self.kcsrs.i_status.status.eq(Array(i_statuses)[sel])
|
self.cri.o_status.eq(Array(o_statuses)[sel]),
|
||||||
|
self.cri.i_status.eq(Array(i_statuses)[sel])
|
||||||
]
|
]
|
||||||
|
|
||||||
# Counter access
|
self.comb += self.cri.counter.eq(self.counter.value_sys << fine_ts_width)
|
||||||
self.sync += \
|
|
||||||
If(self.kcsrs.counter_update.re,
|
|
||||||
self.kcsrs.counter.status.eq(self.counter.value_sys
|
|
||||||
<< fine_ts_width)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Auto clear/zero pad event data
|
|
||||||
self.comb += [
|
|
||||||
self.kcsrs.o_data.dat_w.eq(0),
|
|
||||||
self.kcsrs.o_data.we.eq(self.kcsrs.o_timestamp.re),
|
|
||||||
self.kcsrs.o_address.dat_w.eq(0),
|
|
||||||
self.kcsrs.o_address.we.eq(self.kcsrs.o_timestamp.re),
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_csrs(self):
|
|
||||||
return self.kcsrs.get_csrs()
|
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
"""Common RTIO Interface"""
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.record import *
|
||||||
|
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
|
||||||
|
|
||||||
|
commands = {
|
||||||
|
"nop": 0,
|
||||||
|
"reset": 1,
|
||||||
|
"reset_phy": 2,
|
||||||
|
|
||||||
|
"write": 3,
|
||||||
|
"read": 4,
|
||||||
|
|
||||||
|
"o_underflow_reset": 5,
|
||||||
|
"o_sequence_error_reset": 6,
|
||||||
|
"o_collision_reset": 7,
|
||||||
|
"o_busy_reset": 8,
|
||||||
|
"i_overflow_reset": 9
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
layout = [
|
||||||
|
("arb_req", 1, DIR_M_TO_S),
|
||||||
|
("arb_gnt", 1, DIR_S_TO_M),
|
||||||
|
|
||||||
|
("cmd", 4, DIR_M_TO_S),
|
||||||
|
# 8 MSBs of chan_sel are used to select core
|
||||||
|
("chan_sel", 24, DIR_M_TO_S),
|
||||||
|
|
||||||
|
("o_data", 512, DIR_M_TO_S),
|
||||||
|
("o_address", 16, DIR_M_TO_S),
|
||||||
|
("o_timestamp", 64, DIR_M_TO_S),
|
||||||
|
# o_status bits:
|
||||||
|
# <0:wait> <1:underflow> <2:sequence_error> <3:collision> <4:busy>
|
||||||
|
("o_status", 5, DIR_S_TO_M),
|
||||||
|
|
||||||
|
("i_data", 32, DIR_S_TO_M),
|
||||||
|
("i_timestamp", 64, DIR_S_TO_M),
|
||||||
|
# i_status bits:
|
||||||
|
# <0:wait> <1:overflow>
|
||||||
|
("i_status", 2, DIR_S_TO_M),
|
||||||
|
|
||||||
|
("counter", 64, DIR_S_TO_M)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Interface(Record):
|
||||||
|
def __init__(self):
|
||||||
|
Record.__init__(self, layout)
|
||||||
|
|
||||||
|
|
||||||
|
class KernelInitiator(Module, AutoCSR):
|
||||||
|
def __init__(self, cri=None):
|
||||||
|
self.arb_req = CSRStorage()
|
||||||
|
self.arb_gnt = CSRStatus()
|
||||||
|
|
||||||
|
self.reset = CSR()
|
||||||
|
self.reset_phy = CSR()
|
||||||
|
self.chan_sel = CSRStorage(24)
|
||||||
|
|
||||||
|
self.o_data = CSRStorage(512, write_from_dev=True)
|
||||||
|
self.o_address = CSRStorage(16)
|
||||||
|
self.o_timestamp = CSRStorage(64)
|
||||||
|
self.o_we = CSR()
|
||||||
|
self.o_status = CSRStatus(5)
|
||||||
|
self.o_underflow_reset = CSR()
|
||||||
|
self.o_sequence_error_reset = CSR()
|
||||||
|
self.o_collision_reset = CSR()
|
||||||
|
self.o_busy_reset = CSR()
|
||||||
|
|
||||||
|
self.i_data = CSRStatus(32)
|
||||||
|
self.i_timestamp = CSRStatus(64)
|
||||||
|
self.i_re = CSR()
|
||||||
|
self.i_status = CSRStatus(2)
|
||||||
|
self.i_overflow_reset = CSR()
|
||||||
|
|
||||||
|
self.counter = CSRStatus(64)
|
||||||
|
self.counter_update = CSR()
|
||||||
|
|
||||||
|
if cri is None:
|
||||||
|
cri = Interface()
|
||||||
|
self.cri = cri
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.cri.arb_req.eq(self.arb_req.storage),
|
||||||
|
self.arb_gnt.status.eq(self.cri.arb_gnt),
|
||||||
|
|
||||||
|
self.cri.cmd.eq(commands["nop"]),
|
||||||
|
If(self.reset.re, self.cri.cmd.eq(commands["reset"])),
|
||||||
|
If(self.reset_phy.re, self.cri.cmd.eq(commands["reset_phy"])),
|
||||||
|
If(self.o_we.re, self.cri.cmd.eq(commands["write"])),
|
||||||
|
If(self.i_re.re, self.cri.cmd.eq(commands["read"])),
|
||||||
|
If(self.o_underflow_reset.re, self.cri.cmd.eq(commands["o_underflow_reset"])),
|
||||||
|
If(self.o_sequence_error_reset.re, self.cri.cmd.eq(commands["o_sequence_error_reset"])),
|
||||||
|
If(self.o_collision_reset.re, self.cri.cmd.eq(commands["o_collision_reset"])),
|
||||||
|
If(self.o_busy_reset.re, self.cri.cmd.eq(commands["o_busy_reset"])),
|
||||||
|
If(self.i_overflow_reset.re, self.cri.cmd.eq(commands["i_overflow_reset"])),
|
||||||
|
|
||||||
|
self.cri.chan_sel.eq(self.chan_sel.storage),
|
||||||
|
|
||||||
|
self.cri.o_data.eq(self.o_data.storage),
|
||||||
|
self.cri.o_address.eq(self.o_address.storage),
|
||||||
|
self.cri.o_timestamp.eq(self.o_timestamp.storage),
|
||||||
|
self.o_status.status.eq(self.cri.o_status),
|
||||||
|
|
||||||
|
self.i_data.status.eq(self.cri.i_data),
|
||||||
|
self.i_timestamp.status.eq(self.cri.i_timestamp),
|
||||||
|
self.i_status.status.eq(self.cri.i_status),
|
||||||
|
|
||||||
|
self.o_data.dat_w.eq(0),
|
||||||
|
self.o_data.we.eq(self.o_timestamp.re),
|
||||||
|
]
|
||||||
|
self.sync += If(self.counter_update.re, self.counter.status.eq(self.cri.counter))
|
||||||
|
|
||||||
|
|
||||||
|
class CRIDecoder(Module):
|
||||||
|
def __init__(self, slaves=2, master=None):
|
||||||
|
if isinstance(slaves, int):
|
||||||
|
slaves = [Interface() for _ in range(slaves)]
|
||||||
|
if master is None:
|
||||||
|
master = Interface()
|
||||||
|
self.slaves = slaves
|
||||||
|
self.master = master
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
selected = Signal(8)
|
||||||
|
self.sync += selected.eq(self.master.chan_sel[16:])
|
||||||
|
|
||||||
|
# master -> slave
|
||||||
|
for n, slave in enumerate(slaves):
|
||||||
|
for name, size, direction in layout:
|
||||||
|
if direction == DIR_M_TO_S and name != "cmd":
|
||||||
|
self.comb += getattr(slave, name).eq(getattr(master, name))
|
||||||
|
self.comb += If(selected == n, slave.cmd.eq(master.cmd))
|
||||||
|
|
||||||
|
# slave -> master
|
||||||
|
cases = dict()
|
||||||
|
for n, slave in enumerate(slaves):
|
||||||
|
cases[n] = []
|
||||||
|
for name, size, direction in layout:
|
||||||
|
if direction == DIR_S_TO_M:
|
||||||
|
cases[n].append(getattr(master, name).eq(getattr(slave, name)))
|
||||||
|
self.comb += Case(selected, cases)
|
||||||
|
|
||||||
|
|
||||||
|
class CRIArbiter(Module):
|
||||||
|
def __init__(self, masters=2, slave=None):
|
||||||
|
if isinstance(masters, int):
|
||||||
|
masters = [Interface() for _ in range(masters)]
|
||||||
|
if slave is None:
|
||||||
|
slave = Interface()
|
||||||
|
self.masters = masters
|
||||||
|
self.slave = slave
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
if len(masters) == 1:
|
||||||
|
self.comb += masters[0].connect(slave)
|
||||||
|
else:
|
||||||
|
selected = Signal(max=len(masters))
|
||||||
|
|
||||||
|
# mux master->slave signals
|
||||||
|
for name, size, direction in layout:
|
||||||
|
if direction == DIR_M_TO_S:
|
||||||
|
choices = Array(getattr(m, name) for m in masters)
|
||||||
|
self.comb += getattr(slave, name).eq(choices[selected])
|
||||||
|
|
||||||
|
# connect slave->master signals
|
||||||
|
for name, size, direction in layout:
|
||||||
|
if direction == DIR_S_TO_M:
|
||||||
|
source = getattr(slave, name)
|
||||||
|
for i, m in enumerate(masters):
|
||||||
|
dest = getattr(m, name)
|
||||||
|
if name == "arb_gnt":
|
||||||
|
self.comb += dest.eq(source & (selected == i))
|
||||||
|
else:
|
||||||
|
self.comb += dest.eq(source)
|
||||||
|
|
||||||
|
# select master
|
||||||
|
self.sync += \
|
||||||
|
If(~slave.arb_req,
|
||||||
|
[If(m.arb_req, selected.eq(i)) for i, m in enumerate(masters)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CRIInterconnectShared(Module):
|
||||||
|
def __init__(self, masters=2, slaves=2):
|
||||||
|
shared = Interface()
|
||||||
|
self.submodules.arbiter = CRIArbiter(masters, shared)
|
||||||
|
self.submodules.decoder = CRIDecoder(slaves, shared)
|
|
@ -0,0 +1,332 @@
|
||||||
|
from migen import *
|
||||||
|
from migen.genlib.record import Record, layout_len
|
||||||
|
from migen.genlib.fsm import FSM
|
||||||
|
from misoc.interconnect.csr import *
|
||||||
|
from misoc.interconnect import stream, wishbone
|
||||||
|
|
||||||
|
from artiq.gateware.rtio import cri
|
||||||
|
|
||||||
|
|
||||||
|
class WishboneReader(Module):
|
||||||
|
def __init__(self, bus=None):
|
||||||
|
if bus is None:
|
||||||
|
bus = wishbone.Interface
|
||||||
|
self.bus = bus
|
||||||
|
|
||||||
|
aw = len(bus.adr)
|
||||||
|
dw = len(bus.dat_w)
|
||||||
|
self.sink = stream.Endpoint([("address", aw)])
|
||||||
|
self.source = stream.Endpoint([("data", dw)])
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
bus_stb = Signal()
|
||||||
|
data_reg_loaded = Signal()
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
bus_stb.eq(self.sink.stb & (~data_reg_loaded | self.source.ack)),
|
||||||
|
bus.cyc.eq(bus_stb),
|
||||||
|
bus.stb.eq(bus_stb),
|
||||||
|
bus.adr.eq(self.sink.address),
|
||||||
|
self.sink.ack.eq(bus.ack),
|
||||||
|
self.source.stb.eq(data_reg_loaded),
|
||||||
|
]
|
||||||
|
self.sync += [
|
||||||
|
If(self.source.ack, data_reg_loaded.eq(0)),
|
||||||
|
If(bus.ack,
|
||||||
|
data_reg_loaded.eq(1),
|
||||||
|
self.source.data.eq(bus.dat_r),
|
||||||
|
self.source.eop.eq(self.sink.eop)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DMAReader(Module, AutoCSR):
|
||||||
|
def __init__(self, membus, enable):
|
||||||
|
aw = len(membus.adr)
|
||||||
|
data_alignment = log2_int(len(membus.dat_w)//8)
|
||||||
|
|
||||||
|
self.submodules.wb_reader = WishboneReader(membus)
|
||||||
|
self.source = self.wb_reader.source
|
||||||
|
|
||||||
|
# All numbers in bytes
|
||||||
|
self.base_address = CSRStorage(aw + data_alignment,
|
||||||
|
alignment_bits=data_alignment)
|
||||||
|
self.last_address = CSRStorage(aw + data_alignment,
|
||||||
|
alignment_bits=data_alignment)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
enable_r = Signal()
|
||||||
|
address = self.wb_reader.sink
|
||||||
|
self.sync += [
|
||||||
|
enable_r.eq(enable),
|
||||||
|
If(enable & ~enable_r,
|
||||||
|
address.address.eq(self.base_address.storage),
|
||||||
|
address.eop.eq(0),
|
||||||
|
address.stb.eq(1)
|
||||||
|
),
|
||||||
|
If(address.stb & address.ack,
|
||||||
|
If(address.eop,
|
||||||
|
address.stb.eq(0)
|
||||||
|
).Else(
|
||||||
|
address.address.eq(address.address + 1),
|
||||||
|
If(~enable | (address.address == self.last_address.storage),
|
||||||
|
address.eop.eq(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RawSlicer(Module):
|
||||||
|
def __init__(self, in_size, out_size, granularity):
|
||||||
|
g = granularity
|
||||||
|
|
||||||
|
self.sink = stream.Endpoint([("data", in_size*g)])
|
||||||
|
self.source = Signal(out_size*g)
|
||||||
|
self.source_stb = Signal()
|
||||||
|
self.source_consume = Signal(max=out_size+1)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# worst-case buffer space required (when loading):
|
||||||
|
# <data being shifted out> <new incoming word> <EOP marker>
|
||||||
|
buf_size = out_size - 1 + in_size + 1
|
||||||
|
buf = Signal(buf_size*g)
|
||||||
|
self.comb += self.source.eq(buf[:out_size])
|
||||||
|
|
||||||
|
level = Signal(max=buf_size+1)
|
||||||
|
next_level = Signal(max=buf_size+1)
|
||||||
|
self.sync += level.eq(next_level)
|
||||||
|
self.comb += next_level.eq(level)
|
||||||
|
|
||||||
|
load_buf = Signal()
|
||||||
|
shift_buf = Signal()
|
||||||
|
|
||||||
|
self.sync += [
|
||||||
|
If(load_buf, Case(level,
|
||||||
|
# note how the MSBs of the buffer are set to 0
|
||||||
|
# (including the EOP marker position)
|
||||||
|
{i: buf[i*g:].eq(self.sink.data)
|
||||||
|
for i in range(out_size)})),
|
||||||
|
If(shift_buf, buf.eq(buf >> self.source_consume*g))
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = FSM(reset_state="FETCH")
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("FETCH",
|
||||||
|
self.sink.ack.eq(1),
|
||||||
|
load_buf.eq(1),
|
||||||
|
If(self.sink.stb,
|
||||||
|
If(self.sink.eop,
|
||||||
|
# insert <granularity> bits of 0 to mark EOP
|
||||||
|
next_level.eq(level + in_size + 1)
|
||||||
|
).Else(
|
||||||
|
next_level.eq(level + in_size)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
If(next_level >= out_size, NextState("OUTPUT"))
|
||||||
|
)
|
||||||
|
fsm.act("OUTPUT",
|
||||||
|
self.source_stb.eq(1),
|
||||||
|
shift_buf.eq(1),
|
||||||
|
next_level.eq(level - self.source_consume),
|
||||||
|
If(next_level < out_size, NextState("FETCH"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
record_layout = [
|
||||||
|
("length", 8), # of whole record (header+data)
|
||||||
|
("channel", 24),
|
||||||
|
("timestamp", 64),
|
||||||
|
("address", 16),
|
||||||
|
("data", 512) # variable length
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RecordConverter(Module):
|
||||||
|
def __init__(self, stream_slicer):
|
||||||
|
self.source = stream.Endpoint(record_layout)
|
||||||
|
|
||||||
|
hdrlen = layout_len(record_layout) - 512
|
||||||
|
record_raw = Record(record_layout)
|
||||||
|
self.comb += [
|
||||||
|
record_raw.raw_bits().eq(stream_slicer.source),
|
||||||
|
|
||||||
|
self.source.channel.eq(record_raw.channel),
|
||||||
|
self.source.timestamp.eq(record_raw.timestamp),
|
||||||
|
self.source.address.eq(record_raw.address),
|
||||||
|
Case(record_raw.length,
|
||||||
|
{hdrlen+i*8: self.source.data.eq(record_raw.data[:])
|
||||||
|
for i in range(512//8)}),
|
||||||
|
|
||||||
|
self.source.stb.eq(stream_slicer.source_stb),
|
||||||
|
self.source.eop.eq(record_raw.length == 0),
|
||||||
|
If(self.source.ack,
|
||||||
|
If(record_raw.length == 0,
|
||||||
|
stream_slicer.source_consume.eq(1)
|
||||||
|
).Else(
|
||||||
|
stream_slicer.source_consume.eq(record_raw.length)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RecordSlicer(Module):
|
||||||
|
def __init__(self, in_size):
|
||||||
|
self.submodules.raw_slicer = RawSlicer(
|
||||||
|
in_size, layout_len(record_layout)//8, 8)
|
||||||
|
self.submodules.record_converter = RecordConverter(self.raw_slicer)
|
||||||
|
self.sink = self.raw_slicer.sink
|
||||||
|
self.source = self.record_converter.source
|
||||||
|
|
||||||
|
|
||||||
|
class TimeOffset(Module, AutoCSR):
|
||||||
|
def __init__(self):
|
||||||
|
self.time_offset = CSRStorage(64)
|
||||||
|
self.source = stream.Endpoint(record_layout)
|
||||||
|
self.sink = stream.Endpoint(record_layout)
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
pipe_ce = Signal()
|
||||||
|
self.sync += \
|
||||||
|
If(pipe_ce,
|
||||||
|
self.sink.payload.connect(self.source.payload,
|
||||||
|
leave_out={"timestamp"}),
|
||||||
|
self.source.payload.timestamp.eq(self.sink.payload.timestamp
|
||||||
|
+ self.time_offset.storage),
|
||||||
|
self.source.stb.eq(self.sink.stb)
|
||||||
|
)
|
||||||
|
self.comb += [
|
||||||
|
pipe_ce.eq(self.source.ack | ~self.source.stb),
|
||||||
|
self.sink.ack.eq(pipe_ce)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CRIMaster(Module, AutoCSR):
|
||||||
|
def __init__(self):
|
||||||
|
self.arb_req = CSRStorage()
|
||||||
|
self.arb_gnt = CSRStatus()
|
||||||
|
|
||||||
|
self.error_status = CSRStatus(5) # same encoding as RTIO status
|
||||||
|
self.error_underflow_reset = CSR()
|
||||||
|
self.error_sequence_error_reset = CSR()
|
||||||
|
self.error_collision_reset = CSR()
|
||||||
|
self.error_busy_reset = CSR()
|
||||||
|
|
||||||
|
self.error_channel = CSRStatus(24)
|
||||||
|
self.error_timestamp = CSRStatus(64)
|
||||||
|
self.error_address = CSRStatus(16)
|
||||||
|
|
||||||
|
self.sink = stream.Endpoint(record_layout)
|
||||||
|
self.cri = cri.Interface()
|
||||||
|
self.busy = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.cri.arb_req.eq(self.arb_req.storage),
|
||||||
|
self.arb_gnt.status.eq(self.cri.arb_gnt)
|
||||||
|
]
|
||||||
|
|
||||||
|
error_set = Signal(4)
|
||||||
|
for i, rcsr in enumerate([self.error_underflow_reset, self.error_sequence_error_reset,
|
||||||
|
self.error_collision_reset, self.error_busy_reset]):
|
||||||
|
# bit 0 is RTIO wait and always 0 here
|
||||||
|
bit = i + 1
|
||||||
|
self.sync += [
|
||||||
|
If(error_set[i],
|
||||||
|
self.error_status.status[bit].eq(1),
|
||||||
|
self.error_channel.status.eq(self.sink.channel),
|
||||||
|
self.error_timestamp.status.eq(self.sink.timestamp),
|
||||||
|
self.error_address.status.eq(self.sink.address)
|
||||||
|
),
|
||||||
|
If(rcsr.re, self.error_status.status[bit].eq(0))
|
||||||
|
]
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.cri.chan_sel.eq(self.sink.channel),
|
||||||
|
self.cri.o_timestamp.eq(self.sink.timestamp),
|
||||||
|
self.cri.o_address.eq(self.sink.address),
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = FSM(reset_state="IDLE")
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(self.error_status.status == 0,
|
||||||
|
If(self.sink.stb, NextState("WRITE"))
|
||||||
|
).Else(
|
||||||
|
# discard all data until errors are acked
|
||||||
|
self.sink.ack.eq(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WRITE",
|
||||||
|
self.busy.eq(1),
|
||||||
|
self.cri.cmd.eq(cri.commands["write"]),
|
||||||
|
NextState("CHECK_STATE")
|
||||||
|
)
|
||||||
|
fsm.act("CHECK_STATE",
|
||||||
|
self.busy.eq(1),
|
||||||
|
If(~self.cri.o_status,
|
||||||
|
self.sink.ack.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
),
|
||||||
|
If(self.cri.o_status[1], NextState("UNDERFLOW")),
|
||||||
|
If(self.cri.o_status[2], NextState("SEQUENCE_ERROR")),
|
||||||
|
If(self.cri.o_status[3], NextState("COLLISION")),
|
||||||
|
If(self.cri.o_status[4], NextState("BUSY"))
|
||||||
|
)
|
||||||
|
for n, name in enumerate(["UNDERFLOW", "SEQUENCE_ERROR",
|
||||||
|
"COLLISION", "BUSY"]):
|
||||||
|
fsm.act(name,
|
||||||
|
self.busy.eq(1),
|
||||||
|
error_set.eq(1 << n),
|
||||||
|
self.cri.cmd.eq(cri.commands["o_" + name.lower() + "_reset"]),
|
||||||
|
self.sink.ack.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DMA(Module):
|
||||||
|
def __init__(self, membus):
|
||||||
|
# shutdown procedure: set enable to 0, wait until busy=0
|
||||||
|
self.enable = CSRStorage()
|
||||||
|
self.busy = CSRStatus()
|
||||||
|
|
||||||
|
self.submodules.dma = DMAReader(membus, self.enable.storage)
|
||||||
|
self.submodules.slicer = RecordSlicer(len(membus.dat_w))
|
||||||
|
self.submodules.time_offset = TimeOffset()
|
||||||
|
self.submodules.cri_master = CRIMaster()
|
||||||
|
self.cri = self.cri_master.cri
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
self.dma.source.connect(self.slicer.sink),
|
||||||
|
self.slicer.source.connect(self.time_offset.sink),
|
||||||
|
self.time_offset.source.connect(self.cri_master.sink)
|
||||||
|
]
|
||||||
|
|
||||||
|
fsm = FSM(reset_state="IDLE")
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(self.enable.storage, NextState("FLOWING"))
|
||||||
|
)
|
||||||
|
fsm.act("FLOWING",
|
||||||
|
self.busy.status.eq(1),
|
||||||
|
If(self.cri_master.sink.stb & self.cri_master.sink.ack & self.cri_master.sink.eop,
|
||||||
|
NextState("WAIT_CRI_MASTER")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WAIT_CRI_MASTER",
|
||||||
|
self.busy.status.eq(1),
|
||||||
|
If(~self.cri_master.busy, NextState("IDLE"))
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_csrs(self):
|
||||||
|
return ([self.enable, self.busy] +
|
||||||
|
self.dma.get_csrs() + self.time_offset.get_csrs() +
|
||||||
|
self.cri_master.get_csrs())
|
|
@ -39,8 +39,9 @@ class AMPSoC:
|
||||||
self.submodules.timer_kernel = timer.Timer()
|
self.submodules.timer_kernel = timer.Timer()
|
||||||
self.register_kernel_cpu_csrdevice("timer_kernel")
|
self.register_kernel_cpu_csrdevice("timer_kernel")
|
||||||
|
|
||||||
def register_kernel_cpu_csrdevice(self, name):
|
def register_kernel_cpu_csrdevice(self, name, csrs=None):
|
||||||
csrs = getattr(self, name).get_csrs()
|
if csrs is None:
|
||||||
|
csrs = getattr(self, name).get_csrs()
|
||||||
bank = wishbone.CSRBank(csrs)
|
bank = wishbone.CSRBank(csrs)
|
||||||
self.submodules += bank
|
self.submodules += bank
|
||||||
self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map[name]),
|
self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map[name]),
|
||||||
|
|
|
@ -101,10 +101,11 @@ _ams101_dac = [
|
||||||
|
|
||||||
class _NIST_Ions(MiniSoC, AMPSoC):
|
class _NIST_Ions(MiniSoC, AMPSoC):
|
||||||
mem_map = {
|
mem_map = {
|
||||||
"timer_kernel": 0x10000000, # (shadow @0x90000000)
|
"timer_kernel": 0x10000000,
|
||||||
"rtio": 0x20000000, # (shadow @0xa0000000)
|
"rtio": 0x20000000,
|
||||||
"i2c": 0x30000000, # (shadow @0xb0000000)
|
"rtio_dma": 0x30000000,
|
||||||
"mailbox": 0x70000000 # (shadow @0xf0000000)
|
"i2c": 0x50000000,
|
||||||
|
"mailbox": 0x70000000
|
||||||
}
|
}
|
||||||
mem_map.update(MiniSoC.mem_map)
|
mem_map.update(MiniSoC.mem_map)
|
||||||
|
|
||||||
|
@ -142,8 +143,14 @@ class _NIST_Ions(MiniSoC, AMPSoC):
|
||||||
def add_rtio(self, rtio_channels):
|
def add_rtio(self, rtio_channels):
|
||||||
self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk)
|
self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk)
|
||||||
self.csr_devices.append("rtio_crg")
|
self.csr_devices.append("rtio_crg")
|
||||||
self.submodules.rtio = rtio.RTIO(rtio_channels)
|
self.submodules.rtio_core = rtio.Core(rtio_channels)
|
||||||
|
self.submodules.rtio = rtio.KernelInitiator()
|
||||||
|
self.submodules.rtio_dma = rtio.DMA(self.get_native_sdram_if())
|
||||||
self.register_kernel_cpu_csrdevice("rtio")
|
self.register_kernel_cpu_csrdevice("rtio")
|
||||||
|
self.register_kernel_cpu_csrdevice("rtio_dma")
|
||||||
|
self.submodules.cri_con = rtio.CRIInterconnectShared(
|
||||||
|
[self.rtio.cri, self.rtio_dma.cri],
|
||||||
|
[self.rtio_core.cri])
|
||||||
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
||||||
self.csr_devices.append("rtio_moninj")
|
self.csr_devices.append("rtio_moninj")
|
||||||
|
|
||||||
|
@ -153,8 +160,8 @@ class _NIST_Ions(MiniSoC, AMPSoC):
|
||||||
self.crg.cd_sys.clk,
|
self.crg.cd_sys.clk,
|
||||||
self.rtio_crg.cd_rtio.clk)
|
self.rtio_crg.cd_rtio.clk)
|
||||||
|
|
||||||
self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio,
|
self.submodules.rtio_analyzer = rtio.Analyzer(
|
||||||
self.get_native_sdram_if())
|
self.rtio, self.rtio_core.cri.counter, self.get_native_sdram_if())
|
||||||
self.csr_devices.append("rtio_analyzer")
|
self.csr_devices.append("rtio_analyzer")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
#!/usr/bin/env python3.5
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.build.generic_platform import *
|
||||||
|
|
||||||
|
from misoc.targets.kc705 import MiniSoC, soc_kc705_args, soc_kc705_argdict
|
||||||
|
from misoc.integration.builder import builder_args, builder_argdict
|
||||||
|
|
||||||
|
from artiq.gateware.soc import AMPSoC, build_artiq_soc
|
||||||
|
from artiq.gateware import rtio
|
||||||
|
from artiq.gateware.rtio.phy import ttl_simple
|
||||||
|
from artiq.gateware.drtio.transceiver import gtx_7series
|
||||||
|
from artiq.gateware.drtio import DRTIOMaster
|
||||||
|
from artiq import __version__ as artiq_version
|
||||||
|
|
||||||
|
|
||||||
|
fmc_clock_io = [
|
||||||
|
("ad9154_refclk", 0,
|
||||||
|
Subsignal("p", Pins("HPC:GBTCLK0_M2C_P")),
|
||||||
|
Subsignal("n", Pins("HPC:GBTCLK0_M2C_N")),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Master(MiniSoC, AMPSoC):
|
||||||
|
mem_map = {
|
||||||
|
"timer_kernel": 0x10000000,
|
||||||
|
"rtio": 0x20000000,
|
||||||
|
"rtio_dma": 0x30000000,
|
||||||
|
"mailbox": 0x70000000
|
||||||
|
}
|
||||||
|
mem_map.update(MiniSoC.mem_map)
|
||||||
|
|
||||||
|
def __init__(self, cfg, medium, **kwargs):
|
||||||
|
MiniSoC.__init__(self,
|
||||||
|
cpu_type="or1k",
|
||||||
|
sdram_controller_type="minicon",
|
||||||
|
l2_size=128*1024,
|
||||||
|
with_timer=False,
|
||||||
|
ident=artiq_version,
|
||||||
|
**kwargs)
|
||||||
|
AMPSoC.__init__(self)
|
||||||
|
|
||||||
|
platform = self.platform
|
||||||
|
|
||||||
|
if medium == "sfp":
|
||||||
|
self.comb += platform.request("sfp_tx_disable_n").eq(1)
|
||||||
|
tx_pads = platform.request("sfp_tx")
|
||||||
|
rx_pads = platform.request("sfp_rx")
|
||||||
|
elif medium == "sma":
|
||||||
|
tx_pads = platform.request("user_sma_mgt_tx")
|
||||||
|
rx_pads = platform.request("user_sma_mgt_rx")
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
if cfg == "simple_gbe":
|
||||||
|
# GTX_1000BASE_BX10 Ethernet compatible, 62.5MHz RTIO clock
|
||||||
|
# simple TTLs
|
||||||
|
self.submodules.transceiver = gtx_7series.GTX_1000BASE_BX10(
|
||||||
|
clock_pads=platform.request("sgmii_clock"),
|
||||||
|
tx_pads=tx_pads,
|
||||||
|
rx_pads=rx_pads,
|
||||||
|
sys_clk_freq=self.clk_freq,
|
||||||
|
clock_div2=True)
|
||||||
|
elif cfg == "sawg_3g":
|
||||||
|
# 3Gb link, 150MHz RTIO clock
|
||||||
|
# with SAWG on local RTIO and AD9154-FMC-EBZ
|
||||||
|
platform.register_extension(fmc_clock_io)
|
||||||
|
self.submodules.transceiver = gtx_7series.GTX_3G(
|
||||||
|
clock_pads=platform.request("ad9154_refclk"),
|
||||||
|
tx_pads=tx_pads,
|
||||||
|
rx_pads=rx_pads,
|
||||||
|
sys_clk_freq=self.clk_freq)
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
self.submodules.drtio = DRTIOMaster(self.transceiver)
|
||||||
|
self.csr_devices.append("drtio")
|
||||||
|
|
||||||
|
rtio_clk_period = 1e9/self.transceiver.rtio_clk_freq
|
||||||
|
platform.add_period_constraint(self.transceiver.txoutclk, rtio_clk_period)
|
||||||
|
platform.add_period_constraint(self.transceiver.rxoutclk, rtio_clk_period)
|
||||||
|
platform.add_false_path_constraints(
|
||||||
|
self.crg.cd_sys.clk,
|
||||||
|
self.transceiver.txoutclk, self.transceiver.rxoutclk)
|
||||||
|
|
||||||
|
rtio_channels = []
|
||||||
|
for i in range(8):
|
||||||
|
phy = ttl_simple.Output(platform.request("user_led", i))
|
||||||
|
self.submodules += phy
|
||||||
|
rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
for sma in "user_sma_gpio_p", "user_sma_gpio_n":
|
||||||
|
phy = ttl_simple.Inout(platform.request(sma))
|
||||||
|
self.submodules += phy
|
||||||
|
rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
self.submodules.rtio_core = rtio.Core(rtio_channels, 3)
|
||||||
|
|
||||||
|
self.submodules.rtio = rtio.KernelInitiator()
|
||||||
|
self.submodules.rtio_dma = rtio.DMA(self.get_native_sdram_if())
|
||||||
|
self.register_kernel_cpu_csrdevice("rtio")
|
||||||
|
self.register_kernel_cpu_csrdevice("rtio_dma")
|
||||||
|
self.submodules.cri_con = rtio.CRIInterconnectShared(
|
||||||
|
[self.rtio.cri, self.rtio_dma.cri],
|
||||||
|
[self.drtio.cri, self.rtio_core.cri])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="ARTIQ with DRTIO on KC705 - Master")
|
||||||
|
builder_args(parser)
|
||||||
|
soc_kc705_args(parser)
|
||||||
|
parser.add_argument("-c", "--config", default="simple_gbe",
|
||||||
|
help="configuration: simple_gbe/sawg_3g "
|
||||||
|
"(default: %(default)s)")
|
||||||
|
parser.add_argument("--medium", default="sfp",
|
||||||
|
help="medium to use for transceiver link: sfp/sma "
|
||||||
|
"(default: %(default)s)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
soc = Master(args.config, args.medium, **soc_kc705_argdict(args))
|
||||||
|
build_artiq_soc(soc, builder_argdict(args))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,220 @@
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
from migen.build.generic_platform import *
|
||||||
|
from migen.build.platforms import kc705
|
||||||
|
|
||||||
|
from misoc.cores.i2c import *
|
||||||
|
from misoc.cores.sequencer import *
|
||||||
|
|
||||||
|
from artiq.gateware import rtio
|
||||||
|
from artiq.gateware.rtio.phy import ttl_simple
|
||||||
|
from artiq.gateware.drtio.transceiver import gtx_7series
|
||||||
|
from artiq.gateware.drtio import DRTIOSatellite
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: parameters for sawg_3g
|
||||||
|
def get_i2c_program(sys_clk_freq):
|
||||||
|
# NOTE: the logical parameters DO NOT MAP to physical values written
|
||||||
|
# into registers. They have to be mapped; see the datasheet.
|
||||||
|
# DSPLLsim reports the logical parameters in the design summary, not
|
||||||
|
# the physical register values (but those are present separately).
|
||||||
|
N1_HS = 6 # 10
|
||||||
|
NC1_LS = 7 # 8
|
||||||
|
N2_HS = 6 # 10
|
||||||
|
N2_LS = 20111 # 20112
|
||||||
|
N31 = 2513 # 2514
|
||||||
|
N32 = 4596 # 4597
|
||||||
|
|
||||||
|
i2c_sequence = [
|
||||||
|
# PCA9548: select channel 7
|
||||||
|
[(0x74 << 1), 1 << 7],
|
||||||
|
# Si5324: configure
|
||||||
|
[(0x68 << 1), 0, 0b01010000], # FREE_RUN=1
|
||||||
|
[(0x68 << 1), 1, 0b11100100], # CK_PRIOR2=1 CK_PRIOR1=0
|
||||||
|
[(0x68 << 1), 2, 0b0010 | (4 << 4)], # BWSEL=4
|
||||||
|
[(0x68 << 1), 3, 0b0101 | 0x10], # SQ_ICAL=1
|
||||||
|
[(0x68 << 1), 4, 0b10010010], # AUTOSEL_REG=b10
|
||||||
|
[(0x68 << 1), 6, 0x07], # SFOUT1_REG=b111
|
||||||
|
[(0x68 << 1), 25, (N1_HS << 5 ) & 0xff],
|
||||||
|
[(0x68 << 1), 31, (NC1_LS >> 16) & 0xff],
|
||||||
|
[(0x68 << 1), 32, (NC1_LS >> 8 ) & 0xff],
|
||||||
|
[(0x68 << 1), 33, (NC1_LS) & 0xff],
|
||||||
|
[(0x68 << 1), 40, (N2_HS << 5 ) & 0xff |
|
||||||
|
(N2_LS >> 16) & 0xff],
|
||||||
|
[(0x68 << 1), 41, (N2_LS >> 8 ) & 0xff],
|
||||||
|
[(0x68 << 1), 42, (N2_LS) & 0xff],
|
||||||
|
[(0x68 << 1), 43, (N31 >> 16) & 0xff],
|
||||||
|
[(0x68 << 1), 44, (N31 >> 8) & 0xff],
|
||||||
|
[(0x68 << 1), 45, (N31) & 0xff],
|
||||||
|
[(0x68 << 1), 46, (N32 >> 16) & 0xff],
|
||||||
|
[(0x68 << 1), 47, (N32 >> 8) & 0xff],
|
||||||
|
[(0x68 << 1), 48, (N32) & 0xff],
|
||||||
|
[(0x68 << 1), 137, 0x01], # FASTLOCK=1
|
||||||
|
[(0x68 << 1), 136, 0x40], # ICAL=1
|
||||||
|
]
|
||||||
|
|
||||||
|
program = [
|
||||||
|
InstWrite(I2C_CONFIG_ADDR, int(sys_clk_freq/1e3)),
|
||||||
|
]
|
||||||
|
for subseq in i2c_sequence:
|
||||||
|
program += [
|
||||||
|
InstWrite(I2C_XFER_ADDR, I2C_START),
|
||||||
|
InstWait(I2C_XFER_ADDR, I2C_IDLE),
|
||||||
|
]
|
||||||
|
for octet in subseq:
|
||||||
|
program += [
|
||||||
|
InstWrite(I2C_XFER_ADDR, I2C_WRITE | octet),
|
||||||
|
InstWait(I2C_XFER_ADDR, I2C_IDLE),
|
||||||
|
]
|
||||||
|
program += [
|
||||||
|
InstWrite(I2C_XFER_ADDR, I2C_STOP),
|
||||||
|
InstWait(I2C_XFER_ADDR, I2C_IDLE),
|
||||||
|
]
|
||||||
|
program += [
|
||||||
|
InstEnd(),
|
||||||
|
]
|
||||||
|
return program
|
||||||
|
|
||||||
|
|
||||||
|
class Si5324ResetClock(Module):
|
||||||
|
def __init__(self, platform, sys_clk_freq):
|
||||||
|
self.si5324_not_ready = Signal(reset=1)
|
||||||
|
|
||||||
|
# minimum reset pulse 1us
|
||||||
|
reset_done = Signal()
|
||||||
|
si5324_rst_n = platform.request("si5324").rst_n
|
||||||
|
reset_val = int(sys_clk_freq*1.1e-6)
|
||||||
|
reset_ctr = Signal(max=reset_val+1, reset=reset_val)
|
||||||
|
self.sync += \
|
||||||
|
If(reset_ctr != 0,
|
||||||
|
reset_ctr.eq(reset_ctr - 1)
|
||||||
|
).Else(
|
||||||
|
si5324_rst_n.eq(1),
|
||||||
|
reset_done.eq(1)
|
||||||
|
)
|
||||||
|
# 10ms after reset to microprocessor access ready
|
||||||
|
ready_val = int(sys_clk_freq*11e-3)
|
||||||
|
ready_ctr = Signal(max=ready_val+1, reset=ready_val)
|
||||||
|
self.sync += \
|
||||||
|
If(reset_done,
|
||||||
|
If(ready_ctr != 0,
|
||||||
|
ready_ctr.eq(ready_ctr - 1)
|
||||||
|
).Else(
|
||||||
|
self.si5324_not_ready.eq(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
si5324_clkin = platform.request("si5324_clkin")
|
||||||
|
self.specials += \
|
||||||
|
Instance("OBUFDS",
|
||||||
|
i_I=ClockSignal("rtio_rx"),
|
||||||
|
o_O=si5324_clkin.p, o_OB=si5324_clkin.n
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fmc_clock_io = [
|
||||||
|
("ad9154_refclk", 0,
|
||||||
|
Subsignal("p", Pins("HPC:GBTCLK0_M2C_P")),
|
||||||
|
Subsignal("n", Pins("HPC:GBTCLK0_M2C_N")),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Satellite(Module):
|
||||||
|
def __init__(self, cfg, medium, toolchain):
|
||||||
|
self.platform = platform = kc705.Platform(toolchain=toolchain)
|
||||||
|
|
||||||
|
rtio_channels = []
|
||||||
|
for i in range(8):
|
||||||
|
phy = ttl_simple.Output(platform.request("user_led", i))
|
||||||
|
self.submodules += phy
|
||||||
|
rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
for sma in "user_sma_gpio_p", "user_sma_gpio_n":
|
||||||
|
phy = ttl_simple.Inout(platform.request(sma))
|
||||||
|
self.submodules += phy
|
||||||
|
rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
|
||||||
|
sys_clock_pads = platform.request("clk156")
|
||||||
|
self.clock_domains.cd_sys = ClockDomain(reset_less=True)
|
||||||
|
self.specials += Instance("IBUFGDS",
|
||||||
|
i_I=sys_clock_pads.p, i_IB=sys_clock_pads.n,
|
||||||
|
o_O=self.cd_sys.clk)
|
||||||
|
sys_clk_freq = 156000000
|
||||||
|
|
||||||
|
i2c_master = I2CMaster(platform.request("i2c"))
|
||||||
|
sequencer = ResetInserter()(Sequencer(get_i2c_program(sys_clk_freq)))
|
||||||
|
si5324_reset_clock = Si5324ResetClock(platform, sys_clk_freq)
|
||||||
|
self.submodules += i2c_master, sequencer, si5324_reset_clock
|
||||||
|
self.comb += [
|
||||||
|
sequencer.bus.connect(i2c_master.bus),
|
||||||
|
sequencer.reset.eq(si5324_reset_clock.si5324_not_ready)
|
||||||
|
]
|
||||||
|
|
||||||
|
if medium == "sfp":
|
||||||
|
self.comb += platform.request("sfp_tx_disable_n").eq(1)
|
||||||
|
tx_pads = platform.request("sfp_tx")
|
||||||
|
rx_pads = platform.request("sfp_rx")
|
||||||
|
elif medium == "sma":
|
||||||
|
tx_pads = platform.request("user_sma_mgt_tx")
|
||||||
|
rx_pads = platform.request("user_sma_mgt_rx")
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
if cfg == "simple_gbe":
|
||||||
|
# GTX_1000BASE_BX10 Ethernet compatible, 62.5MHz RTIO clock
|
||||||
|
# simple TTLs
|
||||||
|
self.submodules.transceiver = gtx_7series.GTX_1000BASE_BX10(
|
||||||
|
clock_pads=platform.request("sgmii_clock"),
|
||||||
|
tx_pads=tx_pads,
|
||||||
|
rx_pads=rx_pads,
|
||||||
|
sys_clk_freq=sys_clk_freq,
|
||||||
|
clock_div2=True)
|
||||||
|
elif cfg == "sawg_3g":
|
||||||
|
# 3Gb link, 150MHz RTIO clock
|
||||||
|
# with SAWG on local RTIO and AD9154-FMC-EBZ
|
||||||
|
platform.register_extension(fmc_clock_io)
|
||||||
|
self.submodules.transceiver = gtx_7series.GTX_3G(
|
||||||
|
clock_pads=platform.request("ad9154_refclk"),
|
||||||
|
tx_pads=tx_pads,
|
||||||
|
rx_pads=rx_pads,
|
||||||
|
sys_clk_freq=sys_clk_freq)
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
self.submodules.rx_synchronizer = gtx_7series.RXSynchronizer(
|
||||||
|
self.transceiver.rtio_clk_freq)
|
||||||
|
self.submodules.drtio = DRTIOSatellite(
|
||||||
|
self.transceiver, self.rx_synchronizer, rtio_channels)
|
||||||
|
|
||||||
|
rtio_clk_period = 1e9/self.transceiver.rtio_clk_freq
|
||||||
|
platform.add_period_constraint(self.transceiver.txoutclk, rtio_clk_period)
|
||||||
|
platform.add_period_constraint(self.transceiver.rxoutclk, rtio_clk_period)
|
||||||
|
platform.add_false_path_constraints(
|
||||||
|
sys_clock_pads,
|
||||||
|
self.transceiver.txoutclk, self.transceiver.rxoutclk)
|
||||||
|
|
||||||
|
|
||||||
|
def build(self, *args, **kwargs):
|
||||||
|
self.platform.build(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="KC705 DRTIO satellite")
|
||||||
|
parser.add_argument("--toolchain", default="vivado",
|
||||||
|
help="FPGA toolchain to use: ise, vivado")
|
||||||
|
parser.add_argument("--output-dir", default="drtiosat_kc705",
|
||||||
|
help="output directory for generated "
|
||||||
|
"source files and binaries")
|
||||||
|
parser.add_argument("-c", "--config", default="simple_gbe",
|
||||||
|
help="configuration: simple_gbe/sawg_3g "
|
||||||
|
"(default: %(default)s)")
|
||||||
|
parser.add_argument("--medium", default="sfp",
|
||||||
|
help="medium to use for transceiver link: sfp/sma "
|
||||||
|
"(default: %(default)s)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
top = Satellite(args.config, args.medium, args.toolchain)
|
||||||
|
top.build(build_dir=args.output_dir)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -217,12 +217,13 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd
|
||||||
rtio_channels.append(rtio.LogChannel())
|
rtio_channels.append(rtio.LogChannel())
|
||||||
|
|
||||||
# RTIO logic
|
# RTIO logic
|
||||||
self.submodules.rtio = rtio.RTIO(rtio_channels)
|
self.submodules.rtio_core = rtio.Core(rtio_channels)
|
||||||
|
self.submodules.rtio = rtio.KernelInitiator(self.rtio_core.cri)
|
||||||
self.register_kernel_cpu_csrdevice("rtio")
|
self.register_kernel_cpu_csrdevice("rtio")
|
||||||
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
|
||||||
self.csr_devices.append("rtio_moninj")
|
self.csr_devices.append("rtio_moninj")
|
||||||
self.submodules.rtio_analyzer = rtio.Analyzer(
|
self.submodules.rtio_analyzer = rtio.Analyzer(
|
||||||
self.rtio, self.get_native_sdram_if())
|
self.rtio, self.rtio_core.cri.counter, self.get_native_sdram_if())
|
||||||
self.csr_devices.append("rtio_analyzer")
|
self.csr_devices.append("rtio_analyzer")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ class MessageType(Enum):
|
||||||
|
|
||||||
|
|
||||||
class ExceptionType(Enum):
|
class ExceptionType(Enum):
|
||||||
reset_rising = 0b000000
|
reset = 0b000000
|
||||||
reset_falling = 0b000001
|
legacy_reset_falling = 0b000001
|
||||||
reset_phy_rising = 0b000010
|
reset_phy = 0b000010
|
||||||
reset_phy_falling = 0b000011
|
legacy_reset_phy_falling = 0b000011
|
||||||
|
|
||||||
o_underflow_reset = 0b010000
|
o_underflow_reset = 0b010000
|
||||||
o_sequence_error_reset = 0b010001
|
o_sequence_error_reset = 0b010001
|
||||||
|
|
|
@ -322,10 +322,7 @@ pub unsafe fn main() {
|
||||||
(mem::transmute::<usize, fn()>(library.lookup("__modinit__")))();
|
(mem::transmute::<usize, fn()>(library.lookup("__modinit__")))();
|
||||||
send(&NowSave(NOW));
|
send(&NowSave(NOW));
|
||||||
|
|
||||||
let typeinfo = library.lookup("typeinfo");
|
attribute_writeback(library.lookup("typeinfo") as *const ());
|
||||||
if typeinfo != 0 {
|
|
||||||
attribute_writeback(typeinfo as *const ())
|
|
||||||
}
|
|
||||||
|
|
||||||
send(&RunFinished);
|
send(&RunFinished);
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@ const RTIO_I_STATUS_OVERFLOW: u32 = 2;
|
||||||
pub extern fn init() {
|
pub extern fn init() {
|
||||||
unsafe {
|
unsafe {
|
||||||
csr::rtio::reset_write(1);
|
csr::rtio::reset_write(1);
|
||||||
csr::rtio::reset_write(0);
|
|
||||||
csr::rtio::reset_phy_write(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
use board::csr;
|
||||||
|
use sched::{Waiter, Spawner};
|
||||||
|
|
||||||
|
fn drtio_link_is_up() -> bool {
|
||||||
|
unsafe {
|
||||||
|
csr::drtio::link_status_read() == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drtio_sync_tsc() {
|
||||||
|
unsafe {
|
||||||
|
csr::drtio::set_time_write(1);
|
||||||
|
while csr::drtio::set_time_read() == 1 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drtio_init_channel(channel: u16) {
|
||||||
|
unsafe {
|
||||||
|
csr::drtio::chan_sel_override_write(channel);
|
||||||
|
csr::drtio::chan_sel_override_en_write(1);
|
||||||
|
|
||||||
|
csr::drtio::o_reset_channel_status_write(1);
|
||||||
|
csr::drtio::o_get_fifo_space_write(1);
|
||||||
|
while csr::drtio::o_wait_read() == 1 {}
|
||||||
|
info!("FIFO space on channel {} is {}", channel, csr::drtio::o_dbg_fifo_space_read());
|
||||||
|
|
||||||
|
csr::drtio::chan_sel_override_en_write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link_thread(waiter: Waiter, _spawner: Spawner) {
|
||||||
|
loop {
|
||||||
|
waiter.until(drtio_link_is_up).unwrap();
|
||||||
|
info!("link RX is up");
|
||||||
|
|
||||||
|
waiter.sleep(600).unwrap();
|
||||||
|
info!("wait for remote side done");
|
||||||
|
|
||||||
|
drtio_sync_tsc();
|
||||||
|
info!("TSC synced");
|
||||||
|
for channel in 0..16 {
|
||||||
|
drtio_init_channel(channel);
|
||||||
|
}
|
||||||
|
info!("link initialization completed");
|
||||||
|
|
||||||
|
waiter.until(|| !drtio_link_is_up()).unwrap();
|
||||||
|
info!("link is down");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drtio_packet_error_present() -> bool {
|
||||||
|
unsafe {
|
||||||
|
csr::drtio::packet_err_present_read() != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drtio_get_packet_error() -> u8 {
|
||||||
|
unsafe {
|
||||||
|
let err = csr::drtio::packet_err_code_read();
|
||||||
|
csr::drtio::packet_err_present_write(1);
|
||||||
|
err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_thread(waiter: Waiter, _spawner: Spawner) {
|
||||||
|
loop {
|
||||||
|
waiter.until(drtio_packet_error_present).unwrap();
|
||||||
|
error!("DRTIO packet error {}", drtio_get_packet_error());
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,6 +80,8 @@ mod session;
|
||||||
mod moninj;
|
mod moninj;
|
||||||
#[cfg(has_rtio_analyzer)]
|
#[cfg(has_rtio_analyzer)]
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
|
#[cfg(has_drtio)]
|
||||||
|
mod drtio;
|
||||||
|
|
||||||
extern {
|
extern {
|
||||||
fn network_init();
|
fn network_init();
|
||||||
|
@ -127,6 +129,11 @@ pub unsafe extern fn rust_main() {
|
||||||
scheduler.spawner().spawn(4096, moninj::thread);
|
scheduler.spawner().spawn(4096, moninj::thread);
|
||||||
#[cfg(has_rtio_analyzer)]
|
#[cfg(has_rtio_analyzer)]
|
||||||
scheduler.spawner().spawn(4096, analyzer::thread);
|
scheduler.spawner().spawn(4096, analyzer::thread);
|
||||||
|
#[cfg(has_drtio)]
|
||||||
|
{
|
||||||
|
scheduler.spawner().spawn(4096, drtio::link_thread);
|
||||||
|
scheduler.spawner().spawn(4096, drtio::error_thread);
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
scheduler.run();
|
scheduler.run();
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use core::{mem, ptr};
|
use core::{mem, ptr};
|
||||||
use core::cell::{Cell, RefCell};
|
use core::cell::RefCell;
|
||||||
use log::{self, Log, LogLevel, LogMetadata, LogRecord, LogLevelFilter};
|
use log::{self, Log, LogLevel, LogMetadata, LogRecord, LogLevelFilter};
|
||||||
use log_buffer::LogBuffer;
|
use log_buffer::LogBuffer;
|
||||||
use clock;
|
use clock;
|
||||||
|
|
||||||
pub struct BufferLogger {
|
pub struct BufferLogger {
|
||||||
buffer: RefCell<LogBuffer<&'static mut [u8]>>,
|
buffer: RefCell<LogBuffer<&'static mut [u8]>>
|
||||||
trace_to_uart: Cell<bool>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Sync for BufferLogger {}
|
unsafe impl Sync for BufferLogger {}
|
||||||
|
@ -16,8 +15,7 @@ static mut LOGGER: *const BufferLogger = ptr::null();
|
||||||
impl BufferLogger {
|
impl BufferLogger {
|
||||||
pub fn new(buffer: &'static mut [u8]) -> BufferLogger {
|
pub fn new(buffer: &'static mut [u8]) -> BufferLogger {
|
||||||
BufferLogger {
|
BufferLogger {
|
||||||
buffer: RefCell::new(LogBuffer::new(buffer)),
|
buffer: RefCell::new(LogBuffer::new(buffer))
|
||||||
trace_to_uart: Cell::new(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,14 +48,6 @@ impl BufferLogger {
|
||||||
pub fn extract<R, F: FnOnce(&str) -> R>(&self, f: F) -> R {
|
pub fn extract<R, F: FnOnce(&str) -> R>(&self, f: F) -> R {
|
||||||
f(self.buffer.borrow_mut().extract())
|
f(self.buffer.borrow_mut().extract())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_trace_to_uart(&self) {
|
|
||||||
if self.trace_to_uart.get() {
|
|
||||||
trace!("disabling tracing to UART; all further trace messages \
|
|
||||||
are sent to core log only");
|
|
||||||
self.trace_to_uart.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Log for BufferLogger {
|
impl Log for BufferLogger {
|
||||||
|
@ -71,10 +61,7 @@ impl Log for BufferLogger {
|
||||||
writeln!(self.buffer.borrow_mut(),
|
writeln!(self.buffer.borrow_mut(),
|
||||||
"[{:12}us] {:>5}({}): {}",
|
"[{:12}us] {:>5}({}): {}",
|
||||||
clock::get_us(), record.level(), record.target(), record.args()).unwrap();
|
clock::get_us(), record.level(), record.target(), record.args()).unwrap();
|
||||||
|
if record.level() <= LogLevel::Info {
|
||||||
// Printing to UART is really slow, so avoid doing that when we have an alternative
|
|
||||||
// route to retrieve the debug messages.
|
|
||||||
if self.trace_to_uart.get() || record.level() <= LogLevel::Info {
|
|
||||||
println!("[{:12}us] {:>5}({}): {}",
|
println!("[{:12}us] {:>5}({}): {}",
|
||||||
clock::get_us(), record.level(), record.target(), record.args());
|
clock::get_us(), record.level(), record.target(), record.args());
|
||||||
}
|
}
|
||||||
|
|
|
@ -583,8 +583,6 @@ pub fn thread(waiter: Waiter, spawner: Spawner) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferLogger::with_instance(|logger| logger.disable_trace_to_uart());
|
|
||||||
|
|
||||||
let addr = SocketAddr::new(IP_ANY, 1381);
|
let addr = SocketAddr::new(IP_ANY, 1381);
|
||||||
let listener = TcpListener::bind(waiter, addr).expect("cannot bind socket");
|
let listener = TcpListener::bind(waiter, addr).expect("cannot bind socket");
|
||||||
listener.set_keepalive(true);
|
listener.set_keepalive(true);
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import unittest
|
||||||
|
import random
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio.link_layer import *
|
||||||
|
from artiq.gateware.drtio.aux_controller import *
|
||||||
|
|
||||||
|
|
||||||
|
class Loopback(Module):
|
||||||
|
def __init__(self, nwords):
|
||||||
|
ks = [Signal() for k in range(nwords)]
|
||||||
|
ds = [Signal(8) for d in range(nwords)]
|
||||||
|
encoder = SimpleNamespace(k=ks, d=ds)
|
||||||
|
decoders = [SimpleNamespace(k=k, d=d) for k, d in zip(ks, ds)]
|
||||||
|
self.submodules.tx = LinkLayerTX(encoder)
|
||||||
|
self.submodules.rx = LinkLayerRX(decoders)
|
||||||
|
|
||||||
|
self.ready = Signal()
|
||||||
|
|
||||||
|
self.tx_aux_frame = self.tx.aux_frame
|
||||||
|
self.tx_aux_data = self.tx.aux_data
|
||||||
|
self.tx_aux_ack = self.tx.aux_ack
|
||||||
|
self.tx_rt_frame = self.tx.rt_frame
|
||||||
|
self.tx_rt_data = self.tx.rt_data
|
||||||
|
|
||||||
|
self.rx_aux_stb = self.rx.aux_stb
|
||||||
|
self.rx_aux_frame = self.rx.aux_frame & self.ready
|
||||||
|
self.rx_aux_data = self.rx.aux_data
|
||||||
|
self.rx_rt_frame = self.rx.rt_frame & self.ready
|
||||||
|
self.rx_rt_data = self.rx.rt_data
|
||||||
|
|
||||||
|
|
||||||
|
class TB(Module):
|
||||||
|
def __init__(self, nwords):
|
||||||
|
self.submodules.link_layer = Loopback(nwords)
|
||||||
|
self.submodules.aux_controller = ClockDomainsRenamer(
|
||||||
|
{"rtio": "sys", "rtio_rx": "sys"})(AuxController(self.link_layer))
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuxController(unittest.TestCase):
|
||||||
|
def test_aux_controller(self):
|
||||||
|
dut = TB(4)
|
||||||
|
|
||||||
|
def link_init():
|
||||||
|
for i in range(8):
|
||||||
|
yield
|
||||||
|
yield dut.link_layer.ready.eq(1)
|
||||||
|
|
||||||
|
def send_packet(packet):
|
||||||
|
for i, d in enumerate(packet):
|
||||||
|
yield from dut.aux_controller.bus.write(i, d)
|
||||||
|
yield from dut.aux_controller.transmitter.aux_tx_length.write(len(packet)*4)
|
||||||
|
yield from dut.aux_controller.transmitter.aux_tx.write(1)
|
||||||
|
yield
|
||||||
|
while (yield from dut.aux_controller.transmitter.aux_tx.read()):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def receive_packet():
|
||||||
|
while not (yield from dut.aux_controller.receiver.aux_rx_present.read()):
|
||||||
|
yield
|
||||||
|
length = yield from dut.aux_controller.receiver.aux_rx_length.read()
|
||||||
|
r = []
|
||||||
|
for i in range(length//4):
|
||||||
|
r.append((yield from dut.aux_controller.bus.read(256+i)))
|
||||||
|
yield from dut.aux_controller.receiver.aux_rx_present.write(1)
|
||||||
|
return r
|
||||||
|
|
||||||
|
prng = random.Random(0)
|
||||||
|
|
||||||
|
def send_and_check_packet():
|
||||||
|
data = [prng.randrange(2**32-1) for _ in range(prng.randrange(1, 16))]
|
||||||
|
yield from send_packet(data)
|
||||||
|
received = yield from receive_packet()
|
||||||
|
self.assertEqual(data, received)
|
||||||
|
|
||||||
|
def sim():
|
||||||
|
yield from link_init()
|
||||||
|
for i in range(8):
|
||||||
|
yield from send_and_check_packet()
|
||||||
|
|
||||||
|
@passive
|
||||||
|
def rt_traffic():
|
||||||
|
while True:
|
||||||
|
while prng.randrange(4):
|
||||||
|
yield
|
||||||
|
yield dut.link_layer.tx_rt_frame.eq(1)
|
||||||
|
yield
|
||||||
|
while prng.randrange(4):
|
||||||
|
yield
|
||||||
|
yield dut.link_layer.tx_rt_frame.eq(0)
|
||||||
|
yield
|
||||||
|
|
||||||
|
run_simulation(dut, [sim(), rt_traffic()])
|
|
@ -0,0 +1,258 @@
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
import random
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio import *
|
||||||
|
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 LargeDataReceiver(Module):
|
||||||
|
def __init__(self, width):
|
||||||
|
self.rtlink = rtlink.Interface(rtlink.OInterface(width))
|
||||||
|
self.received_data = Signal(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.master = DRTIOMaster(self.transceivers.alice)
|
||||||
|
self.submodules.master_ki = rtio.KernelInitiator(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 = LargeDataReceiver(512)
|
||||||
|
rtio_channels = [
|
||||||
|
rtio.Channel.from_phy(self.phy0, ofifo_depth=4),
|
||||||
|
rtio.Channel.from_phy(self.phy1, ofifo_depth=4),
|
||||||
|
rtio.Channel.from_phy(self.phy2, ofifo_depth=4),
|
||||||
|
]
|
||||||
|
self.submodules.satellite = DRTIOSatellite(
|
||||||
|
self.transceivers.bob, rx_synchronizer, rtio_channels)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullStack(unittest.TestCase):
|
||||||
|
clocks = {"sys": 8, "rtio": 5, "rtio_rx": 5, "rio": 5, "rio_phy": 5}
|
||||||
|
|
||||||
|
def test_controller(self):
|
||||||
|
dut = DUT(2)
|
||||||
|
kcsrs = dut.master_ki
|
||||||
|
csrs = dut.master.rt_controller.csrs
|
||||||
|
mgr = dut.master.rt_manager
|
||||||
|
|
||||||
|
ttl_changes = []
|
||||||
|
correct_ttl_changes = [
|
||||||
|
# from test_pulses
|
||||||
|
(203, 0),
|
||||||
|
(208, 0),
|
||||||
|
(208, 1),
|
||||||
|
(214, 1),
|
||||||
|
|
||||||
|
# from test_fifo_space
|
||||||
|
(414, 0),
|
||||||
|
(454, 0),
|
||||||
|
(494, 0),
|
||||||
|
(534, 0),
|
||||||
|
(574, 0),
|
||||||
|
(614, 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
now = 0
|
||||||
|
def delay(dt):
|
||||||
|
nonlocal now
|
||||||
|
now += dt
|
||||||
|
|
||||||
|
def get_fifo_space(channel):
|
||||||
|
yield from csrs.chan_sel_override_en.write(1)
|
||||||
|
yield from csrs.chan_sel_override.write(channel)
|
||||||
|
yield from csrs.o_get_fifo_space.write(1)
|
||||||
|
yield
|
||||||
|
while (yield from csrs.o_wait.read()):
|
||||||
|
yield
|
||||||
|
r = (yield from csrs.o_dbg_fifo_space.read())
|
||||||
|
yield from csrs.chan_sel_override_en.write(0)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def write(channel, data):
|
||||||
|
yield from kcsrs.chan_sel.write(channel)
|
||||||
|
yield from kcsrs.o_timestamp.write(now)
|
||||||
|
yield from kcsrs.o_data.write(data)
|
||||||
|
yield from kcsrs.o_we.write(1)
|
||||||
|
yield
|
||||||
|
status = 1
|
||||||
|
wlen = 0
|
||||||
|
while status:
|
||||||
|
status = yield from kcsrs.o_status.read()
|
||||||
|
if status & 2:
|
||||||
|
yield from kcsrs.o_underflow_reset.write(1)
|
||||||
|
raise RTIOUnderflow
|
||||||
|
if status & 4:
|
||||||
|
yield from kcsrs.o_sequence_error_reset.write(1)
|
||||||
|
raise RTIOSequenceError
|
||||||
|
yield
|
||||||
|
wlen += 1
|
||||||
|
return wlen
|
||||||
|
|
||||||
|
def test_init():
|
||||||
|
yield from get_fifo_space(0)
|
||||||
|
yield from get_fifo_space(1)
|
||||||
|
|
||||||
|
def test_underflow():
|
||||||
|
with self.assertRaises(RTIOUnderflow):
|
||||||
|
yield from write(0, 0)
|
||||||
|
|
||||||
|
def test_pulses():
|
||||||
|
delay(200*8)
|
||||||
|
yield from write(0, 1)
|
||||||
|
delay(5*8)
|
||||||
|
yield from write(0, 0)
|
||||||
|
yield from write(1, 1)
|
||||||
|
delay(6*8)
|
||||||
|
yield from write(1, 0)
|
||||||
|
|
||||||
|
def test_sequence_error():
|
||||||
|
delay(-200*8)
|
||||||
|
with self.assertRaises(RTIOSequenceError):
|
||||||
|
yield from write(0, 1)
|
||||||
|
delay(200*8)
|
||||||
|
|
||||||
|
def test_large_data():
|
||||||
|
correct_large_data = random.Random(0).randrange(2**512-1)
|
||||||
|
self.assertNotEqual((yield dut.phy2.received_data), correct_large_data)
|
||||||
|
delay(10*8)
|
||||||
|
yield from write(2, correct_large_data)
|
||||||
|
for i in range(40):
|
||||||
|
yield
|
||||||
|
self.assertEqual((yield dut.phy2.received_data), correct_large_data)
|
||||||
|
|
||||||
|
def test_fifo_space():
|
||||||
|
delay(200*8)
|
||||||
|
max_wlen = 0
|
||||||
|
for _ in range(3):
|
||||||
|
wlen = yield from write(0, 1)
|
||||||
|
max_wlen = max(max_wlen, wlen)
|
||||||
|
delay(40*8)
|
||||||
|
wlen = yield from write(0, 0)
|
||||||
|
max_wlen = max(max_wlen, wlen)
|
||||||
|
delay(40*8)
|
||||||
|
# check that some writes caused FIFO space requests
|
||||||
|
self.assertGreater(max_wlen, 5)
|
||||||
|
|
||||||
|
def test_fifo_emptied():
|
||||||
|
# wait for all TTL events to execute
|
||||||
|
while len(ttl_changes) < len(correct_ttl_changes):
|
||||||
|
yield
|
||||||
|
# check "last timestamp passed" FIFO empty condition
|
||||||
|
delay(1000*8)
|
||||||
|
wlen = yield from write(0, 1)
|
||||||
|
self.assertEqual(wlen, 2)
|
||||||
|
|
||||||
|
def test_tsc_error():
|
||||||
|
err_present = yield from mgr.packet_err_present.read()
|
||||||
|
self.assertEqual(err_present, 0)
|
||||||
|
yield from csrs.tsc_correction.write(100000000)
|
||||||
|
yield from csrs.set_time.write(1)
|
||||||
|
for i in range(15):
|
||||||
|
yield
|
||||||
|
delay(10000*8)
|
||||||
|
yield from write(0, 1)
|
||||||
|
for i in range(10):
|
||||||
|
yield
|
||||||
|
err_present = yield from mgr.packet_err_present.read()
|
||||||
|
err_code = yield from mgr.packet_err_code.read()
|
||||||
|
self.assertEqual(err_present, 1)
|
||||||
|
self.assertEqual(err_code, 3)
|
||||||
|
yield from mgr.packet_err_present.write(1)
|
||||||
|
yield
|
||||||
|
err_present = yield from mgr.packet_err_present.read()
|
||||||
|
self.assertEqual(err_present, 0)
|
||||||
|
|
||||||
|
def test():
|
||||||
|
while not (yield from dut.master.link_layer.link_status.read()):
|
||||||
|
yield
|
||||||
|
|
||||||
|
yield from test_init()
|
||||||
|
yield from test_underflow()
|
||||||
|
yield from test_pulses()
|
||||||
|
yield from test_sequence_error()
|
||||||
|
yield from test_fifo_space()
|
||||||
|
yield from test_large_data()
|
||||||
|
yield from test_fifo_emptied()
|
||||||
|
yield from test_tsc_error()
|
||||||
|
|
||||||
|
@passive
|
||||||
|
def check_ttls():
|
||||||
|
cycle = 0
|
||||||
|
old_ttls = [0, 0]
|
||||||
|
while True:
|
||||||
|
ttls = [(yield dut.ttl0), (yield 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
|
||||||
|
|
||||||
|
run_simulation(dut,
|
||||||
|
{"sys": test(), "rtio": check_ttls()}, self.clocks)
|
||||||
|
self.assertEqual(ttl_changes, correct_ttl_changes)
|
||||||
|
|
||||||
|
def test_echo(self):
|
||||||
|
dut = DUT(2)
|
||||||
|
csrs = dut.master.rt_controller.csrs
|
||||||
|
mgr = dut.master.rt_manager
|
||||||
|
|
||||||
|
def test():
|
||||||
|
while not (yield from dut.master.link_layer.link_status.read()):
|
||||||
|
yield
|
||||||
|
|
||||||
|
yield from mgr.update_packet_cnt.write(1)
|
||||||
|
yield
|
||||||
|
self.assertEqual((yield from mgr.packet_cnt_tx.read()), 0)
|
||||||
|
self.assertEqual((yield from mgr.packet_cnt_rx.read()), 0)
|
||||||
|
|
||||||
|
yield from mgr.request_echo.write(1)
|
||||||
|
|
||||||
|
for i in range(15):
|
||||||
|
yield
|
||||||
|
|
||||||
|
yield from mgr.update_packet_cnt.write(1)
|
||||||
|
yield
|
||||||
|
self.assertEqual((yield from mgr.packet_cnt_tx.read()), 1)
|
||||||
|
self.assertEqual((yield from mgr.packet_cnt_rx.read()), 1)
|
||||||
|
|
||||||
|
run_simulation(dut, test(), self.clocks)
|
|
@ -0,0 +1,129 @@
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio.link_layer import *
|
||||||
|
|
||||||
|
|
||||||
|
def process(seq):
|
||||||
|
dut = Scrambler(8)
|
||||||
|
rseq = []
|
||||||
|
def pump():
|
||||||
|
yield dut.i.eq(seq[0])
|
||||||
|
yield
|
||||||
|
for w in seq[1:]:
|
||||||
|
yield dut.i.eq(w)
|
||||||
|
yield
|
||||||
|
rseq.append((yield dut.o))
|
||||||
|
yield
|
||||||
|
rseq.append((yield dut.o))
|
||||||
|
run_simulation(dut, pump())
|
||||||
|
return rseq
|
||||||
|
|
||||||
|
|
||||||
|
class Loopback(Module):
|
||||||
|
def __init__(self, nwords):
|
||||||
|
ks = [Signal() for k in range(nwords)]
|
||||||
|
ds = [Signal(8) for d in range(nwords)]
|
||||||
|
encoder = SimpleNamespace(k=ks, d=ds)
|
||||||
|
decoders = [SimpleNamespace(k=k, d=d) for k, d in zip(ks, ds)]
|
||||||
|
self.submodules.tx = LinkLayerTX(encoder)
|
||||||
|
self.submodules.rx = LinkLayerRX(decoders)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLinkLayer(unittest.TestCase):
|
||||||
|
def test_packets(self):
|
||||||
|
dut = Loopback(4)
|
||||||
|
|
||||||
|
def scrambler_sync():
|
||||||
|
for i in range(8):
|
||||||
|
yield
|
||||||
|
|
||||||
|
rt_packets = [
|
||||||
|
[0x12459970, 0x9938cdef, 0x12340000],
|
||||||
|
[0xabcdef00, 0x12345678],
|
||||||
|
[0xeeeeeeee, 0xffffffff, 0x01020304, 0x11223344],
|
||||||
|
[0x88277475, 0x19883332, 0x19837662, 0x81726668, 0x81876261]
|
||||||
|
]
|
||||||
|
def transmit_rt_packets():
|
||||||
|
yield from scrambler_sync()
|
||||||
|
|
||||||
|
for packet in rt_packets:
|
||||||
|
yield dut.tx.rt_frame.eq(1)
|
||||||
|
for data in packet:
|
||||||
|
yield dut.tx.rt_data.eq(data)
|
||||||
|
yield
|
||||||
|
yield dut.tx.rt_frame.eq(0)
|
||||||
|
yield
|
||||||
|
# flush
|
||||||
|
for i in range(20):
|
||||||
|
yield
|
||||||
|
|
||||||
|
rx_rt_packets = []
|
||||||
|
@passive
|
||||||
|
def receive_rt_packets():
|
||||||
|
yield from scrambler_sync()
|
||||||
|
|
||||||
|
previous_frame = 0
|
||||||
|
while True:
|
||||||
|
frame = yield dut.rx.rt_frame
|
||||||
|
if frame and not previous_frame:
|
||||||
|
packet = []
|
||||||
|
rx_rt_packets.append(packet)
|
||||||
|
previous_frame = frame
|
||||||
|
if frame:
|
||||||
|
packet.append((yield dut.rx.rt_data))
|
||||||
|
yield
|
||||||
|
|
||||||
|
aux_packets = [
|
||||||
|
[0x12, 0x34],
|
||||||
|
[0x44, 0x11, 0x98, 0x78],
|
||||||
|
[0xbb, 0xaa, 0xdd, 0xcc, 0x00, 0xff, 0xee]
|
||||||
|
]
|
||||||
|
def transmit_aux_packets():
|
||||||
|
yield from scrambler_sync()
|
||||||
|
|
||||||
|
for packet in aux_packets:
|
||||||
|
yield dut.tx.aux_frame.eq(1)
|
||||||
|
for data in packet:
|
||||||
|
yield dut.tx.aux_data.eq(data)
|
||||||
|
yield
|
||||||
|
while not (yield dut.tx.aux_ack):
|
||||||
|
yield
|
||||||
|
yield dut.tx.aux_frame.eq(0)
|
||||||
|
yield
|
||||||
|
while not (yield dut.tx.aux_ack):
|
||||||
|
yield
|
||||||
|
# flush
|
||||||
|
for i in range(20):
|
||||||
|
yield
|
||||||
|
|
||||||
|
rx_aux_packets = []
|
||||||
|
@passive
|
||||||
|
def receive_aux_packets():
|
||||||
|
yield from scrambler_sync()
|
||||||
|
|
||||||
|
previous_frame = 0
|
||||||
|
while True:
|
||||||
|
if (yield dut.rx.aux_stb):
|
||||||
|
frame = yield dut.rx.aux_frame
|
||||||
|
if frame and not previous_frame:
|
||||||
|
packet = []
|
||||||
|
rx_aux_packets.append(packet)
|
||||||
|
previous_frame = frame
|
||||||
|
if frame:
|
||||||
|
packet.append((yield dut.rx.aux_data))
|
||||||
|
yield
|
||||||
|
|
||||||
|
run_simulation(dut, [transmit_rt_packets(), receive_rt_packets(),
|
||||||
|
transmit_aux_packets(), receive_aux_packets()])
|
||||||
|
|
||||||
|
# print("RT:")
|
||||||
|
# for packet in rx_rt_packets:
|
||||||
|
# print(" ".join("{:08x}".format(x) for x in packet))
|
||||||
|
# print("AUX:")
|
||||||
|
# for packet in rx_aux_packets:
|
||||||
|
# print(" ".join("{:02x}".format(x) for x in packet))
|
||||||
|
self.assertEqual(rt_packets, rx_rt_packets)
|
||||||
|
self.assertEqual(aux_packets, rx_aux_packets)
|
|
@ -0,0 +1,212 @@
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
import random
|
||||||
|
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from artiq.gateware.drtio.rt_packets import *
|
||||||
|
from artiq.gateware.drtio.rt_packets import (_CrossDomainRequest,
|
||||||
|
_CrossDomainNotification)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
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)
|
||||||
|
completed = False
|
||||||
|
def send():
|
||||||
|
yield from pt.send("echo_request")
|
||||||
|
while not completed:
|
||||||
|
yield
|
||||||
|
def receive(packet_type, field_dict, trailer):
|
||||||
|
nonlocal completed
|
||||||
|
self.assertEqual(packet_type, "echo_reply")
|
||||||
|
self.assertEqual(trailer, [])
|
||||||
|
completed = True
|
||||||
|
run_simulation(dut, [send(), pr.receive(receive)])
|
||||||
|
|
||||||
|
def test_set_time(self):
|
||||||
|
for nwords in range(1, 8):
|
||||||
|
pt, _, dut = self.create_dut(nwords)
|
||||||
|
tx_times = [0x12345678aabbccdd, 0x0102030405060708,
|
||||||
|
0xaabbccddeeff1122]
|
||||||
|
def send():
|
||||||
|
for t in tx_times:
|
||||||
|
yield from pt.send("set_time", timestamp=t)
|
||||||
|
# flush
|
||||||
|
for i in range(10):
|
||||||
|
yield
|
||||||
|
rx_times = []
|
||||||
|
@passive
|
||||||
|
def receive():
|
||||||
|
while True:
|
||||||
|
if (yield dut.tsc_load):
|
||||||
|
rx_times.append((yield dut.tsc_value))
|
||||||
|
yield
|
||||||
|
run_simulation(dut, [send(), receive()])
|
||||||
|
self.assertEqual(tx_times, rx_times)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCDC(unittest.TestCase):
|
||||||
|
def test_cross_domain_request(self):
|
||||||
|
prng = random.Random(1)
|
||||||
|
for sys_freq in 3, 6, 11:
|
||||||
|
for srv_freq in 3, 6, 11:
|
||||||
|
req_stb = Signal()
|
||||||
|
req_ack = Signal()
|
||||||
|
req_data = Signal(8)
|
||||||
|
srv_stb = Signal()
|
||||||
|
srv_ack = Signal()
|
||||||
|
srv_data = Signal(8)
|
||||||
|
test_seq = [93, 92, 19, 39, 91, 30, 12, 91, 38, 42]
|
||||||
|
received_seq = []
|
||||||
|
|
||||||
|
def requester():
|
||||||
|
for data in test_seq:
|
||||||
|
yield req_data.eq(data)
|
||||||
|
yield req_stb.eq(1)
|
||||||
|
yield
|
||||||
|
while not (yield req_ack):
|
||||||
|
yield
|
||||||
|
yield req_stb.eq(0)
|
||||||
|
for j in range(prng.randrange(0, 10)):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def server():
|
||||||
|
for i in range(len(test_seq)):
|
||||||
|
while not (yield srv_stb):
|
||||||
|
yield
|
||||||
|
received_seq.append((yield srv_data))
|
||||||
|
for j in range(prng.randrange(0, 10)):
|
||||||
|
yield
|
||||||
|
yield srv_ack.eq(1)
|
||||||
|
yield
|
||||||
|
yield srv_ack.eq(0)
|
||||||
|
yield
|
||||||
|
|
||||||
|
dut = _CrossDomainRequest("srv",
|
||||||
|
req_stb, req_ack, req_data,
|
||||||
|
srv_stb, srv_ack, srv_data)
|
||||||
|
run_simulation(dut,
|
||||||
|
{"sys": requester(), "srv": server()},
|
||||||
|
{"sys": sys_freq, "srv": srv_freq})
|
||||||
|
self.assertEqual(test_seq, received_seq)
|
||||||
|
|
||||||
|
def test_cross_domain_notification(self):
|
||||||
|
prng = random.Random(1)
|
||||||
|
|
||||||
|
emi_stb = Signal()
|
||||||
|
emi_data = Signal(8)
|
||||||
|
rec_stb = Signal()
|
||||||
|
rec_ack = Signal()
|
||||||
|
rec_data = Signal(8)
|
||||||
|
|
||||||
|
test_seq = [23, 12, 8, 3, 28]
|
||||||
|
received_seq = []
|
||||||
|
|
||||||
|
def emitter():
|
||||||
|
for data in test_seq:
|
||||||
|
yield emi_stb.eq(1)
|
||||||
|
yield emi_data.eq(data)
|
||||||
|
yield
|
||||||
|
yield emi_stb.eq(0)
|
||||||
|
yield
|
||||||
|
for j in range(prng.randrange(0, 3)):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def receiver():
|
||||||
|
for i in range(len(test_seq)):
|
||||||
|
while not (yield rec_stb):
|
||||||
|
yield
|
||||||
|
received_seq.append((yield rec_data))
|
||||||
|
yield rec_ack.eq(1)
|
||||||
|
yield
|
||||||
|
yield rec_ack.eq(0)
|
||||||
|
yield
|
||||||
|
for j in range(prng.randrange(0, 3)):
|
||||||
|
yield
|
||||||
|
|
||||||
|
dut = _CrossDomainNotification("emi",
|
||||||
|
emi_stb, emi_data,
|
||||||
|
rec_stb, rec_ack, rec_data)
|
||||||
|
run_simulation(dut,
|
||||||
|
{"emi": emitter(), "sys": receiver()},
|
||||||
|
{"emi": 13, "sys": 3})
|
||||||
|
self.assertEqual(test_seq, received_seq)
|
Loading…
Reference in New Issue