From bb047aabe99652f857931ea3537560310d40cffe Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 17 Nov 2016 22:32:39 +0800 Subject: [PATCH] drtio: simpler link layer --- artiq/gateware/drtio/core.py | 9 +- artiq/gateware/drtio/link_layer.py | 343 ++++++++---------- .../gateware/drtio/transceiver/gtx_7series.py | 2 +- artiq/runtime.rs/src/drtio.rs | 7 +- .../gateware/drtio/test_aux_controller.py | 8 +- artiq/test/gateware/drtio/test_full_stack.py | 6 +- artiq/test/gateware/drtio/test_link_layer.py | 41 +-- 7 files changed, 173 insertions(+), 243 deletions(-) diff --git a/artiq/gateware/drtio/core.py b/artiq/gateware/drtio/core.py index 51d8cd391..a51d22629 100644 --- a/artiq/gateware/drtio/core.py +++ b/artiq/gateware/drtio/core.py @@ -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) diff --git a/artiq/gateware/drtio/link_layer.py b/artiq/gateware/drtio/link_layer.py index 59f286732..24d325f9c 100644 --- a/artiq/gateware/drtio/link_layer.py +++ b/artiq/gateware/drtio/link_layer.py @@ -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) - 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) - curval.pop() - self.sync += state.eq(Cat(*curval[:n_state])) + 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.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) ) diff --git a/artiq/gateware/drtio/transceiver/gtx_7series.py b/artiq/gateware/drtio/transceiver/gtx_7series.py index 00166e11f..68885bf08 100644 --- a/artiq/gateware/drtio/transceiver/gtx_7series.py +++ b/artiq/gateware/drtio/transceiver/gtx_7series.py @@ -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), diff --git a/artiq/runtime.rs/src/drtio.rs b/artiq/runtime.rs/src/drtio.rs index 214295697..089dd55b4 100644 --- a/artiq/runtime.rs/src/drtio.rs +++ b/artiq/runtime.rs/src/drtio.rs @@ -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"); diff --git a/artiq/test/gateware/drtio/test_aux_controller.py b/artiq/test/gateware/drtio/test_aux_controller.py index d9a3ef467..f07f5eb6d 100644 --- a/artiq/test/gateware/drtio/test_aux_controller.py +++ b/artiq/test/gateware/drtio/test_aux_controller.py @@ -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) diff --git a/artiq/test/gateware/drtio/test_full_stack.py b/artiq/test/gateware/drtio/test_full_stack.py index eea0c2256..ae0ce7290 100644 --- a/artiq/test/gateware/drtio/test_full_stack.py +++ b/artiq/test/gateware/drtio/test_full_stack.py @@ -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): diff --git a/artiq/test/gateware/drtio/test_link_layer.py b/artiq/test/gateware/drtio/test_link_layer.py index 25a2cd2a0..3d161e670 100644 --- a/artiq/test/gateware/drtio/test_link_layer.py +++ b/artiq/test/gateware/drtio/test_link_layer.py @@ -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,12 +36,9 @@ 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) - yield + def scrambler_sync(): + for i in range(8): + yield rt_packets = [ [0x12459970, 0x9938cdef, 0x12340000], @@ -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:")