From d27727968cd1bdb9bf0e9d7d92e180aa208c76b5 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 19 Jan 2018 12:17:54 +0100 Subject: [PATCH] add artix7 gtp (3gbps), share clock aligner with gth_ultrascale --- .../drtio/transceiver/clock_aligner.py | 114 +++++++ .../drtio/transceiver/gth_ultrascale.py | 3 +- .../drtio/transceiver/gth_ultrascale_init.py | 112 +------ .../gateware/drtio/transceiver/gtp_7series.py | 251 ++++++++++++++ .../drtio/transceiver/gtp_7series_init.py | 305 ++++++++++++++++++ 5 files changed, 672 insertions(+), 113 deletions(-) create mode 100644 artiq/gateware/drtio/transceiver/clock_aligner.py create mode 100644 artiq/gateware/drtio/transceiver/gtp_7series.py create mode 100644 artiq/gateware/drtio/transceiver/gtp_7series_init.py diff --git a/artiq/gateware/drtio/transceiver/clock_aligner.py b/artiq/gateware/drtio/transceiver/clock_aligner.py new file mode 100644 index 000000000..9d17d2b77 --- /dev/null +++ b/artiq/gateware/drtio/transceiver/clock_aligner.py @@ -0,0 +1,114 @@ +from math import ceil +from functools import reduce +from operator import add + +from migen import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer + + +# Changes the phase of the transceiver RX clock to align the comma to +# the LSBs of RXDATA, fixing the latency. +# +# This is implemented by repeatedly resetting the transceiver until it +# gives out the correct phase. Each reset gives a random phase. +# +# If Xilinx had designed the GTX transceiver correctly, RXSLIDE_MODE=PMA +# would achieve this faster and in a cleaner way. But: +# * the phase jumps are of 2 UI at every second RXSLIDE pulse, instead +# of 1 UI at every pulse. It is unclear what the latency becomes. +# * RXSLIDE_MODE=PMA cannot be used with the RX buffer bypassed. +# Those design flaws make RXSLIDE_MODE=PMA yet another broken and useless +# transceiver "feature". +# +# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped +# compared to the usual 8b10b binary representation. +class BruteforceClockAligner(Module): + def __init__(self, comma, tx_clk_freq, check_period=6e-3): + self.rxdata = Signal(20) + self.restart = Signal() + + self.ready = Signal() + + check_max_val = ceil(check_period*tx_clk_freq) + check_counter = Signal(max=check_max_val+1) + check = Signal() + reset_check_counter = Signal() + self.sync.rtio_tx += [ + check.eq(0), + If(reset_check_counter, + check_counter.eq(check_max_val) + ).Else( + If(check_counter == 0, + check.eq(1), + check_counter.eq(check_max_val) + ).Else( + check_counter.eq(check_counter-1) + ) + ) + ] + + checks_reset = PulseSynchronizer("rtio_tx", "rtio_rx") + self.submodules += checks_reset + + comma_n = ~comma & 0b1111111111 + comma_seen_rxclk = Signal() + comma_seen = Signal() + comma_seen_rxclk.attr.add("no_retiming") + self.specials += MultiReg(comma_seen_rxclk, comma_seen) + self.sync.rtio_rx += \ + If(checks_reset.o, + comma_seen_rxclk.eq(0) + ).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n), + comma_seen_rxclk.eq(1) + ) + + error_seen_rxclk = Signal() + error_seen = Signal() + error_seen_rxclk.attr.add("no_retiming") + self.specials += MultiReg(error_seen_rxclk, error_seen) + rx1cnt = Signal(max=11) + self.sync.rtio_rx += [ + rx1cnt.eq(reduce(add, [self.rxdata[i] for i in range(10)])), + If(checks_reset.o, + error_seen_rxclk.eq(0) + ).Elif((rx1cnt != 4) & (rx1cnt != 5) & (rx1cnt != 6), + error_seen_rxclk.eq(1) + ) + ] + + fsm = ClockDomainsRenamer("rtio_tx")(FSM(reset_state="WAIT_COMMA")) + self.submodules += fsm + + fsm.act("WAIT_COMMA", + If(check, + # Errors are still OK at this stage, as the transceiver + # has just been reset and may output garbage data. + If(comma_seen, + NextState("WAIT_NOERROR") + ).Else( + self.restart.eq(1) + ), + checks_reset.i.eq(1) + ) + ) + fsm.act("WAIT_NOERROR", + If(check, + If(comma_seen & ~error_seen, + NextState("READY") + ).Else( + self.restart.eq(1), + NextState("WAIT_COMMA") + ), + checks_reset.i.eq(1) + ) + ) + fsm.act("READY", + reset_check_counter.eq(1), + self.ready.eq(1), + If(error_seen, + checks_reset.i.eq(1), + self.restart.eq(1), + NextState("WAIT_COMMA") + ) + ) + diff --git a/artiq/gateware/drtio/transceiver/gth_ultrascale.py b/artiq/gateware/drtio/transceiver/gth_ultrascale.py index d6a74bb04..5d5cf2575 100644 --- a/artiq/gateware/drtio/transceiver/gth_ultrascale.py +++ b/artiq/gateware/drtio/transceiver/gth_ultrascale.py @@ -3,14 +3,13 @@ from operator import or_ from migen import * from migen.genlib.resetsync import AsyncResetSynchronizer -from migen.genlib.cdc import MultiReg -from misoc.interconnect.csr import * from misoc.cores.code_8b10b import Encoder, Decoder from microscope import * from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface +from artiq.gateware.drtio.transceiver.clock_aligner import BruteforceClockAligner from artiq.gateware.drtio.transceiver.gth_ultrascale_init import * diff --git a/artiq/gateware/drtio/transceiver/gth_ultrascale_init.py b/artiq/gateware/drtio/transceiver/gth_ultrascale_init.py index 0c2734051..8d188f6c0 100644 --- a/artiq/gateware/drtio/transceiver/gth_ultrascale_init.py +++ b/artiq/gateware/drtio/transceiver/gth_ultrascale_init.py @@ -1,13 +1,11 @@ from math import ceil -from functools import reduce -from operator import add from migen import * from migen.genlib.cdc import MultiReg, PulseSynchronizer from migen.genlib.misc import WaitTimer -__all__ = ["BruteforceClockAligner", "GTHInit"] +__all__ = ["GTHInit"] class GTHInit(Module): @@ -140,111 +138,3 @@ class GTHInit(Module): self.done.eq(1), If(self.restart, NextState("RESET_ALL")) ) - - -# Changes the phase of the transceiver RX clock to align the comma to -# the LSBs of RXDATA, fixing the latency. -# -# This is implemented by repeatedly resetting the transceiver until it -# gives out the correct phase. Each reset gives a random phase. -# -# If Xilinx had designed the GTX transceiver correctly, RXSLIDE_MODE=PMA -# would achieve this faster and in a cleaner way. But: -# * the phase jumps are of 2 UI at every second RXSLIDE pulse, instead -# of 1 UI at every pulse. It is unclear what the latency becomes. -# * RXSLIDE_MODE=PMA cannot be used with the RX buffer bypassed. -# Those design flaws make RXSLIDE_MODE=PMA yet another broken and useless -# transceiver "feature". -# -# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped -# compared to the usual 8b10b binary representation. -class BruteforceClockAligner(Module): - def __init__(self, comma, tx_clk_freq, check_period=6e-3): - self.rxdata = Signal(20) - self.restart = Signal() - - self.ready = Signal() - - check_max_val = ceil(check_period*tx_clk_freq) - check_counter = Signal(max=check_max_val+1) - check = Signal() - reset_check_counter = Signal() - self.sync.rtio_tx += [ - check.eq(0), - If(reset_check_counter, - check_counter.eq(check_max_val) - ).Else( - If(check_counter == 0, - check.eq(1), - check_counter.eq(check_max_val) - ).Else( - check_counter.eq(check_counter-1) - ) - ) - ] - - checks_reset = PulseSynchronizer("rtio_tx", "rtio_rx") - self.submodules += checks_reset - - comma_n = ~comma & 0b1111111111 - comma_seen_rxclk = Signal() - comma_seen = Signal() - comma_seen_rxclk.attr.add("no_retiming") - self.specials += MultiReg(comma_seen_rxclk, comma_seen) - self.sync.rtio_rx += \ - If(checks_reset.o, - comma_seen_rxclk.eq(0) - ).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n), - comma_seen_rxclk.eq(1) - ) - - error_seen_rxclk = Signal() - error_seen = Signal() - error_seen_rxclk.attr.add("no_retiming") - self.specials += MultiReg(error_seen_rxclk, error_seen) - rx1cnt = Signal(max=11) - self.sync.rtio_rx += [ - rx1cnt.eq(reduce(add, [self.rxdata[i] for i in range(10)])), - If(checks_reset.o, - error_seen_rxclk.eq(0) - ).Elif((rx1cnt != 4) & (rx1cnt != 5) & (rx1cnt != 6), - error_seen_rxclk.eq(1) - ) - ] - - fsm = ClockDomainsRenamer("rtio_tx")(FSM(reset_state="WAIT_COMMA")) - self.submodules += fsm - - fsm.act("WAIT_COMMA", - If(check, - # Errors are still OK at this stage, as the transceiver - # has just been reset and may output garbage data. - If(comma_seen, - NextState("WAIT_NOERROR") - ).Else( - self.restart.eq(1) - ), - checks_reset.i.eq(1) - ) - ) - fsm.act("WAIT_NOERROR", - If(check, - If(comma_seen & ~error_seen, - NextState("READY") - ).Else( - self.restart.eq(1), - NextState("WAIT_COMMA") - ), - checks_reset.i.eq(1) - ) - ) - fsm.act("READY", - reset_check_counter.eq(1), - self.ready.eq(1), - If(error_seen, - checks_reset.i.eq(1), - self.restart.eq(1), - NextState("WAIT_COMMA") - ) - ) - diff --git a/artiq/gateware/drtio/transceiver/gtp_7series.py b/artiq/gateware/drtio/transceiver/gtp_7series.py new file mode 100644 index 000000000..4c6fed24b --- /dev/null +++ b/artiq/gateware/drtio/transceiver/gtp_7series.py @@ -0,0 +1,251 @@ +from functools import reduce +from operator import or_ + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from misoc.cores.code_8b10b import Encoder, Decoder + +from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface +from artiq.gateware.drtio.transceiver.clock_aligner import BruteforceClockAligner +from artiq.gateware.drtio.transceiver.gtp_7series_init import * + + +class GTPSingle(Module): + def __init__(self, qpll_channel, tx_pads, rx_pads, sys_clk_freq, rtio_clk_freq, mode): + if mode != "master": + raise NotImplementedError + self.submodules.encoder = encoder = ClockDomainsRenamer("rtio_tx")( + Encoder(2, True)) + self.submodules.decoders = decoders = [ClockDomainsRenamer("rtio_rx")( + (Decoder(True))) for _ in range(2)] + self.rx_ready = Signal() + + # transceiver direct clock outputs + # useful to specify clock constraints in a way palatable to Vivado + self.txoutclk = Signal() + self.rxoutclk = Signal() + + # # # + + # TX generates RTIO clock, init must be in system domain + tx_init = GTPTXInit(sys_clk_freq) + # RX receives restart commands from RTIO domain + rx_init = ClockDomainsRenamer("rtio_tx")(GTPRXInit(rtio_clk_freq)) + self.submodules += tx_init, rx_init + + self.comb += [ + qpll_channel.reset.eq(tx_init.pllreset), + tx_init.plllock.eq(qpll_channel.lock), + rx_init.plllock.eq(qpll_channel.lock), + ] + + txdata = Signal(20) + rxdata = Signal(20) + rxphaligndone = Signal() + self.specials += \ + Instance("GTPE2_CHANNEL", + # Reset modes + i_GTRESETSEL=0, + i_RESETOVRD=0, + + # DRP + i_DRPADDR=rx_init.drpaddr, + i_DRPCLK=ClockSignal("rtio_tx"), + i_DRPDI=rx_init.drpdi, + o_DRPDO=rx_init.drpdo, + i_DRPEN=rx_init.drpen, + o_DRPRDY=rx_init.drprdy, + i_DRPWE=rx_init.drpwe, + + # PMA Attributes + p_PMA_RSV=0x333, + p_PMA_RSV2=0x2040, + p_PMA_RSV3=0, + p_PMA_RSV4=0, + p_RX_BIAS_CFG=0b0000111100110011, + p_RX_CM_SEL=0b01, + p_RX_CM_TRIM=0b1010, + p_RX_OS_CFG=0b10000000, + p_RXLPM_IPCM_CFG=1, + i_RXELECIDLEMODE=0b11, + i_RXOSINTCFG=0b0010, + i_RXOSINTEN=1, + + # Power-Down Attributes + p_PD_TRANS_TIME_FROM_P2=0x3c, + p_PD_TRANS_TIME_NONE_P2=0x3c, + p_PD_TRANS_TIME_TO_P2=0x64, + + # QPLL + i_PLL0CLK=qpll_channel.clk, + i_PLL0REFCLK=qpll_channel.refclk, + + # TX clock + p_TXBUF_EN="FALSE", + p_TX_XCLK_SEL="TXUSR", + o_TXOUTCLK=self.txoutclk, + p_TXOUT_DIV=2, + i_TXSYSCLKSEL=0b00, + i_TXOUTCLKSEL=0b11, + + # TX Startup/Reset + i_GTTXRESET=tx_init.gttxreset, + o_TXRESETDONE=tx_init.txresetdone, + p_TXSYNC_OVRD=1, + i_TXDLYSRESET=tx_init.txdlysreset, + o_TXDLYSRESETDONE=tx_init.txdlysresetdone, + i_TXPHINIT=tx_init.txphinit, + o_TXPHINITDONE=tx_init.txphinitdone, + i_TXPHALIGNEN=1, + i_TXPHALIGN=tx_init.txphalign, + o_TXPHALIGNDONE=tx_init.txphaligndone, + i_TXDLYEN=tx_init.txdlyen, + i_TXUSERRDY=tx_init.txuserrdy, + + # TX data + p_TX_DATA_WIDTH=20, + i_TXCHARDISPMODE=Cat(txdata[9], txdata[19]), + i_TXCHARDISPVAL=Cat(txdata[8], txdata[18]), + i_TXDATA=Cat(txdata[:8], txdata[10:18]), + i_TXUSRCLK=ClockSignal("rtio_tx"), + i_TXUSRCLK2=ClockSignal("rtio_tx"), + + # TX electrical + i_TXBUFDIFFCTRL=0b100, + i_TXDIFFCTRL=0b1000, + + # RX Startup/Reset + i_GTRXRESET=rx_init.gtrxreset, + o_RXRESETDONE=rx_init.rxresetdone, + i_RXDLYSRESET=rx_init.rxdlysreset, + o_RXDLYSRESETDONE=rx_init.rxdlysresetdone, + o_RXPHALIGNDONE=rxphaligndone, + i_RXSYNCALLIN=rxphaligndone, + i_RXUSERRDY=rx_init.rxuserrdy, + i_RXSYNCIN=0, + i_RXSYNCMODE=1, + p_RXSYNC_MULTILANE=0, + p_RXSYNC_OVRD=0, + o_RXSYNCDONE=rx_init.rxsyncdone, + p_RXPMARESET_TIME=0b11, + o_RXPMARESETDONE=rx_init.rxpmaresetdone, + + # RX clock + p_RX_CLK25_DIV=5, + p_TX_CLK25_DIV=5, + p_RX_XCLK_SEL="RXUSR", + p_RXOUT_DIV=2, + i_RXSYSCLKSEL=0b00, + i_RXOUTCLKSEL=0b010, + o_RXOUTCLK=self.rxoutclk, + i_RXUSRCLK=ClockSignal("rtio_rx"), + i_RXUSRCLK2=ClockSignal("rtio_rx"), + p_RXCDR_CFG=0x0000107FE206001041010, + p_RXPI_CFG1=1, + p_RXPI_CFG2=1, + + # RX Clock Correction Attributes + p_CLK_CORRECT_USE="FALSE", + + # RX data + p_RXBUF_EN="FALSE", + p_RXDLY_CFG=0x001f, + p_RXDLY_LCFG=0x030, + p_RXPHDLY_CFG=0x084020, + p_RXPH_CFG=0xc00002, + p_RX_DATA_WIDTH=20, + i_RXCOMMADETEN=1, + i_RXDLYBYPASS=0, + i_RXDDIEN=1, + o_RXDISPERR=Cat(rxdata[9], rxdata[19]), + o_RXCHARISK=Cat(rxdata[8], rxdata[18]), + o_RXDATA=Cat(rxdata[:8], rxdata[10:18]), + + # Pads + i_GTPRXP=rx_pads.p, + i_GTPRXN=rx_pads.n, + o_GTPTXP=tx_pads.p, + o_GTPTXN=tx_pads.n + ) + + # tx clocking + tx_reset_deglitched = Signal() + tx_reset_deglitched.attr.add("no_retiming") + self.sync += tx_reset_deglitched.eq(~tx_init.done) + self.clock_domains.cd_rtio_tx = ClockDomain() + if mode == "master": + txoutclk_bufg = Signal() + txoutclk_bufr = Signal() + tx_bufr_div = 150.e6/rtio_clk_freq + assert tx_bufr_div == int(tx_bufr_div) + self.specials += [ + Instance("BUFG", i_I=self.txoutclk, o_O=txoutclk_bufg), + Instance("BUFR", i_I=txoutclk_bufg, o_O=txoutclk_bufr, + i_CE=1, p_BUFR_DIVIDE=str(int(tx_bufr_div))), + Instance("BUFG", i_I=txoutclk_bufr, o_O=self.cd_rtio_tx.clk) + ] + self.specials += AsyncResetSynchronizer(self.cd_rtio_tx, tx_reset_deglitched) + + # rx clocking + rx_reset_deglitched = Signal() + rx_reset_deglitched.attr.add("no_retiming") + self.sync.rtio_tx += rx_reset_deglitched.eq(~rx_init.done) + self.clock_domains.cd_rtio_rx = ClockDomain() + self.specials += [ + Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_rtio_rx.clk), + AsyncResetSynchronizer(self.cd_rtio_rx, rx_reset_deglitched) + ] + + # tx data + self.comb += txdata.eq(Cat(*[encoder.output[i] for i in range(2)])) + + # rx data + for i in range(2): + self.comb += decoders[i].input.eq(rxdata[10*i:10*(i+1)]) + + # clock alignment + clock_aligner = BruteforceClockAligner(0b0101111100, rtio_clk_freq, check_period=10e-3) + 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) + ] + + +class GTP(Module, TransceiverInterface): + def __init__(self, qpll_channel, tx_pads, rx_pads, sys_clk_freq, rtio_clk_freq, master=0): + self.nchannels = nchannels = len(tx_pads) + self.gtps = [] + if nchannels >= 1: + raise NotImplementedError + + # # # + + rtio_tx_clk = Signal() + channel_interfaces = [] + for i in range(nchannels): + mode = "master" if i == master else "slave" + gtp = GTPSingle(qpll_channel, tx_pads[i], rx_pads[i], sys_clk_freq, rtio_clk_freq, mode) + if mode == "master": + self.comb += rtio_tx_clk.eq(gtp.cd_rtio_tx.clk) + else: + self.comb += gtp.cd_rtio_tx.clk.eq(rtio_tx_clk) + self.gtps.append(gtp) + setattr(self.submodules, "gtp"+str(i), gtp) + channel_interface = ChannelInterface(gtp.encoder, gtp.decoders) + self.comb += channel_interface.rx_ready.eq(gtp.rx_ready) + channel_interfaces.append(channel_interface) + + TransceiverInterface.__init__(self, channel_interfaces) + + self.comb += [ + self.cd_rtio.clk.eq(self.gtps[master].cd_rtio_tx.clk), + self.cd_rtio.rst.eq(reduce(or_, [gtp.cd_rtio_tx.rst for gtp in self.gtps])) + ] + for i in range(nchannels): + self.comb += [ + getattr(self, "cd_rtio_rx" + str(i)).clk.eq(self.gtps[i].cd_rtio_rx.clk), + getattr(self, "cd_rtio_rx" + str(i)).rst.eq(self.gtps[i].cd_rtio_rx.rst) + ] diff --git a/artiq/gateware/drtio/transceiver/gtp_7series_init.py b/artiq/gateware/drtio/transceiver/gtp_7series_init.py new file mode 100644 index 000000000..e3f2d9316 --- /dev/null +++ b/artiq/gateware/drtio/transceiver/gtp_7series_init.py @@ -0,0 +1,305 @@ +from math import ceil + +from migen import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer +from migen.genlib.misc import WaitTimer + + +__all__ = ["GTPTXInit", "GTPRXInit"] + + +class GTPTXInit(Module): + def __init__(self, sys_clk_freq): + self.done = Signal() + self.restart = Signal() + + # GTP signals + self.plllock = Signal() + self.pllreset = Signal() + self.gttxreset = Signal() + self.txresetdone = Signal() + self.txdlysreset = Signal() + self.txdlysresetdone = Signal() + self.txphinit = Signal() + self.txphinitdone = Signal() + self.txphalign = Signal() + self.txphaligndone = Signal() + self.txdlyen = Signal() + self.txuserrdy = Signal() + + # # # + + # Double-latch transceiver asynch outputs + plllock = Signal() + txresetdone = Signal() + txdlysresetdone = Signal() + txphinitdone = Signal() + txphaligndone = Signal() + self.specials += [ + MultiReg(self.plllock, plllock), + MultiReg(self.txresetdone, txresetdone), + MultiReg(self.txdlysresetdone, txdlysresetdone), + MultiReg(self.txphinitdone, txphinitdone), + MultiReg(self.txphaligndone, txphaligndone) + ] + + # Deglitch FSM outputs driving transceiver asynch inputs + gttxreset = Signal() + txdlysreset = Signal() + txphinit = Signal() + txphalign = Signal() + txdlyen = Signal() + txuserrdy = Signal() + self.sync += [ + self.gttxreset.eq(gttxreset), + self.txdlysreset.eq(txdlysreset), + self.txphinit.eq(txphinit), + self.txphalign.eq(txphalign), + self.txdlyen.eq(txdlyen), + self.txuserrdy.eq(txuserrdy) + ] + + # PLL reset must be at least 500us + pll_reset_cycles = ceil(500e-9*sys_clk_freq) + pll_reset_timer = WaitTimer(pll_reset_cycles) + self.submodules += pll_reset_timer + + startup_fsm = ResetInserter()(FSM(reset_state="PLL_RESET")) + self.submodules += startup_fsm + + ready_timer = WaitTimer(int(1e-3*sys_clk_freq)) + self.submodules += ready_timer + self.comb += [ + ready_timer.wait.eq(~self.done & ~startup_fsm.reset), + startup_fsm.reset.eq(self.restart | ready_timer.done) + ] + + txphaligndone_r = Signal(reset=1) + txphaligndone_rising = Signal() + self.sync += txphaligndone_r.eq(txphaligndone) + self.comb += txphaligndone_rising.eq(txphaligndone & ~txphaligndone_r) + + startup_fsm.act("PLL_RESET", + self.pllreset.eq(1), + pll_reset_timer.wait.eq(1), + If(pll_reset_timer.done, + NextState("GTP_RESET") + ) + ) + startup_fsm.act("GTP_RESET", + gttxreset.eq(1), + If(plllock, + NextState("WAIT_GTP_RESET_DONE") + ) + ) + # Release GTP reset and wait for GTP resetdone + # (from UG482, GTP is reset on falling edge + # of gttxreset) + startup_fsm.act("WAIT_GTP_RESET_DONE", + txuserrdy.eq(1), + If(txresetdone, NextState("ALIGN")) + ) + # Start delay alignment + startup_fsm.act("ALIGN", + txuserrdy.eq(1), + txdlysreset.eq(1), + If(txdlysresetdone, + NextState("PHALIGN") + ) + ) + # Start phase alignment + startup_fsm.act("PHALIGN", + txuserrdy.eq(1), + txphinit.eq(1), + If(txphinitdone, + NextState("WAIT_FIRST_ALIGN_DONE") + ) + ) + # Wait 2 rising edges of Xxphaligndone + # (from UG482 in TX Buffer Bypass in Single-Lane Auto Mode) + startup_fsm.act("WAIT_FIRST_ALIGN_DONE", + txuserrdy.eq(1), + txphalign.eq(1), + If(txphaligndone_rising, + NextState("WAIT_SECOND_ALIGN_DONE") + ) + ) + startup_fsm.act("WAIT_SECOND_ALIGN_DONE", + txuserrdy.eq(1), + txdlyen.eq(1), + If(txphaligndone_rising, + NextState("READY") + ) + ) + startup_fsm.act("READY", + txuserrdy.eq(1), + self.done.eq(1), + If(self.restart, NextState("PLL_RESET")) + ) + + +class GTPRXInit(Module): + def __init__(self, sys_clk_freq): + self.done = Signal() + self.restart = Signal() + + # GTP signals + self.plllock = Signal() + self.gtrxreset = Signal() + self.rxresetdone = Signal() + self.rxdlysreset = Signal() + self.rxdlysresetdone = Signal() + self.rxphalign = Signal() + self.rxdlyen = Signal() + self.rxuserrdy = Signal() + self.rxsyncdone = Signal() + self.rxpmaresetdone = Signal() + + self.drpaddr = Signal(9) + self.drpen = Signal() + self.drpdi = Signal(16) + self.drprdy = Signal() + self.drpdo = Signal(16) + self.drpwe = Signal() + + # # # + + drpvalue = Signal(16) + drpmask = Signal() + self.comb += [ + self.drpaddr.eq(0x011), + If(drpmask, + self.drpdi.eq(drpvalue & 0xf7ff) + ).Else( + self.drpdi.eq(drpvalue) + ) + ] + + rxpmaresetdone = Signal() + self.specials += MultiReg(self.rxpmaresetdone, rxpmaresetdone) + rxpmaresetdone_r = Signal() + self.sync += rxpmaresetdone_r.eq(rxpmaresetdone) + + # Double-latch transceiver asynch outputs + plllock = Signal() + rxresetdone = Signal() + rxdlysresetdone = Signal() + rxsyncdone = Signal() + self.specials += [ + MultiReg(self.plllock, plllock), + MultiReg(self.rxresetdone, rxresetdone), + MultiReg(self.rxdlysresetdone, rxdlysresetdone), + MultiReg(self.rxsyncdone, rxsyncdone) + ] + + # Deglitch FSM outputs driving transceiver asynch inputs + gtrxreset = Signal() + rxdlysreset = Signal() + rxphalign = Signal() + rxdlyen = Signal() + rxuserrdy = Signal() + self.sync += [ + self.gtrxreset.eq(gtrxreset), + self.rxdlysreset.eq(rxdlysreset), + self.rxphalign.eq(rxphalign), + self.rxdlyen.eq(rxdlyen), + self.rxuserrdy.eq(rxuserrdy) + ] + + # After configuration, transceiver resets have to stay low for + # at least 500ns (see AR43482) + pll_reset_cycles = ceil(500e-9*sys_clk_freq) + pll_reset_timer = WaitTimer(pll_reset_cycles) + self.submodules += pll_reset_timer + + startup_fsm = ResetInserter()(FSM(reset_state="GTP_RESET")) + self.submodules += startup_fsm + + ready_timer = WaitTimer(int(4e-3*sys_clk_freq)) + self.submodules += ready_timer + self.comb += [ + ready_timer.wait.eq(~self.done & ~startup_fsm.reset), + startup_fsm.reset.eq(self.restart | ready_timer.done) + ] + + cdr_stable_timer = WaitTimer(1024) + self.submodules += cdr_stable_timer + + startup_fsm.act("GTP_RESET", + gtrxreset.eq(1), + NextState("DRP_READ_ISSUE") + ) + startup_fsm.act("DRP_READ_ISSUE", + gtrxreset.eq(1), + self.drpen.eq(1), + NextState("DRP_READ_WAIT") + ) + startup_fsm.act("DRP_READ_WAIT", + gtrxreset.eq(1), + If(self.drprdy, + NextValue(drpvalue, self.drpdo), + NextState("DRP_MOD_ISSUE") + ) + ) + startup_fsm.act("DRP_MOD_ISSUE", + gtrxreset.eq(1), + drpmask.eq(1), + self.drpen.eq(1), + self.drpwe.eq(1), + NextState("DRP_MOD_WAIT") + ) + startup_fsm.act("DRP_MOD_WAIT", + gtrxreset.eq(1), + If(self.drprdy, + NextState("WAIT_PMARST_FALL") + ) + ) + startup_fsm.act("WAIT_PMARST_FALL", + rxuserrdy.eq(1), + If(rxpmaresetdone_r & ~rxpmaresetdone, + NextState("DRP_RESTORE_ISSUE") + ) + ) + startup_fsm.act("DRP_RESTORE_ISSUE", + rxuserrdy.eq(1), + self.drpen.eq(1), + self.drpwe.eq(1), + NextState("DRP_RESTORE_WAIT") + ) + startup_fsm.act("DRP_RESTORE_WAIT", + rxuserrdy.eq(1), + If(self.drprdy, + NextState("WAIT_GTP_RESET_DONE") + ) + ) + # Release GTP reset and wait for GTP resetdone + # (from UG482, GTP is reset on falling edge + # of gtrxreset) + startup_fsm.act("WAIT_GTP_RESET_DONE", + rxuserrdy.eq(1), + cdr_stable_timer.wait.eq(1), + If(rxresetdone & cdr_stable_timer.done, + NextState("ALIGN") + ) + ) + # Start delay alignment + startup_fsm.act("ALIGN", + rxuserrdy.eq(1), + rxdlysreset.eq(1), + If(rxdlysresetdone, + NextState("WAIT_ALIGN_DONE") + ) + ) + # Wait for delay alignment + startup_fsm.act("WAIT_ALIGN_DONE", + rxuserrdy.eq(1), + If(rxsyncdone, + NextState("READY") + ) + ) + startup_fsm.act("READY", + rxuserrdy.eq(1), + self.done.eq(1), + If(self.restart, NextState("GTP_RESET") + ) + )