drtio: simpler link layer

This commit is contained in:
Sebastien Bourdeauducq 2016-11-17 22:32:39 +08:00
parent 09363e1da8
commit bb047aabe9
7 changed files with 173 additions and 243 deletions

View File

@ -6,10 +6,9 @@ from artiq.gateware.drtio import link_layer, rt_packets, iot, rt_controller, aux
class DRTIOSatellite(Module):
def __init__(self, transceiver, rx_synchronizer, channels, fine_ts_width=3, full_ts_width=63,
ll_rx_ready_confirm=1000):
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, ll_rx_ready_confirm)
transceiver.encoder, transceiver.decoders)
self.comb += [
transceiver.rx_reset.eq(self.link_layer.rx_reset),
self.link_layer.rx_ready.eq(transceiver.rx_ready)
@ -52,9 +51,9 @@ class DRTIOSatellite(Module):
class DRTIOMaster(Module):
def __init__(self, transceiver, channel_count=1024, fine_ts_width=3, ll_rx_ready_confirm=1000):
def __init__(self, transceiver, channel_count=1024, fine_ts_width=3):
self.submodules.link_layer = link_layer.LinkLayer(
transceiver.encoder, transceiver.decoders, ll_rx_ready_confirm)
transceiver.encoder, transceiver.decoders)
self.comb += [
transceiver.rx_reset.eq(self.link_layer.rx_reset),
self.link_layer.rx_ready.eq(transceiver.rx_ready)

View File

@ -3,43 +3,102 @@ from operator import xor, or_
from migen import *
from migen.genlib.fsm import *
from migen.genlib.cdc import MultiReg, BusSynchronizer
from migen.genlib.cdc import MultiReg
from migen.genlib.misc import WaitTimer
from misoc.interconnect.csr import *
class Scrambler(Module):
def __init__(self, n_io, n_state=23, taps=[17, 22]):
self.i = Signal(n_io)
self.o = Signal(n_io)
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(n_io)):
flip = reduce(xor, [curval[tap] for tap in taps])
self.sync += self.o[i].eq(flip ^ self.i[i])
curval.insert(0, flip)
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()
self.sync += state.eq(Cat(*curval[:n_state]))
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.link_init = Signal()
self.signal_rx_ready = Signal()
self.aux_frame = Signal()
self.aux_data = Signal(2*nwords)
self.aux_ack = Signal()
@ -49,93 +108,64 @@ class LinkLayerTX(Module):
# # #
# Idle and auxiliary traffic use special characters excluding K.28.7,
# K.29.7 and K.30.7 in order to easily separate the link initialization
# phase (K.28.7 is additionally excluded as we cannot guarantee its
# non-repetition here).
# 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
aux_scrambler = ResetInserter()(CEInserter()(Scrambler(3*nwords)))
self.submodules += aux_scrambler
#
# 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,
aux_scrambler.i.eq(Cat(*aux_data_ctl))
scrambler.i1.eq(Cat(*aux_data_ctl))
).Else(
aux_scrambler.i.eq(Replicate(0b100, nwords))
scrambler.i1.eq(Replicate(0b100, nwords))
),
aux_scrambler.reset.eq(self.link_init),
aux_scrambler.ce.eq(~self.rt_frame),
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 = aux_scrambler.o[i*3:i*3+3]
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),
If(scrambled_ctl == 7,
encoder.d[i].eq(K(23, 7))
).Else(
encoder.d[i].eq(K(28, scrambled_ctl))
)
encoder.d[i].eq(Array(aux_coding)[scrambled_ctl])
]
# Real-time traffic uses data characters and is framed by the special
# characters of auxiliary traffic. RT traffic is also scrambled.
rt_scrambler = ResetInserter()(CEInserter()(Scrambler(8*nwords)))
self.submodules += rt_scrambler
self.comb += [
rt_scrambler.i.eq(self.rt_data),
rt_scrambler.reset.eq(self.link_init),
rt_scrambler.ce.eq(self.rt_frame)
]
rt_frame_r = Signal()
self.sync += [
rt_frame_r.eq(self.rt_frame),
self.sync += \
If(rt_frame_r,
[k.eq(0) for k in encoder.k],
[d.eq(rt_scrambler.o[i*8:i*8+8]) for i, d in enumerate(encoder.d)]
[d.eq(scrambler.o2[i*8:i*8+8]) for i, d in enumerate(encoder.d)]
)
]
# During link init, send a series of 1*K.28.7 (comma) + 31*K.29.7/K.30.7
# The receiving end configures its transceiver to also place the comma
# on its LSB, achieving fixed (or known) latency and alignment of
# packet starts.
# K.29.7 and K.30.7 are chosen to avoid comma alignment issues arising
# from K.28.7.
# K.30.7 is sent instead of K.29.7 to signal the alignment of the local
# receiver, thus the remote can end its link initialization pattern.
link_init_r = Signal()
link_init_counter = Signal(max=32//nwords)
self.sync += [
link_init_r.eq(self.link_init),
If(link_init_r,
link_init_counter.eq(link_init_counter + 1),
[k.eq(1) for k in encoder.k],
If(self.signal_rx_ready,
[d.eq(K(30, 7)) for d in encoder.d[1:]]
).Else(
[d.eq(K(29, 7)) for d in encoder.d[1:]]
),
If(link_init_counter == 0,
encoder.d[0].eq(K(28, 7)),
).Else(
If(self.signal_rx_ready,
encoder.d[0].eq(K(30, 7))
).Else(
encoder.d[0].eq(K(29, 7))
)
)
).Else(
link_init_counter.eq(0)
)
]
class LinkLayerRX(Module):
@ -144,9 +174,6 @@ class LinkLayerRX(Module):
# nwords must be a power of 2
assert nwords & (nwords - 1) == 0
self.link_init = Signal()
self.remote_rx_ready = Signal()
self.aux_stb = Signal()
self.aux_frame = Signal()
self.aux_data = Signal(2*nwords)
@ -156,65 +183,46 @@ class LinkLayerRX(Module):
# # #
aux_descrambler = ResetInserter()(CEInserter()(Scrambler(3*nwords)))
rt_descrambler = ResetInserter()(CEInserter()(Scrambler(8*nwords)))
self.submodules += aux_descrambler, rt_descrambler
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 += [
self.aux_frame.eq(~aux_descrambler.o[2]),
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(*[aux_descrambler.o[3*i:3*i+2] for i in range(nwords)])),
self.rt_data.eq(rt_descrambler.o),
]
aux_stb_d = Signal()
rt_frame_d = Signal()
self.sync += [
self.aux_stb.eq(aux_stb_d),
self.rt_frame.eq(rt_frame_d)
]
link_init_char = Signal()
self.comb += [
link_init_char.eq(
(decoders[0].d == K(28, 7)) |
(decoders[0].d == K(29, 7)) |
(decoders[0].d == K(30, 7))),
If(decoders[0].k,
If(link_init_char,
aux_descrambler.reset.eq(1),
rt_descrambler.reset.eq(1)
).Else(
aux_stb_d.eq(1)
),
aux_descrambler.ce.eq(1)
).Else(
rt_frame_d.eq(1),
rt_descrambler.ce.eq(1)
),
aux_descrambler.i.eq(Cat(*[d.d[5:] for d in decoders])),
rt_descrambler.i.eq(Cat(*[d.d for d in decoders]))
Cat(*[descrambler.o1[3*i:3*i+2] for i in range(nwords)])),
self.rt_data.eq(descrambler.o2)
]
self.sync += [
self.link_init.eq(0),
If(decoders[0].k,
If(link_init_char, self.link_init.eq(1)),
If(decoders[0].d == K(30, 7),
self.remote_rx_ready.eq(1)
).Elif(decoders[0].d != K(28, 7),
self.remote_rx_ready.eq(0)
),
If(decoders[1].d == K(30, 7),
self.remote_rx_ready.eq(1)
) if len(decoders) > 1 else None
).Else(
self.remote_rx_ready.eq(0)
)
self.aux_stb.eq(decoders[0].k),
self.rt_frame.eq(~decoders[0].k)
]
class LinkLayer(Module, AutoCSR):
def __init__(self, encoder, decoders, rx_ready_confirm_cycles):
self.link_status = CSRStatus(3)
def __init__(self, encoder, decoders):
self.link_status = CSRStatus()
# control signals, in rtio clock domain
self.reset = Signal()
@ -242,6 +250,8 @@ class LinkLayer(Module, AutoCSR):
self.rx_rt_frame = Signal()
self.rx_rt_data = rx.rt_data
# # #
ready_r = Signal()
ready_rx = Signal()
self.sync.rtio += ready_r.eq(self.ready)
@ -251,8 +261,10 @@ class LinkLayer(Module, AutoCSR):
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 = WaitTimer(15)
self.submodules += wait_scrambler
fsm = ClockDomainsRenamer("rtio")(
ResetInserter()(FSM(reset_state="RESET_RX")))
@ -260,68 +272,17 @@ class LinkLayer(Module, AutoCSR):
self.comb += fsm.reset.eq(self.reset)
rx_remote_rx_ready = Signal()
rx_link_init = Signal()
rx.remote_rx_ready.attr.add("no_retiming")
rx.link_init.attr.add("no_retiming")
self.specials += [
MultiReg(rx.remote_rx_ready, rx_remote_rx_ready, "rtio"),
MultiReg(rx.link_init, rx_link_init, "rtio")
]
link_status = BusSynchronizer(3, "rtio", "sys")
self.submodules += link_status
self.comb += self.link_status.status.eq(link_status.o)
wait_confirm = ClockDomainsRenamer("rtio")(
WaitTimer(rx_ready_confirm_cycles))
self.submodules += wait_confirm
signal_rx_ready_margin = ClockDomainsRenamer("rtio")(WaitTimer(15))
self.submodules += signal_rx_ready_margin
fsm.act("RESET_RX",
link_status.i.eq(0),
tx.link_init.eq(1),
self.rx_reset.eq(1),
NextState("WAIT_LOCAL_RX_READY")
NextState("WAIT_RX_READY")
)
fsm.act("WAIT_LOCAL_RX_READY",
link_status.i.eq(1),
tx.link_init.eq(1),
If(self.rx_ready, NextState("CONFIRM_LOCAL_RX_READY"))
fsm.act("WAIT_RX_READY",
If(self.rx_ready, NextState("WAIT_SCRAMBLER_SYNC"))
)
fsm.act("CONFIRM_LOCAL_RX_READY",
link_status.i.eq(2),
tx.link_init.eq(1),
wait_confirm.wait.eq(1),
If(wait_confirm.done, NextState("WAIT_REMOTE_RX_READY")),
If(~rx_link_init, NextState("RESET_RX"))
)
fsm.act("WAIT_REMOTE_RX_READY",
link_status.i.eq(3),
tx.link_init.eq(1),
tx.signal_rx_ready.eq(1),
If(rx_remote_rx_ready, NextState("ENSURE_SIGNAL_RX_READY"))
)
# If the transceiver transmits one character per RTIO cycle,
# we may be unlucky and signal_rx_ready will transmit a comma
# on the first cycle instead of a "RX ready" character.
# Further, we need to ensure the rx.remote_rx_ready
# gets through MultiReg to rx_remote_rx_ready at the receiver.
# So transmit the "RX ready" pattern for several cycles.
fsm.act("ENSURE_SIGNAL_RX_READY",
link_status.i.eq(3),
tx.link_init.eq(1),
tx.signal_rx_ready.eq(1),
signal_rx_ready_margin.wait.eq(1),
If(signal_rx_ready_margin.done, NextState("WAIT_REMOTE_LINK_UP"))
)
fsm.act("WAIT_REMOTE_LINK_UP",
link_status.i.eq(4),
If(~rx_link_init, NextState("READY"))
fsm.act("WAIT_SCRAMBLER_SYNC",
wait_scrambler.wait.eq(1),
If(wait_scrambler.done, NextState("READY")),
)
fsm.act("READY",
link_status.i.eq(5),
If(rx_link_init, NextState("RESET_RX")), # TODO: remove this, link deinit should be detected at upper layer
self.ready.eq(1)
)

View File

@ -185,7 +185,7 @@ class GTX_1000BASE_BX10(Module):
self.decoders[1].input.eq(rxdata[10:])
]
clock_aligner = BruteforceClockAligner(0b0001111100, self.rtio_clk_freq)
clock_aligner = BruteforceClockAligner(0b0101111100, self.rtio_clk_freq)
self.submodules += clock_aligner
self.comb += [
clock_aligner.rxdata.eq(rxdata),

View File

@ -3,7 +3,7 @@ use sched::{Waiter, Spawner};
fn drtio_link_is_up() -> bool {
unsafe {
csr::drtio::link_status_read() == 5
csr::drtio::link_status_read() == 1
}
}
@ -31,7 +31,10 @@ fn drtio_init_channel(channel: u16) {
pub fn link_thread(waiter: Waiter, _spawner: Spawner) {
loop {
waiter.until(drtio_link_is_up).unwrap();
info!("link is up");
info!("link RX is up");
waiter.sleep(300);
info!("wait for remote side done");
drtio_sync_tsc();
info!("TSC synced");

View File

@ -44,13 +44,7 @@ class TestAuxController(unittest.TestCase):
dut = TB(4)
def link_init():
yield dut.link_layer.tx.link_init.eq(1)
yield
yield
yield dut.link_layer.tx.link_init.eq(0)
while not (yield dut.link_layer.rx.link_init):
yield
while (yield dut.link_layer.rx.link_init):
for i in range(8):
yield
yield dut.link_layer.ready.eq(1)

View File

@ -41,8 +41,7 @@ class DUT(Module):
self.ttl1 = Signal()
self.transceivers = DummyTransceiverPair(nwords)
self.submodules.master = DRTIOMaster(self.transceivers.alice,
ll_rx_ready_confirm=15)
self.submodules.master = DRTIOMaster(self.transceivers.alice)
rx_synchronizer = DummyRXSynchronizer()
self.submodules.phy0 = ttl_simple.Output(self.ttl0)
@ -52,8 +51,7 @@ class DUT(Module):
rtio.Channel.from_phy(self.phy1, ofifo_depth=4)
]
self.submodules.satellite = DRTIOSatellite(
self.transceivers.bob, rx_synchronizer, rtio_channels,
ll_rx_ready_confirm=15)
self.transceivers.bob, rx_synchronizer, rtio_channels)
class TestFullStack(unittest.TestCase):

View File

@ -22,15 +22,6 @@ def process(seq):
return rseq
class TestScrambler(unittest.TestCase):
def test_roundtrip(self):
seq = list(range(256))*3
scrambled_seq = process(seq)
descrambled_seq = process(scrambled_seq)
self.assertNotEqual(seq, scrambled_seq)
self.assertEqual(seq, descrambled_seq)
class Loopback(Module):
def __init__(self, nwords):
ks = [Signal() for k in range(nwords)]
@ -45,11 +36,8 @@ class TestLinkLayer(unittest.TestCase):
def test_packets(self):
dut = Loopback(4)
def link_init():
yield dut.tx.link_init.eq(1)
yield
yield
yield dut.tx.link_init.eq(0)
def scrambler_sync():
for i in range(8):
yield
rt_packets = [
@ -59,10 +47,7 @@ class TestLinkLayer(unittest.TestCase):
[0x88277475, 0x19883332, 0x19837662, 0x81726668, 0x81876261]
]
def transmit_rt_packets():
while not (yield dut.tx.link_init):
yield
while (yield dut.tx.link_init):
yield
yield from scrambler_sync()
for packet in rt_packets:
yield dut.tx.rt_frame.eq(1)
@ -78,10 +63,7 @@ class TestLinkLayer(unittest.TestCase):
rx_rt_packets = []
@passive
def receive_rt_packets():
while not (yield dut.rx.link_init):
yield
while (yield dut.rx.link_init):
yield
yield from scrambler_sync()
previous_frame = 0
while True:
@ -100,10 +82,7 @@ class TestLinkLayer(unittest.TestCase):
[0xbb, 0xaa, 0xdd, 0xcc, 0x00, 0xff, 0xee]
]
def transmit_aux_packets():
while not (yield dut.tx.link_init):
yield
while (yield dut.tx.link_init):
yield
yield from scrambler_sync()
for packet in aux_packets:
yield dut.tx.aux_frame.eq(1)
@ -123,10 +102,7 @@ class TestLinkLayer(unittest.TestCase):
rx_aux_packets = []
@passive
def receive_aux_packets():
while not (yield dut.rx.link_init):
yield
while (yield dut.rx.link_init):
yield
yield from scrambler_sync()
previous_frame = 0
while True:
@ -140,8 +116,7 @@ class TestLinkLayer(unittest.TestCase):
packet.append((yield dut.rx.aux_data))
yield
run_simulation(dut, [link_init(),
transmit_rt_packets(), receive_rt_packets(),
run_simulation(dut, [transmit_rt_packets(), receive_rt_packets(),
transmit_aux_packets(), receive_aux_packets()])
# print("RT:")