Skip to content

Commit

Permalink
Merge branch 'drtio'
Browse files Browse the repository at this point in the history
sbourdeauducq committed Dec 3, 2016
2 parents 3520038 + 5d145ff commit 88ad054
Showing 35 changed files with 4,112 additions and 242 deletions.
148 changes: 148 additions & 0 deletions artiq/examples/drtio/device_db.pyon
Original file line number Diff line number Diff line change
@@ -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}
},

}
27 changes: 27 additions & 0 deletions artiq/examples/drtio/repository/blink_forever.py
Original file line number Diff line number Diff line change
@@ -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)
25 changes: 25 additions & 0 deletions artiq/examples/drtio/repository/pulse_rate.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions artiq/gateware/drtio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from artiq.gateware.drtio.core import DRTIOSatellite, DRTIOMaster

226 changes: 226 additions & 0 deletions artiq/gateware/drtio/aux_controller.py
Original file line number Diff line number Diff line change
@@ -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()
68 changes: 68 additions & 0 deletions artiq/gateware/drtio/core.py
Original file line number Diff line number Diff line change
@@ -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())
86 changes: 86 additions & 0 deletions artiq/gateware/drtio/iot.py
Original file line number Diff line number Diff line change
@@ -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])
278 changes: 278 additions & 0 deletions artiq/gateware/drtio/link_layer.py
Original file line number Diff line number Diff line change
@@ -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"))
)
233 changes: 233 additions & 0 deletions artiq/gateware/drtio/rt_controller.py
Original file line number Diff line number Diff line change
@@ -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)
)
731 changes: 731 additions & 0 deletions artiq/gateware/drtio/rt_packets.py

Large diffs are not rendered by default.

Empty file.
265 changes: 265 additions & 0 deletions artiq/gateware/drtio/transceiver/gtx_7series.py
Original file line number Diff line number Diff line change
@@ -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
226 changes: 226 additions & 0 deletions artiq/gateware/drtio/transceiver/gtx_7series_init.py
Original file line number Diff line number Diff line change
@@ -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")
)
)
4 changes: 3 additions & 1 deletion artiq/gateware/rtio/__init__.py
Original file line number Diff line number Diff line change
@@ -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.moninj import MonInj
from artiq.gateware.rtio.dma import DMA
51 changes: 11 additions & 40 deletions artiq/gateware/rtio/analyzer.py
Original file line number Diff line number Diff line change
@@ -42,35 +42,23 @@


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.overflow = CSRStatus()
self.overflow_reset = CSR()

# # #

kcsrs = rtio_core.kcsrs

