artiq/artiq/gateware/drtio/link_layer.py

343 lines
11 KiB
Python

"""Link layer, common to satellite and master"""
from functools import reduce
from operator import xor, or_
from migen import *
from migen.genlib.fsm import *
from migen.genlib.cdc import MultiReg, GrayCounter, GrayDecoder
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.rx_up = CSRStatus()
self.rx_disable = CSRStorage()
self.tx_force_aux_zero = CSRStorage()
self.tx_force_rt_zero = CSRStorage()
# receiver locked, comma aligned, receiving valid 8b10b symbols
self.rx_ready = Signal()
tx = LinkLayerTX(encoder)
rx = ClockDomainsRenamer("rtio_rx")(LinkLayerRX(decoders))
self.submodules += tx, rx
# in rtio clock domain
self.tx_aux_frame = Signal()
self.tx_aux_data = Signal(len(tx.aux_data))
self.tx_aux_ack = Signal()
self.tx_rt_frame = Signal()
self.tx_rt_data = Signal(len(tx.rt_data))
# in rtio_rx clock domain
self.rx_aux_stb = Signal()
self.rx_aux_frame = Signal()
self.rx_aux_frame_perm = Signal()
self.rx_aux_data = Signal(len(rx.aux_data))
self.rx_rt_frame = Signal()
self.rx_rt_frame_perm = Signal()
self.rx_rt_data = Signal(len(rx.rt_data))
# # #
rx_up = Signal()
rx_up_r = Signal()
self.sync += rx_up_r.eq(rx_up)
rx_up_rx = Signal()
rx_up_r.attr.add("no_retiming")
self.specials += [
MultiReg(rx_up_r, rx_up_rx, "rtio_rx"),
MultiReg(rx_up_r, self.rx_up.status)]
rx_disable_rx = Signal()
self.rx_disable.storage.attr.add("no_retiming")
self.specials += MultiReg(self.rx_disable.storage, rx_disable_rx, "rtio_rx")
self.comb += [
tx.aux_frame.eq(self.tx_aux_frame | self.tx_force_aux_zero.storage),
tx.aux_data.eq(Mux(self.tx_force_aux_zero.storage, 0, self.tx_aux_data)),
self.tx_aux_ack.eq(tx.aux_ack),
tx.rt_frame.eq(self.tx_rt_frame | self.tx_force_rt_zero.storage),
tx.rt_data.eq(Mux(self.tx_force_rt_zero.storage, 0, self.tx_rt_data))
]
# we register those to improve timing margins, as the data may need
# to be recaptured by RXSynchronizer.
self.sync.rtio_rx += [
self.rx_aux_stb.eq(rx.aux_stb),
self.rx_aux_frame.eq(rx.aux_frame & rx_up_rx & ~rx_disable_rx),
self.rx_aux_frame_perm.eq(rx.aux_frame & rx_up_rx),
self.rx_aux_data.eq(rx.aux_data),
self.rx_rt_frame.eq(rx.rt_frame & rx_up_rx & ~rx_disable_rx),
self.rx_rt_frame_perm.eq(rx.rt_frame & rx_up_rx),
self.rx_rt_data.eq(rx.rt_data)
]
wait_scrambler = WaitTimer(15)
self.submodules += wait_scrambler
fsm = 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",
rx_up.eq(1),
If(~self.rx_ready, NextState("WAIT_RX_READY"))
)
# Zero word count is used with the "force zero" TX modes to implement
# PRBS tests.
class LinkLayerStats(Module, AutoCSR):
def __init__(self, link_layer, rx_clock_domain):
self.aux_word_cnt = CSRStatus(64)
self.aux_zword_cnt = CSRStatus(64)
self.rt_word_cnt = CSRStatus(64)
self.rt_zword_cnt = CSRStatus(64)
self.update_link_stats = CSR()
# # #
aux_word = Signal()
aux_zword = Signal()
rt_word = Signal()
rt_zword = Signal()
sync = getattr(self.sync, rx_clock_domain)
sync += [
aux_word.eq(link_layer.rx_aux_frame_perm),
aux_zword.eq(link_layer.rx_aux_frame_perm & (link_layer.rx_aux_data == 0)),
rt_word.eq(link_layer.rx_rt_frame_perm),
rt_zword.eq(link_layer.rx_rt_frame_perm & (link_layer.rx_aux_data == 0))
]
for trigger, csr in [(aux_word, self.aux_word_cnt),
(aux_zword, self.aux_zword_cnt),
(rt_word, self.rt_word_cnt),
(rt_zword, self.rt_zword_cnt)]:
counter = ClockDomainsRenamer(rx_clock_domain)(GrayCounter(64))
decoder = GrayDecoder(64)
self.submodules += counter, decoder
counter.q.attr.add("no_retiming")
self.specials += MultiReg(counter.q, decoder.i)
self.comb += counter.ce.eq(trigger)
self.sync += If(self.update_link_stats.re, csr.status.eq(decoder.o))