diff --git a/src/gateware/cxp_downconn.py b/src/gateware/cxp_downconn.py index 4c1d1f4..bd83ba6 100644 --- a/src/gateware/cxp_downconn.py +++ b/src/gateware/cxp_downconn.py @@ -13,7 +13,7 @@ from functools import reduce class CXP_DownConn(Module, AutoCSR): def __init__(self, refclk, pads, sys_clk_freq, debug_sma, pmod_pads): self.rx_start_init = CSRStorage() - self.rx_restart = CSRStorage() + self.rx_restart = CSR() self.tx_start_init = CSRStorage() self.tx_restart = CSR() @@ -54,7 +54,7 @@ class CXP_DownConn(Module, AutoCSR): gtx.txenable.eq(self.txenable.storage[0]), gtx.tx_restart.eq(self.tx_restart.re), - gtx.rx_restart.eq(self.rx_restart.storage), + gtx.rx_restart.eq(self.rx_restart.re), gtx.tx_init.clk_path_ready.eq(self.tx_start_init.storage), gtx.rx_init.clk_path_ready.eq(self.rx_start_init.storage), # gtx.rx_alignment_en.eq(self.rx_data_alignment.storage), @@ -100,10 +100,10 @@ class CXP_DownConn(Module, AutoCSR): Instance("OBUF", i_I=gtx.cd_cxp_gtx_tx.clk, o_O=debug_sma.n_rx), # pmod 0-7 pin - Instance("OBUF", i_I=gtx.tx_init.gtXxreset, o_O=pmod_pads[0]), - Instance("OBUF", i_I=gtx.tx_init.Xxdlysreset, 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=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]), @@ -359,7 +359,76 @@ class CXP_BruteforceClockAligner(Module): ) ) +# 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): + self.rxslide = Signal() + self.rxdata = Signal(20) + + self.ready = Signal() + + # # # + + checks_reset = Signal() + error_seen = Signal() + comma_seen = Signal() + + rx1cnt = Signal(max=11) + + 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) + ), + If(checks_reset, + comma_seen.eq(0) + ).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n), + comma_seen.eq(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 + + fsm.act("IDLE", + slide_timer.wait.eq(1), + If(slide_timer.done, + If(comma_seen, + NextState("READY"), + ).Else( + NextState("SLIDING") + ) + ) + ) + + fsm.act("SLIDING", + self.rxslide.eq(1), + checks_reset.eq(1), + NextState("IDLE") + ) + + fsm.act("READY", + self.ready.eq(1), + If(counter == 0, + NextValue(counter, check_cycles - 1), + If(error_seen, + NextState("IDLE"), + ) + ).Else( + NextValue(counter, counter - 1), + ) + ) + class GTX(Module): # Settings: @@ -399,10 +468,12 @@ class GTX(Module): # # # # TX generates cxp_tx clock, init must be in system domain - # DEBUG: 500e6 is used to fix tx reset by holding gtxtxreset for a couple cycle more + # 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) + # RX receives restart commands from txusrclk domain - self.submodules.rx_init = rx_init = ClockDomainsRenamer("cxp_gtx_tx")(GTXInit(qpll.tx_usrclk_freq, True, mode=rx_mode)) + # self.submodules.rx_init = rx_init = ClockDomainsRenamer("cxp_gtx_tx")(GTXInit(500e6, True, mode=rx_mode)) self.comb += [ tx_init.cplllock.eq(qpll.lock), @@ -411,6 +482,8 @@ class GTX(Module): txdata = Signal(20) rxdata = Signal(20) + + rxslide = Signal() # Note: the following parameters were set after consulting AR45360 self.specials += \ Instance("GTXE2_CHANNEL", @@ -540,16 +613,22 @@ class GTX(Module): # RX Byte and Word Alignment Attributes p_ALIGN_COMMA_DOUBLE="FALSE", p_ALIGN_COMMA_ENABLE=0b1111111111, - p_ALIGN_COMMA_WORD=1, - p_ALIGN_MCOMMA_DET="TRUE", + p_ALIGN_COMMA_WORD=2, + p_ALIGN_MCOMMA_DET="FALSE", p_ALIGN_MCOMMA_VALUE=0b1010000011, - p_ALIGN_PCOMMA_DET="TRUE", + p_ALIGN_PCOMMA_DET="FALSE", p_ALIGN_PCOMMA_VALUE=0b0101111100, p_SHOW_REALIGN_COMMA="FALSE", 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_RXSLIDE=rxslide, + # RX 8B/10B Decoder Attributes p_RX_DISPERR_SEQ_MATCH="FALSE", p_DEC_MCOMMA_DETECT="TRUE", @@ -678,11 +757,21 @@ class GTX(Module): # 125MHz: align <1s # 156.25MHz: align <15s # 250MHz: cannot align - clock_aligner = CXP_BruteforceClockAligner(0b0101111100, 800_000) - self.submodules += clock_aligner + # clock_aligner = CXP_BruteforceClockAligner(0b0101111100, 1_000_000) + # 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), + # ] + + self.submodules.clk_aligner = clk_aligner = Manual_Aligner(0b0101111100) + self.comb += [ - clock_aligner.rxdata.eq(rxdata), - rx_init.restart.eq(clock_aligner.restart), - self.rx_ready.eq(clock_aligner.ready), - tx_init.restart.eq(self.tx_restart) + clk_aligner.rxdata.eq(rxdata), + rxslide.eq(clk_aligner.rxslide), + self.rx_ready.eq(clk_aligner.ready), + + rx_init.restart.eq(self.rx_restart), + tx_init.restart.eq(self.tx_restart), ]