input_output_stb = Signal()
input_output = Record(input_output_layout)
if hasattr(kcsrs, "o_data"):
o_data = kcsrs.o_data.storage
else:
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
o_data = kcsrs.o_data.storage
o_address = kcsrs.o_address.storage
i_data = kcsrs.i_data.status
self.comb += [
input_output.channel.eq(kcsrs.chan_sel.storage),
input_output.address_padding.eq(o_address),
input_output.rtio_counter.eq(
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
input_output.rtio_counter.eq(rtio_counter),
If(kcsrs.o_we.re,
input_output.message_type.eq(MessageType.output.value),
input_output.timestamp.eq(kcsrs.o_timestamp.storage),
@@ -88,39 +76,22 @@ def __init__(self, rtio_core, enable):
self.comb += [
exception.message_type.eq(MessageType.exception.value),
exception.channel.eq(kcsrs.chan_sel.storage),
exception.rtio_counter.eq(
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
exception.rtio_counter.eq(rtio_counter),
]
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"):
self.comb += \
If(getattr(kcsrs, ename).re,
exception_stb.eq(1),
exception.exception_type.eq(
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)
self.comb += [
stopped.message_type.eq(MessageType.stopped.value),
stopped.rtio_counter.eq(
rtio_core.counter.value_sys << rtio_core.fine_ts_width),
stopped.rtio_counter.eq(rtio_counter),
]

enable_r = Signal()
@@ -210,13 +181,13 @@ def __init__(self, membus):


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
self.enable = CSRStorage()
self.busy = CSRStatus()

self.submodules.message_encoder = MessageEncoder(
rtio_core, self.enable.storage)
kcsrs, rtio_counter, self.enable.storage)
self.submodules.fifo = stream.SyncFIFO(
[("data", message_len)], fifo_depth, True)
self.submodules.converter = stream.Converter(
66 changes: 66 additions & 0 deletions artiq/gateware/rtio/cdc.py
Original file line number Diff line number Diff line change
@@ -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)
]
204 changes: 43 additions & 161 deletions artiq/gateware/rtio/core.py
Original file line number Diff line number Diff line change
@@ -3,73 +3,11 @@

from migen import *
from migen.genlib.record import Record
from migen.genlib.cdc import *
from migen.genlib.fifo import AsyncFIFO
from migen.genlib.resetsync import AsyncResetSynchronizer
from misoc.interconnect.csr import *

from artiq.gateware.rtio import rtlink


# 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)
]
from artiq.gateware.rtio import cri, rtlink
from artiq.gateware.rtio.cdc import *


# CHOOSING A GUARD TIME
@@ -227,7 +165,7 @@ def __init__(self, interface, counter, fifo_depth, guard_io_cycles):
interface.stb.eq(dout_stb & dout_ack)
]

busy_transfer = _BlindTransfer()
busy_transfer = BlindTransfer()
self.submodules += busy_transfer
self.comb += [
busy_transfer.i.eq(interface.stb & interface.busy),
@@ -289,7 +227,7 @@ def __init__(self, interface, counter, fifo_depth):
fifo.re.eq(self.re)
]

overflow_transfer = _BlindTransfer()
overflow_transfer = BlindTransfer()
self.submodules += overflow_transfer
self.comb += [
overflow_transfer.i.eq(fifo.we & ~fifo.writable),
@@ -326,81 +264,47 @@ def __init__(self):
self.overrides = []


class _KernelCSRs(AutoCSR):
def __init__(self, chan_sel_width,
data_width, address_width, full_ts_width):
self.reset = CSRStorage(reset=1)
self.reset_phy = CSRStorage(reset=1)
self.chan_sel = CSRStorage(chan_sel_width)

if data_width:
self.o_data = CSRStorage(data_width, write_from_dev=True)
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()
class Core(Module):
def __init__(self, channels, fine_ts_width=None, guard_io_cycles=20):
if fine_ts_width is None:
fine_ts_width = max(rtlink.get_fine_ts_width(c.interface)
for c in channels)

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)
self.cri = cri.Interface()
self.comb += self.cri.arb_gnt.eq(1)

# Clocking/Reset
# 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_rio = ClockDomain()
self.clock_domains.cd_rio_phy = ClockDomain()
self.comb += [
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.specials += AsyncResetSynchronizer(
self.cd_rio,
self.kcsrs.reset.storage | ResetSignal("rtio",
allow_reset_less=True))
self.cd_rio, cmd_reset)
self.comb += self.cd_rio_phy.clk.eq(ClockSignal("rtio"))
self.specials += AsyncResetSynchronizer(
self.cd_rio_phy,
self.kcsrs.reset_phy.storage | ResetSignal("rtio",
allow_reset_less=True))
self.cd_rio_phy, cmd_reset_phy)

# 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 = [], []
o_statuses, i_statuses = [], []
sel = self.kcsrs.chan_sel.storage
sel = self.cri.chan_sel[:16]
for n, channel in enumerate(channels):
if isinstance(channel, LogChannel):
i_datas.append(0)
@@ -416,30 +320,26 @@ def __init__(self, channels, full_ts_width=63, guard_io_cycles=20):
self.submodules += o_manager

