diff --git a/src/gateware/cxp_downconn.py b/src/gateware/cxp_downconn.py index 8750f4c..bdf6443 100644 --- a/src/gateware/cxp_downconn.py +++ b/src/gateware/cxp_downconn.py @@ -1,4 +1,6 @@ from migen import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer +from migen.genlib.misc import WaitTimer from migen.genlib.resetsync import AsyncResetSynchronizer from misoc.cores.code_8b10b import Encoder, Decoder @@ -100,12 +102,12 @@ class CXP_DownConn(Module, AutoCSR): # pmod 0-7 pin Instance("OBUF", i_I=gtx.clk_aligner.rxslide, o_O=pmod_pads[0]), - Instance("OBUF", i_I=gtx.clk_aligner.ready, o_O=pmod_pads[1]), - # Instance("OBUF", i_I=gtx.tx_init.Xxdlysresetdone , o_O=pmod_pads[2]), - # Instance("OBUF", i_I=gtx.tx_init.Xxphaligndone , o_O=pmod_pads[3]), - # Instance("OBUF", i_I=, o_O=pmod_pads[4]), - # Instance("OBUF", i_I=, o_O=pmod_pads[5]), - # Instance("OBUF", i_I=, o_O=pmod_pads[6]), + Instance("OBUF", i_I=gtx.clk_aligner.rxinit_done, o_O=pmod_pads[1]), + Instance("OBUF", i_I=gtx.clk_aligner.restart, o_O=pmod_pads[2]), + Instance("OBUF", i_I=gtx.clk_aligner.comma_det.comma_aligned, o_O=pmod_pads[3]), + Instance("OBUF", i_I=gtx.clk_aligner.ready, o_O=pmod_pads[4]), + Instance("OBUF", i_I=gtx.clk_aligner.comma_det.reset, o_O=pmod_pads[5]), + Instance("OBUF", i_I=gtx.clk_aligner.comma_det.detected, o_O=pmod_pads[6]), # Instance("OBUF", i_I=, o_O=pmod_pads[7]), ] @@ -131,6 +133,8 @@ class CXP_DownConn(Module, AutoCSR): self.decoded_k_0 = CSRStatus() self.decoded_k_1 = CSRStatus() + self.shifted = CSRStatus(9) + self.sync.cxp_gtx_tx += [ If(counter == 0, @@ -158,6 +162,9 @@ class CXP_DownConn(Module, AutoCSR): self.rxdata_1.status.eq(self.gtx.decoders[1].input), self.decoded_data_1.status.eq(self.gtx.decoders[1].d), self.decoded_k_1.status.eq(self.gtx.decoders[1].k), + If(self.gtx.clk_aligner.comma_det.detected, + self.shifted.status.eq(self.gtx.clk_aligner.comma_det.bitshift), + ) ] @@ -253,75 +260,192 @@ class QPLL(Module): ) ] +# detect if the comma is located at data[0:10] +class Comma_Detector(Module): + def __init__(self, comma, width): + self.reset = Signal() + self.data = Signal(width) + + self.detected = Signal() + self.bitshift = Signal(max=width) + self.comma_aligned = Signal() + + # # # + last_data = Signal(10) + data = Signal(width+10) + + comma_n = ~comma & 0b1111111111 + self.sync += [ + last_data.eq(self.data[:10]), + data.eq(Cat(self.data, last_data)), + If(self.reset, + self.bitshift.eq(0), + self.detected.eq(0), + ), + + If(self.reset, + self.comma_aligned.eq(0) + ).Elif((self.data[:10] == comma) | (self.data[:10] == comma_n), + self.comma_aligned.eq(1) + ), + ] + + for n in range(width): + self.sync += \ + If((data[n:n+10] == comma) | (data[n:n+10] == comma_n), + self.bitshift.eq(width-n), + self.detected.eq(1), + ) + # Warning: Xilinx transceivers are LSB first, and comma needs to be flipped # compared to the usual 8b10b binary representation. class Manual_Aligner(Module): - def __init__(self, comma, check_cycles=20000): - + def __init__(self, comma, check_period=50_000, width=20): self.rxslide = Signal() - self.rxdata = Signal(20) + self.data = Signal(width) + self.rxinit_done = Signal() self.ready = Signal() + self.restart = Signal() # # # - checks_reset = Signal() - error_seen = Signal() - comma_seen = Signal() + self.submodules.restart_ps = restart_ps = PulseSynchronizer("cxp_gtx_rx", "sys") - rx1cnt = Signal(max=11) + timerout_period = 5_000_000 + timerout = Signal(reset=timerout_period-1, max=timerout_period) - comma_n = ~comma & 0b1111111111 - self.sync.cxp_gtx_rx += [ - rx1cnt.eq(reduce(add, [self.rxdata[i] for i in range(10)])), - If(checks_reset, - error_seen.eq(0) - ).Elif((rx1cnt != 4) & (rx1cnt != 5) & (rx1cnt != 6), - error_seen.eq(1) + ready_sys = Signal() + self.specials += MultiReg(self.ready, ready_sys) + + # NOTE: be careful of all the timeout values!!! It should be much larger than the longest fsm + # TODO: fix comma fall too fast for 500MHz (10Gpbs) -> need to change CDR_CFG via DRP + # Restart rx periodically since rx need to be restart when connecting RXN/RXP + self.sync += [ + self.restart.eq(0), + If(restart_ps.o, + timerout.eq(timerout.reset), + self.restart.eq(1), ), - If(checks_reset, - comma_seen.eq(0) - ).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n), - comma_seen.eq(1) + If(~ready_sys, + If((timerout == 0), + timerout.eq(timerout.reset), + self.restart.eq(1), + ).Else( + timerout.eq(timerout - 1), + ) ) ] - # minimum of 32 RXUSRCLK2 cycles are required between two RXSLIDE pulses - slide_timer = ClockDomainsRenamer("cxp_gtx_rx")(WaitTimer(64)) - self.submodules += slide_timer - counter = Signal(reset=check_cycles-1, max=check_cycles) - fsm = ClockDomainsRenamer("cxp_gtx_rx")(FSM(reset_state="IDLE")) - self.submodules += fsm + self.submodules.comma_det = comma_det = ClockDomainsRenamer("cxp_gtx_rx")(Comma_Detector(comma, width)) + self.comb += comma_det.data.eq(self.data) + + # minimum of 32 RXUSRCLK2 cycles are required between two RXSLIDE pulses + # There is latency between the slide and the slide result at RXDATA + # Max = 967.5 UI + # see 42662 - 7 Series GTX Transceivers - TX and RX Latency Values + # https://support.xilinx.com/s/article/42662?language=en_US + self.submodules.timer = timer = ClockDomainsRenamer("cxp_gtx_rx")(WaitTimer(64)) + + # DS191 (v1.18.1) Table 95 + # Tlock = 50000 UI + self.submodules.cdr_stable_timer = cdr_stable_timer = ClockDomainsRenamer("cxp_gtx_rx")(WaitTimer(check_period)) + self.submodules.fsm = fsm = ClockDomainsRenamer("cxp_gtx_rx")(FSM(reset_state="IDLE")) + + + # NOTE: if this is connected to PMOD it will work on 250MHz + # BUG: somehow the comma det doesn't work after the cdr_stable_timer :( thus, waiting for the timeout to happen + # (which I know from bruteforcealigner, doesn't work well :( ) + rxinit_done_rxclk = Signal() + self.specials += MultiReg(self.rxinit_done, rxinit_done_rxclk, odomain="cxp_gtx_rx") + + check_timer = Signal(reset=check_period-1,max=check_period) + slide_counter = Signal(max=width) fsm.act("IDLE", - slide_timer.wait.eq(1), - If(slide_timer.done, - If(comma_seen, - NextState("READY"), - ).Else( - NextState("SLIDING") - ) - ) + comma_det.reset.eq(1), + If(rxinit_done_rxclk, + NextState("WAIT_COMMA_DET"), + ), ) - fsm.act("SLIDING", - self.rxslide.eq(1), - checks_reset.eq(1), - NextState("IDLE") + fsm.act("WAIT_COMMA_DET", + cdr_stable_timer.wait.eq(1), + If(cdr_stable_timer.done, + If(comma_det.comma_aligned, + NextState("READY") + ).Else( + restart_ps.i.eq(1), + NextState("IDLE"), + ) + ), ) fsm.act("READY", self.ready.eq(1), - If(counter == 0, - NextValue(counter, check_cycles - 1), - If(error_seen, + If(check_timer == 0, + NextValue(check_timer, check_timer.reset), + comma_det.reset.eq(1), + If(~comma_det.comma_aligned, + restart_ps.i.eq(1), NextState("IDLE"), ) ).Else( - NextValue(counter, counter - 1), + NextValue(check_timer, check_timer - 1), ) ) + + # fsm.act("IDLE", + # comma_det.reset.eq(1), + # If(rxinit_done_rxclk, + # cdr_stable_timer.wait.eq(1), + # If(cdr_stable_timer.done, + # NextState("WAIT_COMMA_DET"), + # ), + # ), + # ) + + # fsm.act("WAIT_COMMA_DET", + # If(comma_det.detected, + # NextValue(slide_counter, comma_det.bitshift), + # NextState("SLIDING") + # ) + # ) + + # fsm.act("SLIDING", + # comma_det.reset.eq(1), + # If(slide_counter == 0, + # NextValue(check_timer, check_timer.reset), + # NextState("READY") + # ).Else( + # NextValue(slide_counter, slide_counter - 1), + # self.rxslide.eq(1), + # NextState("WAIT_DATA_SLIDE"), + # ) + # ) + + # fsm.act("WAIT_DATA_SLIDE", + # comma_det.reset.eq(1), + # timer.wait.eq(1), + # If(timer.done, + # NextState("SLIDING"), + # ) + # ) + + # fsm.act("READY", + # self.ready.eq(1), + # If(check_timer == 0, + # NextValue(check_timer, check_timer.reset), + # comma_det.reset.eq(1), + # If(~comma_det.comma_aligned, + # restart_ps.i.eq(1), + # NextState("IDLE"), + # ) + # ).Else( + # NextValue(check_timer, check_timer - 1), + # ) + # ) class GTX(Module): @@ -364,7 +488,7 @@ class GTX(Module): # TX generates cxp_tx clock, init must be in system domain # FIXME: 500e6 is used to fix Xx reset by holding gtxXxreset for a couple cycle more self.submodules.tx_init = tx_init = GTXInit(500e6, False, mode=tx_mode) - self.submodules.rx_init = rx_init = GTXInit(500e6, True, mode=rx_mode) + self.submodules.rx_init = rx_init = GTXInit(sys_clk_freq, True, mode=rx_mode) # RX receives restart commands from txusrclk domain # self.submodules.rx_init = rx_init = ClockDomainsRenamer("cxp_gtx_tx")(GTXInit(500e6, True, mode=rx_mode)) @@ -507,7 +631,7 @@ class GTX(Module): # RX Byte and Word Alignment Attributes p_ALIGN_COMMA_DOUBLE="FALSE", p_ALIGN_COMMA_ENABLE=0b1111111111, - p_ALIGN_COMMA_WORD=2, + p_ALIGN_COMMA_WORD=2, # allow rxslide to shift 20 times p_ALIGN_MCOMMA_DET="FALSE", p_ALIGN_MCOMMA_VALUE=0b1010000011, p_ALIGN_PCOMMA_DET="FALSE", @@ -516,11 +640,9 @@ class GTX(Module): p_RXSLIDE_AUTO_WAIT=7, p_RXSLIDE_MODE="PCS", p_RX_SIG_VALID_DLY=10, - - # Manual Word Alignment i_RXPCOMMAALIGNEN=0, i_RXMCOMMAALIGNEN=0, - i_RXCOMMADETEN=1, # enable word alignment, but breaks rxrestart if gtxXxreset hold too short + i_RXCOMMADETEN=0, # enable manual word alignment i_RXSLIDE=rxslide, # RX 8B/10B Decoder Attributes @@ -650,10 +772,11 @@ class GTX(Module): self.submodules.clk_aligner = clk_aligner = Manual_Aligner(0b0101111100) self.comb += [ - clk_aligner.rxdata.eq(rxdata), + clk_aligner.data.eq(rxdata), + clk_aligner.rxinit_done.eq(rx_init.done), rxslide.eq(clk_aligner.rxslide), self.rx_ready.eq(clk_aligner.ready), - rx_init.restart.eq(self.rx_restart), + rx_init.restart.eq(self.rx_restart | clk_aligner.restart), tx_init.restart.eq(self.tx_restart), ]