if hasattr(o_manager.ev, "data"):
self.comb += o_manager.ev.data.eq(
self.kcsrs.o_data.storage)
self.comb += o_manager.ev.data.eq(self.cri.o_data)
if hasattr(o_manager.ev, "address"):
self.comb += o_manager.ev.address.eq(
self.kcsrs.o_address.storage)
ts_shift = (len(self.kcsrs.o_timestamp.storage)
- len(o_manager.ev.timestamp))
self.comb += o_manager.ev.timestamp.eq(
self.kcsrs.o_timestamp.storage[ts_shift:])
self.comb += o_manager.ev.address.eq(self.cri.o_address)
ts_shift = len(self.cri.o_timestamp) - len(o_manager.ev.timestamp)
self.comb += o_manager.ev.timestamp.eq(self.cri.o_timestamp[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()
sequence_error = Signal()
collision = Signal()
busy = Signal()
self.sync.rsys += [
If(selected & self.kcsrs.o_underflow_reset.re,
If(selected & (self.cri.cmd == cri.commands["o_underflow_reset"]),
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)),
If(selected & self.kcsrs.o_collision_reset.re,
If(selected & (self.cri.cmd == cri.commands["o_collision_reset"]),
collision.eq(0)),
If(selected & self.kcsrs.o_busy_reset.re,
If(selected & (self.cri.cmd == cri.commands["o_busy_reset"]),
busy.eq(0)),
If(o_manager.underflow, underflow.eq(1)),
If(o_manager.sequence_error, sequence_error.eq(1)),
@@ -462,17 +362,16 @@ def __init__(self, channels, full_ts_width=63, guard_io_cycles=20):
else:
i_datas.append(0)
if channel.interface.i.timestamped:
ts_shift = (len(self.kcsrs.i_timestamp.status)
- len(i_manager.ev.timestamp))
ts_shift = (len(self.cri.i_timestamp) - len(i_manager.ev.timestamp))
i_timestamps.append(i_manager.ev.timestamp << ts_shift)
else:
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()
self.sync.rsys += [
If(selected & self.kcsrs.i_overflow_reset.re,
If(selected & (self.cri.cmd == cri.commands["i_overflow_reset"]),
overflow.eq(0)),
If(i_manager.overflow,
overflow.eq(1))
@@ -483,28 +382,11 @@ def __init__(self, channels, full_ts_width=63, guard_io_cycles=20):
i_datas.append(0)
i_timestamps.append(0)
i_statuses.append(0)
if data_width:
self.comb += self.kcsrs.i_data.status.eq(Array(i_datas)[sel])
self.comb += [
self.kcsrs.i_timestamp.status.eq(Array(i_timestamps)[sel]),
self.kcsrs.o_status.status.eq(Array(o_statuses)[sel]),
self.kcsrs.i_status.status.eq(Array(i_statuses)[sel])
]

# Counter access
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),
self.cri.i_data.eq(Array(i_datas)[sel]),
self.cri.i_timestamp.eq(Array(i_timestamps)[sel]),
self.cri.o_status.eq(Array(o_statuses)[sel]),
self.cri.i_status.eq(Array(i_statuses)[sel])
]

def get_csrs(self):
return self.kcsrs.get_csrs()
self.comb += self.cri.counter.eq(self.counter.value_sys << fine_ts_width)
196 changes: 196 additions & 0 deletions artiq/gateware/rtio/cri.py
Original file line number Diff line number Diff line change
@@ -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)
332 changes: 332 additions & 0 deletions artiq/gateware/rtio/dma.py
Original file line number Diff line number Diff line change
@@ -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())
5 changes: 3 additions & 2 deletions artiq/gateware/soc.py
Original file line number Diff line number Diff line change
@@ -39,8 +39,9 @@ def __init__(self):
self.submodules.timer_kernel = timer.Timer()
self.register_kernel_cpu_csrdevice("timer_kernel")

def register_kernel_cpu_csrdevice(self, name):
csrs = getattr(self, name).get_csrs()
def register_kernel_cpu_csrdevice(self, name, csrs=None):
if csrs is None:
csrs = getattr(self, name).get_csrs()
bank = wishbone.CSRBank(csrs)
self.submodules += bank
self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map[name]),
21 changes: 14 additions & 7 deletions artiq/gateware/targets/kc705.py
Original file line number Diff line number Diff line change
@@ -101,10 +101,11 @@ def __init__(self, platform, rtio_internal_clk):

class _NIST_Ions(MiniSoC, AMPSoC):
mem_map = {
"timer_kernel": 0x10000000, # (shadow @0x90000000)
"rtio": 0x20000000, # (shadow @0xa0000000)
"i2c": 0x30000000, # (shadow @0xb0000000)
"mailbox": 0x70000000 # (shadow @0xf0000000)
"timer_kernel": 0x10000000,
"rtio": 0x20000000,
"rtio_dma": 0x30000000,
"i2c": 0x50000000,
"mailbox": 0x70000000
}
mem_map.update(MiniSoC.mem_map)

@@ -142,8 +143,14 @@ def __init__(self, cpu_type="or1k", **kwargs):
def add_rtio(self, rtio_channels):
self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk)
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_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.csr_devices.append("rtio_moninj")

@@ -153,8 +160,8 @@ def add_rtio(self, rtio_channels):
self.crg.cd_sys.clk,
self.rtio_crg.cd_rtio.clk)

self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio,
self.get_native_sdram_if())
self.submodules.rtio_analyzer = rtio.Analyzer(
self.rtio, self.rtio_core.cri.counter, self.get_native_sdram_if())
self.csr_devices.append("rtio_analyzer")


126 changes: 126 additions & 0 deletions artiq/gateware/targets/kc705_drtio_master.py
Original file line number Diff line number Diff line change
@@ -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()
220 changes: 220 additions & 0 deletions artiq/gateware/targets/kc705_drtio_satellite.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 88ad054

Please sign in to comment.