Exemple #1
0
    def elaborate(self, platform):
        m = Module()

        if self.has_tx:
            m.d.comb += self.tx_t.oe.eq(1)
            if self.invert_tx:
                m.d.comb += self.tx_t.o.eq(~self.tx_o)
            else:
                m.d.comb += self.tx_t.o.eq(self.tx_o)

        if self.has_rx:
            if self.invert_rx:
                m.submodules += FFSynchronizer(~self.rx_t.i,
                                               self.rx_i,
                                               reset=1)
            else:
                m.submodules += FFSynchronizer(self.rx_t.i, self.rx_i, reset=1)

        return m
Exemple #2
0
    def elaborate(self, platform):
        m = Module()

        # Keep track of how many cycles we'll keep our PHY in reset.
        # This is larger than any requirement, in order to work with a broad swathe of PHYs,
        # in case a PHY other than the TUSB1310A ever makes it to the market.
        cycles_in_reset = int(5e-6 * 50e6)
        cycles_spent_in_reset = Signal(range(cycles_in_reset + 1))

        # Create versions of our phy_status signals that are observable:
        # 1) as an asynchronous inputs for startup pulses
        # 2) as a single-cycle pulse, for power-state-change notifications
        phy_status = Signal()

        # Convert our PHY status signal into a simple, sync-domain signal.
        m.submodules += FFSynchronizer(self.phy_status[0] | self.phy_status[1],
                                       phy_status),

        with m.FSM():

            # STARTUP_RESET -- post configuration, we'll reset the PIPE PHY.
            # This is distinct from the PHY's built-in power-on-reset, as we run this
            # on every FPGA configuration.
            with m.State("STARTUP_RESET"):
                m.d.comb += [
                    self.reset.eq(1),
                ]

                # Once we've extended past a reset time, we can move on.
                m.d.sync += cycles_spent_in_reset.eq(cycles_spent_in_reset + 1)
                with m.If(cycles_spent_in_reset == cycles_in_reset):
                    m.next = "DETECT_PHY_STARTUP"

            # DETECT_PHY_STARTUP -- post-reset, the PHY should drive its status line high.
            # We'll wait for this to happen, so we can track the PHY's progress.
            with m.State("DETECT_PHY_STARTUP"):

                with m.If(phy_status):
                    m.next = "WAIT_FOR_STARTUP"

            # WAIT_FOR_STARTUP -- we've now detected that the PHY is starting up.
            # We'll wait for that startup signal to be de-asserted, indicating that the PHY is ready.
            with m.State("WAIT_FOR_STARTUP"):

                # For now, we'll start up in P0. This will change once we implement proper RxDetect.
                with m.If(~phy_status):
                    m.next = "READY"

            # READY -- our PHY is all started up and ready for use.
            # For now, we'll remain here until we're reset.
            with m.State("READY"):
                m.d.comb += self.ready.eq(1)

        return m
Exemple #3
0
    def _synchronize_(self, m, output, o_domain="sync", stages=2):
        """ Creates a synchronized copy of this interface's I/O. """

        # Synchronize our inputs...
        m.submodules += [
            FFSynchronizer(self.sck,
                           output.sck,
                           o_domain=o_domain,
                           stages=stages),
            FFSynchronizer(self.sdi,
                           output.sdi,
                           o_domain=o_domain,
                           stages=stages),
            FFSynchronizer(self.cs,
                           output.cs,
                           o_domain=o_domain,
                           stages=stages),
        ]

        # ... and connect our output directly through.
        m.d.comb += self.sdo.eq(output.sdo)
Exemple #4
0
 def elaborate(self, platform):
     m = Module()
     pads = self._pads
     m.d.comb += [
         pads.sbwtck_t.oe.eq(1),
         pads.sbwtck_t.o.eq(self.sbwtck),
         pads.sbwtdio_t.oe.eq(~self.sbwtd_z),
         pads.sbwtdio_t.o.eq(self.sbwtd_o),
     ]
     m.submodules += [
         FFSynchronizer(pads.sbwtdio_t.i, self.sbwtd_i),
     ]
     return m
 def elaborate(self, platform):
     m = Module()
     m.d.comb += [
         self.pads.clk_t.oe.eq(1),
         self.pads.clk_t.o.eq(self.clk),
     ]
     m.submodules += [
         FFSynchronizer(self.pads.din_t.i, self.din),
     ]
     if hasattr(self.pads, "osc_t"):
         m.d.comb += [
             self.pads.osc_t.oe.eq(1),
             self.pads.osc_t.o.eq(self.osc),
         ]
     return m
    def elaborate(self, platform):
        m = Module()
        m.submodules += self.analyzer

        pins_i = Signal.like(self.pads.i_t.i)
        pins_r = Signal.like(self.pads.i_t.i)
        m.submodules += FFSynchronizer(self.pads.i_t.i, pins_i)

        m.d.sync += pins_r.eq(pins_i)
        m.d.comb += [
            self.event_source.data.eq(pins_i),
            self.event_source.trigger.eq(pins_i != pins_r)
        ]

        return m
Exemple #7
0
    def elaborate(self, platform):
        m = Module()

        m.d.comb += [
            self.pads.clock_t.o.eq(0),
            self.pads.clock_t.oe.eq(~self.clock_o),
            self.pads.data_t.o.eq(0),
            self.pads.data_t.oe.eq(~self.data_o),
        ]
        m.submodules += [
            FFSynchronizer(self.pads.clock_t.i, self.clock_i, reset=1),
            FFSynchronizer(self.pads.data_t.i, self.data_i, reset=1),
        ]

        clock_s = Signal(reset=1)
        clock_r = Signal(reset=1)
        m.d.sync += [
            clock_s.eq(self.clock_i),
            clock_r.eq(clock_s),
            self.falling.eq(clock_r & ~clock_s),
            self.rising.eq(~clock_r & clock_s),
        ]

        return m
Exemple #8
0
    def elaborate(self, platform) -> Module:
        """assemble the module"""
        m = Module()

        comb = m.d.comb
        sync = m.d.sync

        nrzi      = Signal()
        nrzi_prev = Signal()
        got_edge  = Signal()

        m.submodules.cdc = FFSynchronizer(self.nrzi_in, nrzi)
        sync += nrzi_prev.eq(nrzi)
        comb += got_edge.eq(nrzi_prev ^ nrzi)

        # we are looking for 10 non changing bits
        # and those will be ~900ns long @48kHz
        # and if we clock at not more than 100MHz
        # the counter will run up to 900ns/10ns = 90
        # so 7 bits will suffice for the counter
        sync_counter = DividingCounter(divisor=12, width=7)
        m.submodules.sync_counter = sync_counter
        bit_time = sync_counter.divided_counter_out

        with m.FSM():
            with m.State("SYNC"):
                comb += self.running.eq(0)
                sync += [
                    self.data_out.eq(0),
                    self.data_out_en.eq(0),
                    sync_counter.reset_in.eq(0)
                ]
                self.find_bit_timings(m, sync_counter, got_edge)

            with m.State("DECODE"):
                comb += self.running.eq(1)
                self.decode_nrzi(m, bit_time, got_edge, sync_counter)

        return m
Exemple #9
0
    def elaborate(self, platform):
        m = Module()

        # The ECP5 SerDes uses a simple feedback mechanism to keep its FIFO clocks in sync
        # with the FPGA's fabric. Accordingly, we'll need to capture the output clocks and then
        # pass them back to the SerDes; this allows the placer to handle clocking correctly, allows us
        # to attach clock constraints for analysis, and allows us to use these clocks for -very- simple tasks.
        txoutclk = Signal()
        rxoutclk = Signal()


        # Internal state.
        rx_los     = Signal()
        rx_lol     = Signal()
        rx_lsm     = Signal()
        rx_align   = Signal()
        rx_bus     = Signal(24)

        tx_lol     = Signal()
        tx_bus     = Signal(24)


        #
        # Clock domain crossing.
        #
        tx_produce_square_wave = Signal()
        tx_produce_pattern     = Signal()
        tx_pattern             = Signal(20)

        m.submodules += [
            # Transmit control  synchronization.
            FFSynchronizer(self.tx_produce_square_wave, tx_produce_square_wave, o_domain="tx"),
            FFSynchronizer(self.tx_produce_pattern, tx_produce_pattern, o_domain="tx"),
            FFSynchronizer(self.tx_pattern, tx_pattern, o_domain="tx"),

            # Receive control synchronization.
            FFSynchronizer(self.rx_align, rx_align, o_domain="rx"),
            FFSynchronizer(rx_los, self.rx_idle, o_domain="sync"),
        ]

        #
        # Clocking / reset control.
        #

        # The SerDes needs to be brought up gradually; we'll do that here.
        m.submodules.reset_sequencer = reset = ECP5ResetSequencer()
        m.d.comb += [
            reset.tx_pll_locked  .eq(~tx_lol),
            reset.rx_pll_locked  .eq(~rx_lol)
        ]


        # Create a local transmit domain, for our transmit-side hardware.
        m.domains.tx = ClockDomain()
        m.d.comb    += ClockSignal("tx").eq(txoutclk)
        m.submodules += [
            ResetSynchronizer(ResetSignal("sync"), domain="tx"),
            FFSynchronizer(~ResetSignal("tx"), self.tx_ready)
        ]

        # Create the same setup, buf for the receive side.
        m.domains.rx = ClockDomain()
        m.d.comb    += ClockSignal("rx").eq(rxoutclk)
        m.submodules += [
            ResetSynchronizer(ResetSignal("sync"), domain="rx"),
            FFSynchronizer(~ResetSignal("rx"), self.rx_ready)
        ]

        #
        # Core SerDes instantiation.
        #
        serdes_params = dict(
            # DCU — power management
            p_D_MACROPDB            = "0b1",
            p_D_IB_PWDNB            = "0b1",    # undocumented (required for RX)
            p_D_TXPLL_PWDNB         = "0b1",
            i_D_FFC_MACROPDB        = 1,

            # DCU — reset
            i_D_FFC_MACRO_RST       = ResetSignal("sync"),
            i_D_FFC_DUAL_RST        = ResetSignal("sync"),

            # DCU — clocking
            i_D_REFCLKI             = self._pll.refclk,
            o_D_FFS_PLOL            = tx_lol,
            p_D_REFCK_MODE          = {
                25: "0b100",
                20: "0b000",
                16: "0b010",
                10: "0b001",
                 8: "0b011"}[self._pll.config["mult"]],
            p_D_TX_MAX_RATE         = "5.0",    # 5.0 Gbps
            p_D_TX_VCO_CK_DIV       = {
                32: "0b111",
                16: "0b110",
                 8: "0b101",
                 4: "0b100",
                 2: "0b010",
                 1: "0b000"}[1],                # DIV/1
            p_D_BITCLK_LOCAL_EN     = "0b1",    # Use clock from local PLL


            # Clock multiplier unit configuration
            p_D_CMUSETBIASI         = "0b00",   # begin undocumented (10BSER sample code used)
            p_D_CMUSETI4CPP         = "0d3",
            p_D_CMUSETI4CPZ         = "0d3",
            p_D_CMUSETI4VCO         = "0b00",
            p_D_CMUSETICP4P         = "0b01",
            p_D_CMUSETICP4Z         = "0b101",
            p_D_CMUSETINITVCT       = "0b00",
            p_D_CMUSETISCL4VCO      = "0b000",
            p_D_CMUSETP1GM          = "0b000",
            p_D_CMUSETP2AGM         = "0b000",
            p_D_CMUSETZGM           = "0b000",

            p_D_SETIRPOLY_AUX       = "0b01",
            p_D_SETICONST_AUX       = "0b01",
            p_D_SETIRPOLY_CH        = "0b01",
            p_D_SETICONST_CH        = "0b10",
            p_D_SETPLLRC            = "0d1",
            p_D_RG_EN               = "0b0",
            p_D_RG_SET              = "0b00",
            p_D_REQ_ISET            = "0b011",
            p_D_PD_ISET             = "0b11",   # end undocumented

            # DCU — FIFOs
            p_D_LOW_MARK            = "0d4",    # Clock compensation FIFO low  water mark (mean=8)
            p_D_HIGH_MARK           = "0d12",   # Clock compensation FIFO high water mark (mean=8)

            # CHX common ---------------------------------------------------------------------------
            # CHX — protocol
            p_CHX_PROTOCOL          = "10BSER",
            p_CHX_UC_MODE           = "0b1",

            p_CHX_ENC_BYPASS        = "******",    # Use the 8b10b encoder
            p_CHX_DEC_BYPASS        = "******",    # Use the 8b10b decoder

            # CHX receive --------------------------------------------------------------------------
            # CHX RX ­— power management
            p_CHX_RPWDNB            = "0b1",
            i_CHX_FFC_RXPWDNB       = 1,

            # CHX RX ­— reset
            i_CHX_FFC_RRST          = ~self.rx_enable | reset.serdes_rx_reset,
            i_CHX_FFC_LANE_RX_RST   = ~self.rx_enable | reset.pcs_reset,

            # CHX RX ­— input
            i_CHX_HDINP             = self._rx_pads.p,
            i_CHX_HDINN             = self._rx_pads.n,

            p_CHX_REQ_EN            = "0b1",    # Enable equalizer
            p_CHX_REQ_LVL_SET       = "0b01",
            p_CHX_RX_RATE_SEL       = "0d09",   # Equalizer  pole position
            p_CHX_RTERM_RX          = {
                "5k-ohms":        "0b00000",
                "80-ohms":        "0b00001",
                "75-ohms":        "0b00100",
                "70-ohms":        "0b00110",
                "60-ohms":        "0b01011",
                "50-ohms":        "0b10011",
                "46-ohms":        "0b11001",
                "wizard-50-ohms": "0d22"}["wizard-50-ohms"],
            p_CHX_RXIN_CM           = "0b11",   # CMFB (wizard value used)
            p_CHX_RXTERM_CM         = "0b10",   # RX Input (wizard value used)

            # CHX RX ­— clocking
            i_CHX_RX_REFCLK         = self._pll.refclk,
            o_CHX_FF_RX_PCLK        = rxoutclk,
            i_CHX_FF_RXI_CLK        = ClockSignal("rx"),

            p_CHX_CDR_MAX_RATE      = "5.0",    # 5.0 Gbps
            p_CHX_RX_DCO_CK_DIV     = {
                32: "0b111",
                16: "0b110",
                 8: "0b101",
                 4: "0b100",
                 2: "0b010",
                 1: "0b000"}[1],                # DIV/1
            p_CHX_RX_GEAR_MODE      = "0b1",    # 1:2 gearbox
            p_CHX_FF_RX_H_CLK_EN    = "0b1",    # enable  DIV/2 output clock
            p_CHX_FF_RX_F_CLK_DIS   = "0b1",    # disable DIV/1 output clock
            p_CHX_SEL_SD_RX_CLK     = "0b1",    # FIFO driven by recovered clock

            p_CHX_AUTO_FACQ_EN      = "0b1",    # undocumented (wizard value used)
            p_CHX_AUTO_CALIB_EN     = "0b1",    # undocumented (wizard value used)
            p_CHX_PDEN_SEL          = "0b0",    # phase detector disabled on LOS

            p_CHX_DCOATDCFG         = "0b00",   # begin undocumented (sample code used)
            p_CHX_DCOATDDLY         = "0b00",
            p_CHX_DCOBYPSATD        = "0b1",
            p_CHX_DCOCALDIV         = "0b000",
            p_CHX_DCOCTLGI          = "0b011",
            p_CHX_DCODISBDAVOID     = "0b0",
            p_CHX_DCOFLTDAC         = "0b00",
            p_CHX_DCOFTNRG          = "0b001",
            p_CHX_DCOIOSTUNE        = "0b010",
            p_CHX_DCOITUNE          = "0b00",
            p_CHX_DCOITUNE4LSB      = "0b010",
            p_CHX_DCOIUPDNX2        = "0b1",
            p_CHX_DCONUOFLSB        = "0b100",
            p_CHX_DCOSCALEI         = "0b01",
            p_CHX_DCOSTARTVAL       = "0b010",
            p_CHX_DCOSTEP           = "0b11",   # end undocumented

            # CHX RX — loss of signal
            o_CHX_FFS_RLOS          = rx_los,
            p_CHX_RLOS_SEL          = "0b1",
            p_CHX_RX_LOS_EN         = "0b0",
            p_CHX_RX_LOS_LVL        = "0b101",  # Lattice "TBD" (wizard value used)
            p_CHX_RX_LOS_CEQ        = "0b11",   # Lattice "TBD" (wizard value used)
            p_CHX_RX_LOS_HYST_EN    = "0b1",

            # CHX RX — loss of lock
            o_CHX_FFS_RLOL          = rx_lol,

            # CHX RX — link state machine
            # Note that Lattice Diamond needs these in their provided bases (and string lengths!).
            # Changing their bases will work with the open toolchain, but will make Diamond mad.
            i_CHX_FFC_SIGNAL_DETECT = rx_align,

            o_CHX_FFS_LS_SYNC_STATUS= rx_lsm,

            p_CHX_ENABLE_CG_ALIGN   = "0b1",

            p_CHX_UDF_COMMA_MASK    = "0x0ff",  # compare the 8 lsbs
            p_CHX_UDF_COMMA_A       = "0x003",   # "0b0000000011", # K28.1, K28.5 and K28.7
            p_CHX_UDF_COMMA_B       = "0x07c",   # "0b0001111100", # K28.1, K28.5 and K28.7


            p_CHX_CTC_BYPASS        = "******",    # bypass CTC FIFO
            p_CHX_MIN_IPG_CNT       = "0b11",   # minimum interpacket gap of 4
            p_CHX_MATCH_2_ENABLE    = "0b0",    # 2 character skip matching
            p_CHX_MATCH_4_ENABLE    = "0b0",    # 4 character skip matching
            p_CHX_CC_MATCH_1        = "0x000",
            p_CHX_CC_MATCH_2        = "0x000",
            p_CHX_CC_MATCH_3        = "0x000",
            p_CHX_CC_MATCH_4        = "0x000",

            # CHX RX — data
            **{"o_CHX_FF_RX_D_%d" % n: rx_bus[n] for n in range(len(rx_bus))},

            # CHX transmit -------------------------------------------------------------------------
            # CHX TX — power management
            p_CHX_TPWDNB            = "0b1",
            i_CHX_FFC_TXPWDNB       = 1,

            # CHX TX ­— reset
            i_D_FFC_TRST            = ~self.tx_enable | reset.serdes_tx_reset,
            i_CHX_FFC_LANE_TX_RST   = ~self.tx_enable | reset.pcs_reset,

            # CHX TX ­— output
            o_CHX_HDOUTP            = self._tx_pads.p,
            o_CHX_HDOUTN            = self._tx_pads.n,

            p_CHX_TXAMPLITUDE       = "0d1000",  # 1000 mV
            p_CHX_RTERM_TX          = {
                "5k-ohms":        "0b00000",
                "80-ohms":        "0b00001",
                "75-ohms":        "0b00100",
                "70-ohms":        "0b00110",
                "60-ohms":        "0b01011",
                "50-ohms":        "0b10011",
                "46-ohms":        "0b11001",
                "wizard-50-ohms": "0d19"}["50-ohms"],

            p_CHX_TDRV_SLICE0_CUR   = "0b011",  # 400 uA
            p_CHX_TDRV_SLICE0_SEL   = "0b01",   # main data
            p_CHX_TDRV_SLICE1_CUR   = "0b000",  # 100 uA
            p_CHX_TDRV_SLICE1_SEL   = "0b00",   # power down
            p_CHX_TDRV_SLICE2_CUR   = "0b11",   # 3200 uA
            p_CHX_TDRV_SLICE2_SEL   = "0b01",   # main data
            p_CHX_TDRV_SLICE3_CUR   = "0b10",   # 2400 uA
            p_CHX_TDRV_SLICE3_SEL   = "0b01",   # main data
            p_CHX_TDRV_SLICE4_CUR   = "0b00",   # 800 uA
            p_CHX_TDRV_SLICE4_SEL   = "0b00",   # power down
            p_CHX_TDRV_SLICE5_CUR   = "0b00",   # 800 uA
            p_CHX_TDRV_SLICE5_SEL   = "0b00",   # power down

            # CHX TX ­— clocking
            o_CHX_FF_TX_PCLK        = txoutclk,
            i_CHX_FF_TXI_CLK        = ClockSignal("tx"),

            p_CHX_TX_GEAR_MODE      = "0b1",    # 1:2 gearbox
            p_CHX_FF_TX_H_CLK_EN    = "0b1",    # enable  DIV/2 output clock
            p_CHX_FF_TX_F_CLK_DIS   = "0b1",    # disable DIV/1 output clock

            # CHX TX — data
            **{"i_CHX_FF_TX_D_%d" % n: tx_bus[n] for n in range(len(tx_bus))},

            # SCI interface.
            #**{"i_D_SCIWDATA%d" % n: sci.sci_wdata[n] for n in range(8)},
            #**{"i_D_SCIADDR%d"   % n: sci.sci_addr[n] for n in range(6)},
            #**{"o_D_SCIRDATA%d" % n: sci.sci_rdata[n] for n in range(8)},
            #i_D_SCIENAUX  = sci.dual_sel,
            #i_D_SCISELAUX = sci.dual_sel,
            #i_CHX_SCIEN   = sci.chan_sel,
            #i_CHX_SCISEL  = sci.chan_sel,
            #i_D_SCIRD     = sci.sci_rd,
            #i_D_SCIWSTN   = sci.sci_wrn,

            # Out-of-band signaling Rx support.
            p_CHX_LDR_RX2CORE_SEL     = "0b1",            # Enables low-speed out-of-band input.
            o_CHX_LDR_RX2CORE         = self.rx_gpio,

            # Out-of-band signaling Tx support.
            p_CHX_LDR_CORE2TX_SEL     = "0b0",            # Uses CORE2TX_EN to enable out-of-band output.
            i_CHX_LDR_CORE2TX         = self.tx_gpio,
            i_CHX_FFC_LDR_CORE2TX_EN  = self.tx_gpio_en
        )

        # Translate the 'CHX' string to the correct channel name in each of our SerDes parameters,
        # and create our SerDes instance.
        serdes_params = {k.replace("CHX", f"CH{self._channel}"):v for (k,v) in serdes_params.items()}
        m.submodules.serdes = serdes = Instance("DCUA", **serdes_params)

        # Bind our SerDes to the correct location inside the FPGA.
        serdes.attrs["LOC"] = "DCU{}".format(self._dual)
        serdes.attrs["CHAN"] = "CH{}".format(self._channel)
        serdes.attrs["BEL"] = "X42/Y71/DCU"

        #
        # TX and RX datapaths (SerDes <-> stream conversion)
        #
        sink   = self.sink
        source = self.source

        m.d.comb += [
            # Grab our received data directly from our SerDes; modifying things to match the
            # SerDes Rx bus layout, which squishes status signals between our two geared words.
            source.data[0: 8]  .eq(rx_bus[ 0: 8]),
            source.data[8:16]  .eq(rx_bus[12:20]),
            source.ctrl[0]     .eq(rx_bus[8]),
            source.ctrl[1]     .eq(rx_bus[20]),
            source.valid       .eq(1),

            # Stick the data we'd like to transmit into the SerDes; again modifying things to match
            # the transmit bus layout.
            tx_bus[ 0: 8]      .eq(sink.data[0: 8]),
            tx_bus[12:20]      .eq(sink.data[8:16]),
            tx_bus[8]          .eq(sink.ctrl[0]),
            tx_bus[20]         .eq(sink.ctrl[1]),
            sink.ready         .eq(1)
        ]


        return m
Exemple #10
0
    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        # Do TX CDC
        # FFSynchronizer
        if False:
            m.submodules += FFSynchronizer(Cat(self.tx_symbol,
                                               self.tx_set_disp, self.tx_disp,
                                               self.tx_e_idle),
                                           Cat(self.__lane.tx_symbol,
                                               self.__lane.tx_set_disp,
                                               self.__lane.tx_disp,
                                               self.__lane.tx_e_idle),
                                           o_domain="tx",
                                           stages=4)

        # No CDC
        # TODO: Check if this actually works
        if False:
            m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp,
                            self.__lane.tx_disp, self.__lane.tx_e_idle).eq(
                                Cat(self.tx_symbol, self.tx_set_disp,
                                    self.tx_disp, self.tx_e_idle))

        # AsyncFIFOBuffered
        if True:
            tx_fifo = m.submodules.tx_fifo = AsyncFIFOBuffered(
                width=self.ratio * 12, depth=8, r_domain="tx", w_domain="rx")
            m.d.comb += tx_fifo.w_data.eq(
                Cat(self.tx_symbol, self.tx_set_disp, self.tx_disp,
                    self.tx_e_idle))
            m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp,
                            self.__lane.tx_disp,
                            self.__lane.tx_e_idle).eq(tx_fifo.r_data)
            m.d.comb += tx_fifo.r_en.eq(1)
            m.d.comb += tx_fifo.w_en.eq(1)

        # Testing symbols
        if False:
            m.d.comb += self.__lane.tx_symbol.eq(Cat(Ctrl.COM, D(10, 2)))

        self.slip = SymbolSlip(symbol_size=10,
                               word_size=self.__lane.ratio,
                               comma=Cat(Ctrl.COM, 1))
        m.submodules += self.slip

        m.d.comb += [
            self.slip.en.eq(self.rx_align),
            self.slip.i.eq(
                Cat((self.__lane.rx_symbol.word_select(n, 9),
                     self.__lane.rx_valid[n])
                    for n in range(self.__lane.ratio))),
            self.rx_symbol.eq(
                Cat(
                    Part(self.slip.o, 10 * n, 9)
                    for n in range(self.__lane.ratio))),
            self.rx_valid.eq(
                Cat(self.slip.o[10 * n + 9]
                    for n in range(self.__lane.ratio))),
        ]
        return m
Exemple #11
0
    def elaborate(self, platform):
        m = Module()
        sync = m.d.sync
        comb = m.d.comb

        self.BAR = Signal()
        sync += self.BAR.eq(~self.BAR)

        self.tms = tms = Signal.like(self.port.tms)
        self.tck = tck = Signal.like(self.port.tck)
        self.tdi = tdi = Signal.like(self.port.tdi)
        self.tdo = tdo = Signal.like(self.port.tdo)

        m.submodules += [
            FFSynchronizer(self.port.tms, tms),
            FFSynchronizer(self.port.tck, tck),
            FFSynchronizer(self.port.tdi, tdi),
        ]

        prev_tck = Signal()
        self.rising_tck = rising_tck = Signal()
        self.falling_tck = falling_tck = Signal()

        sync += prev_tck.eq(tck)

        comb += [
            rising_tck.eq((~prev_tck) & tck),
            falling_tck.eq(prev_tck & (~tck)),
        ]

        self.ir = Signal(JtagIR)
        assert self.ir.width == 5  # Spike
        self.dr = Signal(max([len(v) for _, v in self.regs.items()]))

        self.DATA_WRITE = Signal(debug_module_register_len(JtagIR.DMI))
        self.DATA_READ = Signal(debug_module_register_len(JtagIR.DMI))
        self.DMI_WRITE = Signal(32)

        # TODO
        for ir, record in self.regs.items():
            sync += record.update.eq(0)
            sync += record.capture.eq(0)

        with m.FSM() as jtag_fsm:
            with m.State("TEST-LOGIC-RESET"):
                with m.If(rising_tck & ~tms):
                    sync += self.ir.eq(self.ir_reset)
                    m.next = "RUN-TEST-IDLE"

            with m.State("RUN-TEST-IDLE"):
                with m.If(rising_tck & tms):
                    m.next = "SELECT-DR-SCAN"

            with m.State("SELECT-DR-SCAN"):
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "SELECT-IR-SCAN"  # IR path
                    with m.Else():
                        m.next = "CAPTURE-DR"  # DR path

# DR path
            with m.State("CAPTURE-DR"):
                with m.Switch(self.ir):
                    for ir, record in self.regs.items():
                        with m.Case(ir):
                            sync += self.DATA_READ.eq(record.r)
                            sync += self.dr.eq(record.r)
                            sync += record.capture.eq(rising_tck)
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "EXIT1-DR"
                    with m.Else():
                        m.next = "SHIFT-DR"

            with m.State("SHIFT-DR"):
                with m.If(falling_tck):
                    sync += self.port.tdo.eq(self.dr[0])
                with m.Switch(self.ir):
                    for ir, record in self.regs.items():
                        with m.Case(ir):
                            with m.If(rising_tck):
                                sync += self.dr.eq(
                                    Cat(self.dr[1:len(record.r)], tdi))
                # below is not enough, as it may effect in garbage
                # sync += self.dr.eq(Cat(self.dr[1:], tdi))
                with m.If(rising_tck & tms):
                    m.next = "EXIT1-DR"

            with m.State("EXIT1-DR"):
                sync += self.port.tdo.eq(0)  # TODO
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "UPDATE-DR"
                    with m.Else():
                        m.next = "PAUSE-DR"

            with m.State("PAUSE-DR"):
                with m.If(rising_tck & tms):
                    m.next = "EXIT2-DR"

            with m.State("EXIT2-DR"):
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "UPDATE-DR"
                    with m.Else():
                        m.next = "SHIFT-DR"

            with m.State("UPDATE-DR"):
                with m.Switch(self.ir):
                    for ir, record in self.regs.items():
                        with m.Case(ir):
                            sync += self.DATA_WRITE.eq(self.dr)
                            with m.If(ir == JtagIR.DMI):
                                sync += self.DMI_WRITE.eq(self.dr[2:34])
                            sync += record.w.eq(self.dr)
                            sync += record.update.eq(falling_tck)
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "SELECT-DR-SCAN"
                    with m.Else():
                        m.next = "RUN-TEST-IDLE"

# IR path
            with m.State("SELECT-IR-SCAN"):
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "TEST-LOGIC-RESET"
                    with m.Else():
                        m.next = "CAPTURE-IR"

            with m.State("CAPTURE-IR"):
                sync += self.ir.eq(JtagIR.IDCODE)
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "EXIT1-IR"
                    with m.Else():
                        m.next = "SHIFT-IR"

            with m.State("SHIFT-IR"):
                with m.If(falling_tck):
                    sync += self.port.tdo.eq(self.ir[0])
                with m.If(rising_tck):
                    sync += self.ir.eq(Cat(self.ir[1:], tdi))
                comb += tdo.eq(self.ir[0])
                with m.If(rising_tck & tms):
                    m.next = "EXIT1-IR"

            with m.State("EXIT1-IR"):
                sync += self.port.tdo.eq(0)  # TODO
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "UPDATE-IR"
                    with m.Else():
                        m.next = "PAUSE-IR"

            with m.State("PAUSE-IR"):
                with m.If(rising_tck & tms):
                    m.next = "EXIT2-IR"

            with m.State("EXIT2-IR"):
                with m.If(rising_tck & tms):
                    m.next = "UPDATE-IR"

            with m.State("UPDATE-IR"):
                with m.If(rising_tck):
                    with m.If(tms):
                        m.next = "SELECT-IR-SCAN"
                    with m.Else():
                        m.next = "RUN-TEST-IDLE"

        return m
Exemple #12
0
    def elaborate(self, platform):
        m = Module()

        pads = self._pads

        if hasattr(pads, "ce_t"):
            m.d.comb += [
                pads.ce_t.oe.eq(1),
                pads.ce_t.o.eq(~self.ce),
            ]
        if hasattr(pads, "oe_t"):
            m.d.comb += [
                pads.oe_t.oe.eq(1),
                pads.oe_t.o.eq(~self.oe),
            ]
        if hasattr(pads, "we_t"):
            m.d.comb += [
                pads.we_t.oe.eq(1),
                pads.we_t.o.eq(~self.we),
            ]

        m.d.comb += [
            pads.dq_t.oe.eq(~self.oe),
            pads.dq_t.o.eq(self.d),
        ]
        m.submodules += FFSynchronizer(pads.dq_t.i, self.q)

        m.d.comb += [
            pads.a_t.oe.eq(1),
            pads.a_t.o.eq(self.a),  # directly drive low bits
        ]

        if hasattr(pads, "a_clk_t") and hasattr(pads, "a_si_t"):
            a_clk = Signal(reset=1)
            a_si = Signal()
            a_lat = Signal(reset=0) if hasattr(pads, "a_lat_t") else None
            m.d.comb += [
                pads.a_clk_t.oe.eq(1),
                pads.a_clk_t.o.eq(a_clk),
                pads.a_si_t.oe.eq(1),
                pads.a_si_t.o.eq(a_si),
            ]
            if a_lat is not None:
                m.d.comb += [pads.a_lat_t.oe.eq(1), pads.a_lat_t.o.eq(a_lat)]

            # "sa" is the sliced|shifted address, referring to the top-most bits
            sa_input = self.a[len(pads.a_t.o):]
            # This represents a buffer of those high address bits,
            # not to be confused with the latch pin.
            sa_latch = Signal(self.a_bits - len(pads.a_t.o))

            sh_cyc = math.ceil(platform.default_clk_frequency / self._sh_freq)
            timer = Signal(range(sh_cyc), reset=sh_cyc - 1)
            count = Signal(range(len(sa_latch) + 1))
            first = Signal(reset=1)

            with m.FSM():
                with m.State("READY"):
                    m.d.sync += first.eq(0)
                    with m.If((sa_latch == sa_input) & ~first):
                        m.d.comb += self.rdy.eq(1)
                    with m.Else():
                        m.d.sync += count.eq(len(sa_latch))
                        m.d.sync += sa_latch.eq(sa_input)
                        m.next = "SHIFT"

                with m.State("SHIFT"):
                    with m.If(timer == 0):
                        m.d.sync += timer.eq(timer.reset)
                        m.d.sync += a_clk.eq(~a_clk)
                        with m.If(a_clk):
                            m.d.sync += a_si.eq(sa_latch[-1])
                            m.d.sync += count.eq(count - 1)
                        with m.Else():
                            m.d.sync += sa_latch.eq(sa_latch.rotate_left(1))
                            with m.If(count == 0):
                                if a_lat is None:
                                    m.next = "READY"
                                else:
                                    m.next = "LATCH-1"
                    with m.Else():
                        m.d.sync += timer.eq(timer - 1)

                if a_lat is not None:
                    with m.State("LATCH-1"):
                        m.d.sync += a_lat.eq(1)
                        with m.If(timer == 0):
                            m.d.sync += timer.eq(timer.reset)
                            m.next = "LATCH-2"
                        with m.Else():
                            m.d.sync += timer.eq(timer - 1)

                    with m.State("LATCH-2"):
                        with m.If(timer == 0):
                            m.d.sync += timer.eq(timer.reset)
                            m.d.sync += a_lat.eq(0)
                            m.next = "READY"
                        with m.Else():
                            m.d.sync += timer.eq(timer - 1)

        else:
            m.d.comb += self.rdy.eq(1)

        return m
Exemple #13
0
    def elaborate(self, platform):
        m = Module()

        sync_pulse = Signal(8)

        da_reset_shifter = Signal()
        da_reset_bitstuff = Signal(
        )  # Need to reset the bit stuffer 1 cycle after the shifter.
        stall = Signal()

        # These signals are set during the sync pulse
        sp_reset_bitstuff = Signal()
        sp_reset_shifter = Signal()
        sp_bit = Signal()
        sp_o_data_strobe = Signal()

        # 12MHz domain
        bitstuff_valid_data = Signal()

        # Keep a Gray counter around to smoothly transition between states
        state_gray = Signal(2)
        state_data = Signal()
        state_sync = Signal()

        #
        # Transmit gearing.
        #
        m.submodules.shifter = shifter = TxShifter(width=8)
        m.d.comb += [
            shifter.i_data.eq(self.i_data_payload),
            shifter.i_enable.eq(~stall),
            shifter.i_clear.eq(da_reset_shifter | sp_reset_shifter)
        ]

        #
        # Bit-stuffing and NRZI.
        #
        bitstuff = ResetInserter(da_reset_bitstuff)(TxBitstuffer())
        m.submodules.bitstuff = bitstuff

        m.submodules.nrzi = nrzi = TxNRZIEncoder()

        #
        # Transmit controller.
        #

        m.d.comb += [
            # Send a data strobe when we're two bits from the end of the sync pulse.
            # This is because the pipeline takes two bit times, and we want to ensure the pipeline
            # has spooled up enough by the time we're there.
            bitstuff.i_data.eq(shifter.o_data),
            stall.eq(bitstuff.o_stall),
            sp_bit.eq(sync_pulse[0]),
            sp_reset_bitstuff.eq(sync_pulse[0]),

            # The shifter has one clock cycle of latency, so reset it
            # one cycle before the end of the sync byte.
            sp_reset_shifter.eq(sync_pulse[1]),
            sp_o_data_strobe.eq(sync_pulse[5]),
            state_data.eq(state_gray[0] & state_gray[1]),
            state_sync.eq(state_gray[0] & ~state_gray[1]),
            self.fit_oe.eq(state_data | state_sync),
            self.fit_dat.eq((state_data & shifter.o_data & ~bitstuff.o_stall)
                            | sp_bit),
            self.o_data_strobe.eq(state_data & shifter.o_get & ~stall
                                  & self.i_oe),
        ]

        # If we reset the shifter, then o_empty will go high on the next cycle.
        #

        m.d.usb += [
            # If the shifter runs out of data, percolate the "reset" signal to the
            # shifter, and then down to the bitstuffer.
            # da_reset_shifter.eq(~stall & shifter.o_empty & ~da_stalled_reset),
            # da_stalled_reset.eq(da_reset_shifter),
            # da_reset_bitstuff.eq(~stall & da_reset_shifter),
            bitstuff_valid_data.eq(~stall & shifter.o_get & self.i_oe),
        ]

        with m.FSM(domain="usb"):

            with m.State('IDLE'):
                with m.If(self.i_oe):
                    m.d.usb += [sync_pulse.eq(1 << 7), state_gray.eq(0b01)]
                    m.next = "SEND_SYNC"
                with m.Else():
                    m.d.usb += state_gray.eq(0b00)

            with m.State('SEND_SYNC'):
                m.d.usb += sync_pulse.eq(sync_pulse >> 1)

                with m.If(sync_pulse[0]):
                    m.d.usb += state_gray.eq(0b11)
                    m.next = "SEND_DATA"
                with m.Else():
                    m.d.usb += state_gray.eq(0b01)

            with m.State('SEND_DATA'):
                with m.If(~self.i_oe & shifter.o_empty & ~bitstuff.o_stall):
                    with m.If(bitstuff.o_will_stall):
                        m.next = 'STUFF_LAST_BIT'
                    with m.Else():
                        m.d.usb += state_gray.eq(0b10)
                        m.next = 'IDLE'

                with m.Else():
                    m.d.usb += state_gray.eq(0b11)

            with m.State('STUFF_LAST_BIT'):
                m.d.usb += state_gray.eq(0b10)
                m.next = 'IDLE'

        # 48MHz domain
        # NRZI encoding
        nrzi_dat = Signal()
        nrzi_oe = Signal()

        # Cross the data from the 12MHz domain to the 48MHz domain
        cdc_dat = FFSynchronizer(self.fit_dat,
                                 nrzi_dat,
                                 o_domain="usb_io",
                                 stages=3)
        cdc_oe = FFSynchronizer(self.fit_oe,
                                nrzi_oe,
                                o_domain="usb_io",
                                stages=3)
        m.submodules += [cdc_dat, cdc_oe]

        m.d.comb += [
            nrzi.i_valid.eq(self.i_bit_strobe),
            nrzi.i_data.eq(nrzi_dat),
            nrzi.i_oe.eq(nrzi_oe),
            self.o_usbp.eq(nrzi.o_usbp),
            self.o_usbn.eq(nrzi.o_usbn),
            self.o_oe.eq(nrzi.o_oe),
        ]

        return m
Exemple #14
0
from amaranth import *
from amaranth.lib.cdc import FFSynchronizer
from amaranth.cli import main

i, o = Signal(name="i"), Signal(name="o")
m = Module()
m.submodules += FFSynchronizer(i, o)

if __name__ == "__main__":
    main(m, ports=[i, o])
Exemple #15
0
    def elaborate(self, platform):
        m = Module()

        #
        # Reference clock selection.
        #

        # If we seem to have a raw pin record, we'll assume we're being passed the external REFCLK.
        # We'll instantiate an instance that captures the reference clock signal.
        if hasattr(self._refclk, 'p'):
            refclk = Signal()
            m.submodules.refclk_input = refclk_in = Instance("EXTREFB",
                i_REFCLKP     = self._refclk.p,
                i_REFCLKN     = self._refclk.n,
                o_REFCLKO     = refclk,
                p_REFCK_PWDNB = "0b1",
                p_REFCK_RTERM = "0b1", # 100 Ohm
            )
            refclk_in.attrs["LOC"] =  f"EXTREF{self._refclk_num}"

        # Otherwise, we'll accept the reference clock directly.
        else:
            refclk = self._refclk

        #
        # Raw serdes.
        #
        pll_config = ECP5SerDesPLLConfiguration(refclk, refclk_freq=self._refclk_frequency, linerate=5e9)
        serdes  = ECP5SerDes(
            pll_config  = pll_config,
            tx_pads     = self._tx_pads,
            rx_pads     = self._rx_pads,
            channel     = self._channel,
        )
        m.submodules.serdes = serdes
        m.d.comb += [
            serdes.train_equalizer  .eq(self.train_equalizer),
            self.ready              .eq(serdes.tx_ready & serdes.rx_ready)
        ]


        #
        # Transmit datapath.
        #
        m.submodules.tx_datapath = tx_datapath = TransmitPreprocessing()
        m.d.comb += [
            serdes.tx_idle             .eq(self.tx_idle),
            serdes.tx_enable           .eq(self.enable),

            tx_datapath.sink           .stream_eq(self.sink),
            serdes.sink                .stream_eq(tx_datapath.source),

            serdes.tx_gpio_en          .eq(self.use_tx_as_gpio),
            serdes.tx_gpio             .eq(self.tx_gpio)
        ]


        #
        # Receive datapath.
        #
        m.submodules.rx_datapath = rx_datapath = ReceivePostprocessing()
        m.d.comb += [
            self.rx_idle            .eq(serdes.rx_idle),

            serdes.rx_enable        .eq(self.enable),
            serdes.rx_align         .eq(self.rx_align),
            rx_datapath.align       .eq(self.rx_align),

            rx_datapath.sink        .stream_eq(serdes.source),
            self.source             .stream_eq(rx_datapath.source)
        ]

        # Pass through a synchronized version of our SerDes' rx-gpio.
        m.submodules += FFSynchronizer(serdes.rx_gpio, self.rx_gpio, o_domain="fast")


        #
        # LFPS Detection
        #
        m.submodules.lfps_detector = lfps_detector = LFPSSquareWaveDetector(self._fast_clock_frequency)
        m.d.comb += [
            lfps_detector.rx_gpio         .eq(self.rx_gpio),
            self.lfps_signaling_detected  .eq(lfps_detector.present)
        ]


        # debug signals
        m.d.comb += [
            self.raw_rx_data.eq(serdes.source.data),
            self.raw_rx_ctrl.eq(serdes.source.ctrl),
        ]

        return m
Exemple #16
0
    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        lane = self.lane
        m.submodules += lane  # Add the PCIe Lane as a submodule

        # TODO: Change this for 5 Gbit/s
        platform.add_clock_constraint(
            self.rx_clk, (500e6 if self.speed_5GTps else 250e6) / self.gearing
        )  # For NextPNR, set the maximum clock frequency such that errors are given
        platform.add_clock_constraint(
            self.tx_clk, (500e6 if self.speed_5GTps else 250e6) / self.gearing)

        # RX and TX clock input signals, these go to the SERDES.
        rx_clk_i = Signal()
        tx_clk_i = Signal()
        # RX and TX clock output signals, these come from the SERDES.
        rx_clk_o = Signal()
        tx_clk_o = Signal()

        # Connect RX and TX SERDES clock inputs to the RX clock output
        m.d.comb += rx_clk_i.eq(rx_clk_o)
        m.d.comb += tx_clk_i.eq(tx_clk_o)

        # Clocks exposed by this module are the clock for rx symbol input and tx symbol output, usually they should be frequency-locked but have variable phase offset.
        m.d.comb += self.rx_clk.eq(rx_clk_o)
        m.d.comb += self.tx_clk.eq(tx_clk_o)

        if self.fabric_clk:
            m.d.comb += self.ref_clk.eq(ClockSignal())

        else:
            # The clock input, on the Versa board this comes from the ispCLOCK IC
            m.submodules.extref0 = Instance(
                "EXTREFB",
                o_REFCLKO=self.
                ref_clk,  # The reference clock is output to ref_clk, it is not really accessible as a signal, since it only exists within the SERDES
                p_REFCK_PWDNB="0b1",
                p_REFCK_RTERM="0b1",  # 100 Ohm
                p_REFCK_DCBIAS_EN="0b0",
            )
            m.submodules.extref0.attrs["LOC"] = "EXTREF0"  # Locate it

        if self.gearing == 1:  # Different gearing compatibility!
            # If it is 1:1, only the first symbol has data
            m.d.comb += [
                lane.rx_symbol.eq(self.rx_bus[0:9]),
                lane.rx_valid.eq(
                    self.rx_bus[0:9] != K(14, 7)
                ),  # SERDES outputs K14.7 when there are coding errors
                self.tx_bus.eq(
                    Cat(lane.tx_symbol[0:9], lane.tx_set_disp[0],
                        lane.tx_disp[0], lane.tx_e_idle[0]))
            ]
        else:
            # For 1:2, the output symbols get composed from both symbols, structure of rx_data and tx_data is shown on page 8/9 of TN1261
            m.d.comb += [
                lane.rx_symbol.eq(Cat(self.rx_bus[0:9], self.rx_bus[12:21])),
                lane.rx_valid.eq(
                    Cat(self.rx_bus[0:9] != K(14, 7),
                        self.rx_bus[12:21] != K(14, 7))),
                self.tx_bus.eq(
                    Cat(lane.tx_symbol[0:9], lane.tx_set_disp[0],
                        lane.tx_disp[0], lane.tx_e_idle[0],
                        lane.tx_symbol[9:18], lane.tx_set_disp[1],
                        lane.tx_disp[1], lane.tx_e_idle[1])),
            ]

        #
        vals_ch_write = [  # LSB first
            #[0x16, "----0---"],
            #[0x18, "------10"],
            #[0x04, "----1--1"], # CTC bypass and lsm_disable
            #[0x05, "----00--"], # Disanble skip match
            #[0x1A, "---1----"], # pden_sel
            #[0x1B, "------00"], # los_en
            #[0x18, "----1---"],
        ]

        vals_du_write = [  # LSB first
            #[0x0B, "11-----0"],
            #[0x18, "----1---"],
        ]

        vals_ch_read = [
            #[0x37, self.debug.dco_status], # Read address 16 to test1
            [0x0B, self.debug.dco_status],  # Read address 16 to test1
        ]

        vals_du_read = []

        # SCI interface. (from LUNA https://github.com/greatscottgadgets/luna/blob/main/luna/gateware/interface/serdes_phy/backends/ecp5.py)
        m.submodules.sci = sci = ECP5SerDesConfigInterface()
        m.submodules.sci_ctrl = sci_ctrl = ECP5SerDesConfigController(
            sci, vals_ch_write, vals_du_write, vals_ch_read, vals_du_read)

        # RX signals and their domain-crossed parts
        rx_los = Signal()  # Loss of Signal
        rx_los_s = Signal()
        rx_lol = Signal()  # RX Loss of Lock
        rx_lol_s = Signal()
        rx_lsm = Signal()  # Sync state machine status
        rx_lsm_s = Signal()
        rx_inv = Signal()  # Invert RX
        rx_det = Signal()  # RX detected

        # TX signals
        tx_lol = Signal()  # TX PLL Loss of Lock
        tx_lol_s = Signal()

        # Reset Signals
        serdes_tx_reset = Signal()
        serdes_rx_reset = Signal()
        pcs_reset = Signal()
        cnt = Signal(8)

        with m.FSM(domain="rx"):  # Inspirations taken from LUNA
            with m.State("init"):
                m.d.comb += [
                    serdes_tx_reset.eq(1),
                    serdes_rx_reset.eq(1),
                    pcs_reset.eq(1),
                    lane.reset_done.eq(0),
                ]
                m.d.rx += cnt.eq(0)

                with m.If(~self.lane.reset):
                    m.next = "start-tx"

            with m.State("start-tx"):
                m.d.comb += [
                    serdes_tx_reset.eq(0),
                    serdes_rx_reset.eq(1),
                    pcs_reset.eq(1),
                    lane.reset_done.eq(0),
                ]
                m.d.rx += cnt.eq(cnt + 1)

                with m.If(~tx_lol_s | (cnt > 200)):
                    m.d.rx += cnt.eq(0)
                    m.next = "start-rx"

            with m.State("start-rx"):
                m.d.comb += [
                    serdes_tx_reset.eq(0),
                    serdes_rx_reset.eq(0),
                    pcs_reset.eq(1),
                    lane.reset_done.eq(0),
                ]
                m.d.rx += cnt.eq(cnt + 1)

                with m.If(~rx_lol_s | (cnt > 200)):
                    m.next = "start-pcs-done"

            with m.State("start-pcs-done"):
                m.d.comb += [
                    serdes_tx_reset.eq(0),
                    serdes_rx_reset.eq(0),
                    pcs_reset.eq(0),
                    lane.reset_done.eq(1),
                ]

                with m.If(self.lane.reset):
                    m.next = "init"

        # Clock domain crossing for status signals and tx data
        m.submodules += [
            FFSynchronizer(rx_los, rx_los_s, o_domain="rx"),
            FFSynchronizer(rx_lol, rx_lol_s, o_domain="rx"),
            FFSynchronizer(rx_lsm, rx_lsm_s, o_domain="rx"),
            FFSynchronizer(tx_lol, tx_lol_s, o_domain="rx"),
            #            FFSynchronizer(self.tx_bus, tx_bus_s, o_domain="rx"),
        ]

        # Connect the signals to the lanes signals
        m.d.comb += [
            rx_inv.eq(lane.rx_invert),
            rx_det.eq(lane.rx_align),
            lane.rx_present.eq(~rx_los_s),
            lane.rx_locked.eq(~rx_lol_s),
            lane.rx_aligned.eq(rx_lsm_s),
            lane.tx_locked.eq(~tx_lol_s)
        ]

        pcie_det_en = Signal()  # Enable lane detection
        pcie_ct = Signal()  # Scan enable flag
        pcie_done = Signal()  # Scan finished flag
        pcie_done_s = Signal()
        pcie_con = Signal()  # PCIe lane connected
        pcie_con_s = Signal()

        det_timer = Signal(range(16))  # Detection Timer

        # Clock domain crossing for PCIe detection signals
        m.submodules += [
            FFSynchronizer(pcie_done, pcie_done_s, o_domain="tx"),
            FFSynchronizer(pcie_con, pcie_con_s, o_domain="tx")
        ]

        with m.FSM(domain="tx", reset="START"):
            with m.State("START"):
                # Before starting a Receiver Detection test, the transmitter must be put into
                # electrical idle by setting the tx_idle_ch#_c input high. The Receiver Detection
                # test can begin 120 ns after tx_elec_idle is set high by driving the appropriate
                # pci_det_en_ch#_c high.
                m.d.tx += det_timer.eq(15)
                #m.d.tx += lane.det_valid.eq(0)
                with m.If(lane.det_enable):
                    m.next = "SET-DETECT-H"
            with m.State("SET-DETECT-H"):
                # 1. The user drives pcie_det_en high, putting the corresponding TX driver into
                #    receiver detect mode. [...] The TX driver takes some time to enter this state
                #    so the pcie_det_en must be driven high for at least 120ns before pcie_ct
                #    is asserted.
                with m.If(det_timer == 0):
                    m.d.tx += pcie_det_en.eq(1)
                    m.d.tx += det_timer.eq(15)
                    m.next = "SET-STROBE-H"
                with m.Else():
                    m.d.tx += det_timer.eq(det_timer - 1)
            with m.State("SET-STROBE-H"):
                # 2. The user drives pcie_ct high for four byte clocks.
                with m.If(det_timer == 0):
                    m.d.tx += pcie_ct.eq(1)
                    m.d.tx += det_timer.eq(3)
                    m.next = "SET-STROBE-L"
                with m.Else():
                    m.d.tx += det_timer.eq(det_timer - 1)
            with m.State("SET-STROBE-L"):
                # 3. SERDES drives the corresponding pcie_done low.
                # (this happens asynchronously, so we're going to observe a few samples of pcie_done
                # as high)
                with m.If(det_timer == 0):
                    m.d.tx += pcie_ct.eq(0)
                    m.next = "WAIT-DONE-L"
                with m.Else():
                    m.d.tx += det_timer.eq(det_timer - 1)
            with m.State("WAIT-DONE-L"):
                with m.If(~pcie_done_s):
                    m.next = "WAIT-DONE-H"
            with m.State("WAIT-DONE-H"):
                with m.If(pcie_done_s):
                    #m.d.tx += lane.det_status.eq(pcie_con_s) TODO: Figure this out
                    #m.d.tx += lane.det_status.eq(pcie_con_s)
                    m.d.tx += lane.det_status.eq(1)
                    m.next = "DONE"
            with m.State("DONE"):
                m.d.tx += lane.det_valid.eq(1)
                with m.If(~lane.det_enable):
                    m.next = "START"
                with m.Else():
                    m.next = "DONE"

        gearing_str = "0b0" if self.gearing == 1 else "0b1"  # Automatically select value based on gearing

        dcu_config = {
            "p_D_MACROPDB": "0b1",
            "p_D_IB_PWDNB":
            "0b1",  # undocumented, seems to be "input buffer power down"
            "p_D_TXPLL_PWDNB": "0b1",
            "i_D_FFC_MACROPDB": 1,

            # DCU — reset
            "i_D_FFC_MACRO_RST": 0,
            "i_D_FFC_DUAL_RST": 0,
            "i_D_FFC_TRST": serdes_tx_reset,

            # DCU — clocking
            "i_D_REFCLKI": self.ref_clk,
            "o_D_FFS_PLOL": tx_lol,
            "p_D_REFCK_MODE": "0b100",  # 25x ref_clk
            "p_D_TX_MAX_RATE":
            "5.0" if self.speed_5GTps else "2.5",  # How many Gbps
            "p_D_TX_VCO_CK_DIV": "0b000",  # DIV/1
            "p_D_BITCLK_LOCAL_EN":
            "0b1",  # undocumented (PCIe sample code used)
            "p_D_SYNC_LOCAL_EN": "0b1",
            "p_D_BITCLK_FROM_ND_EN": "0b0",

            # DCU ­— unknown
            "p_D_CMUSETBIASI":
            "0b00",  # begin undocumented (PCIe sample code used)
            "p_D_CMUSETI4CPP": "0d3",  # 0d4 in Yumewatari
            "p_D_CMUSETI4CPZ": "0b101",  # 0d3 in Yumewatari
            "p_D_CMUSETI4VCO": "0b00",
            "p_D_CMUSETICP4P": "0b01",
            "p_D_CMUSETICP4Z": "0b101",
            "p_D_CMUSETINITVCT": "0b00",
            "p_D_CMUSETISCL4VCO": "0b000",
            "p_D_CMUSETP1GM": "0b000",
            "p_D_CMUSETP2AGM": "0b000",
            #"p_D_CMUSETZGM"          :"0b100",
            #"p_D_SETIRPOLY_AUX"      :"0b10",
            "p_D_CMUSETZGM": "0b000",
            "p_D_SETIRPOLY_AUX": "0b00",
            "p_D_SETICONST_AUX": "0b01",
            #"p_D_SETIRPOLY_CH"       :"0b10",
            #"p_D_SETICONST_CH"       :"0b10",
            "p_D_SETIRPOLY_CH": "0b00",
            "p_D_SETICONST_CH": "0b00",
            "p_D_SETPLLRC": "0d1",
            "p_D_RG_EN": "0b1",
            "p_D_RG_SET": "0b00",  # end undocumented

            # DCU — FIFOs
            "p_D_LOW_MARK": "0d4",
            "p_D_HIGH_MARK": "0d12",
        }

        ch_config = {
            # CH0 ­— protocol
            "p_CHx_PROTOCOL": "PCIe",
            "p_CHx_PCIE_MODE": "0b1",

            # RX CH ­— power management
            "p_CHx_RPWDNB": "0b1",
            "i_CHx_FFC_RXPWDNB": 1,

            # RX CH ­— reset
            "i_CHx_FFC_RRST": serdes_rx_reset,
            "i_CHx_FFC_LANE_RX_RST": pcs_reset,

            # RX CH ­— input
            "i_CHx_FFC_SB_INV_RX": rx_inv,

            #"p_CHx_REQ_EN"            :"0b1",
            #"p_CHx_RX_RATE_SEL"       :"0d8",     # LUNA uses 0d09 here
            #"p_CHx_REQ_LVL_SET"       :"0b00",    # LUNA uses 0b01 (9 dB) here
            "p_CHx_RTERM_RX":
            "0d22",  # 50 Ohm (wizard value used, does not match datasheet)
            "p_CHx_RXIN_CM": "0b11",  # CMFB (wizard value used)
            "p_CHx_RXTERM_CM": "0b10",  # RX Input terminate to GND

            # RX CH ­— clocking
            "i_CHx_RX_REFCLK": self.ref_clk,
            "o_CHx_FF_RX_PCLK": rx_clk_o,
            "i_CHx_FF_RXI_CLK": rx_clk_i,
            "p_CHx_RX_GEAR_MODE": gearing_str,  # 1:2 gearbox
            "p_CHx_FF_RX_H_CLK_EN": gearing_str,  # enable  DIV/2 output clock
            "p_CHx_FF_RX_F_CLK_DIS": gearing_str,  # disable DIV/1 output clock
            "i_CHx_FFC_RATE_MODE_RX":
            self.divide_clk,  # Divide by 2 when set to 1
            "p_CHx_AUTO_FACQ_EN": "0b1",  # undocumented (wizard value used)
            "p_CHx_AUTO_CALIB_EN": "0b1",  # undocumented (wizard value used)
            #"p_CHx_BAND_THRESHOLD"    :"0b00",
            "p_CHx_CDR_MAX_RATE":
            "5.0" if self.speed_5GTps else "2.5",  # How many Gbps
            "p_CHx_RX_DCO_CK_DIV": "0b000",  # DIV/1
            "p_CHx_PDEN_SEL": "0b1",  # phase detector disabled on ~LOS
            "p_CHx_SEL_SD_RX_CLK": "0b1",  # FIFO driven by recovered clock
            #"p_CHx_SEL_SD_RX_CLK"     :"0b0",     # FIFO driven by FF_EBRD_CLK
            "p_CHx_CTC_BYPASS": "******",  # bypass CTC FIFO

            # FIFO bridge clocking
            "i_CHx_FF_EBRD_CLK": tx_clk_i,
            "p_CHx_TXDEPRE": "DISABLED",
            "p_CHx_TXDEPOST": "DISABLED",
            "p_CHx_DCOATDCFG":
            "0b00",  # begin undocumented (PCIe sample code used)
            "p_CHx_DCOATDDLY": "0b00",
            "p_CHx_DCOBYPSATD": "0b1",
            #"p_CHx_DCOCALDIV         :"0b010",
            #"p_CHx_DCOCTLGI          :"0b011",
            #"p_CHx_DCODISBDAVOID     :"0b1",
            #"p_CHx_DCOFLTDAC         :"0b00",
            #"p_CHx_DCOFTNRG          :"0b010",
            #"p_CHx_DCOIOSTUNE        :"0b010",
            "p_CHx_DCOCALDIV": "0b001",
            "p_CHx_DCOCTLGI": "0b010",
            "p_CHx_DCODISBDAVOID": "0b0",
            "p_CHx_DCOFLTDAC": "0b01",
            "p_CHx_DCOFTNRG": "0b111",
            "p_CHx_DCOIOSTUNE": "0b000",
            "p_CHx_DCOITUNE": "0b00",
            #"p_CHx_DCOITUNE4LSB      :"0b010",
            "p_CHx_DCOITUNE4LSB": "0b111",
            "p_CHx_DCOIUPDNX2": "0b1",
            "p_CHx_DCONUOFLSB": "0b101",
            #"p_CHx_DCOSCALEI         :"0b01",
            #"p_CHx_DCOSTARTVAL       :"0b010",
            #"p_CHx_DCOSTEP           :"0b11",    # end undocumented
            "p_CHx_DCOSCALEI": "0b00",
            "p_CHx_DCOSTARTVAL": "0b000",
            "p_CHx_DCOSTEP": "0b00",  # end undocumented
            #"p_CHx_DCOATDCFG"         : "0b00",   # begin undocumented (sample code used) (from LUNA https://github.com/greatscottgadgets/luna/blob/0a0393528519d68bfd8dcb2b771502134f331923/luna/gateware/interface/serdes_phy/backends/ecp5.py)
            #"p_CHx_DCOATDDLY"         : "0b00",
            #"p_CHx_DCOBYPSATD"        : "0b1",
            #"p_CHx_DCOCALDIV"         : "0b000",
            #"p_CHx_DCOCTLGI"          : "0b011",
            #"p_CHx_DCODISBDAVOID"     : "0b0",
            #"p_CHx_DCOFLTDAC"         : "0b00",
            #"p_CHx_DCOFTNRG"          : "0b001",
            #"p_CHx_DCOIOSTUNE"        : "0b010",
            #"p_CHx_DCOITUNE"          : "0b00",
            #"p_CHx_DCOITUNE4LSB"      : "0b010",
            #"p_CHx_DCOIUPDNX2"        : "0b1",
            #"p_CHx_DCONUOFLSB"        : "0b100",
            #"p_CHx_DCOSCALEI"         : "0b01",
            #"p_CHx_DCOSTARTVAL"       : "0b010",
            #"p_CHx_DCOSTEP"           : "0b11",   # end undocumented

            # RX CH — link state machine
            "i_CHx_FFC_SIGNAL_DETECT":
            rx_det,  # WARNING: If 0, then no symbol lock happens
            "o_CHx_FFS_LS_SYNC_STATUS": rx_lsm,
            "p_CHx_ENABLE_CG_ALIGN": "0b1",
            "p_CHx_UDF_COMMA_MASK": "0x3ff",  # compare all 10 bits
            "p_CHx_UDF_COMMA_A":
            "0x283",  # K28.5 inverted, encoded in reversed order
            "p_CHx_UDF_COMMA_B": "0x17C",  # K28.5, encoded in reversed order
            "p_CHx_MIN_IPG_CNT": "0b11",  # minimum interpacket gap of 4
            "p_CHx_MATCH_4_ENABLE": "0b1",  # 4 character skip matching
            "p_CHx_CC_MATCH_1": "0x1BC",  # K28.5 Comma
            "p_CHx_CC_MATCH_2": "0x11C",  # K28.0 Skip
            "p_CHx_CC_MATCH_3": "0x11C",  # K28.0 Skip
            "p_CHx_CC_MATCH_4": "0x11C",  # K28.0 Skip

            # RX CH — loss of signal
            "o_CHx_FFS_RLOS": rx_los,
            "p_CHx_RLOS_SEL":
            "0b1",  # Maybe try if values from LUNA are better here?
            "p_CHx_RX_LOS_EN": "0b1",
            "p_CHx_RX_LOS_LVL": "0b100",  # Lattice "TBD" (wizard value used)
            "p_CHx_RX_LOS_CEQ": "0b11",  # Lattice "TBD" (wizard value used)
            "p_CHx_RX_LOS_HYST_EN": "0b0",

            # RX CH — loss of lock
            "o_CHx_FFS_RLOL": rx_lol,

            # RX CH — data
            **{
                "o_CHx_FF_RX_D_%d" % n: self.rx_bus[n]
                for n in range(self.rx_bus.width)
            },  # Connect outputs to RX data signals
            "p_CHx_DEC_BYPASS": "******",  # Bypass 8b10b?

            # TX CH — power management
            #"p_CHx_TPWDNB"           :"0b1",
            "p_CHx_TPWDNB": "0b0",
            "i_CHx_FFC_TXPWDNB": 1,

            # TX CH ­— reset
            "i_CHx_FFC_LANE_TX_RST": pcs_reset,

            # TX CH ­— output
            "p_CHx_TXAMPLITUDE": "0d1000",  # 1000 mV
            "p_CHx_RTERM_TX": "0d19",  # 50 Ohm
            "p_CHx_TDRV_SLICE0_CUR": "0b011",  # 400 uA
            "p_CHx_TDRV_SLICE0_SEL": "0b01",  # main data
            "p_CHx_TDRV_SLICE1_CUR": "0b000",  # 100 uA
            "p_CHx_TDRV_SLICE1_SEL": "0b00",  # power down
            "p_CHx_TDRV_SLICE2_CUR": "0b11",  # 3200 uA
            "p_CHx_TDRV_SLICE2_SEL": "0b01",  # main data
            "p_CHx_TDRV_SLICE3_CUR": "0b11",  # 3200 uA
            "p_CHx_TDRV_SLICE3_SEL": "0b01",  # main data
            "p_CHx_TDRV_SLICE4_CUR": "0b11",  # 3200 uA
            "p_CHx_TDRV_SLICE4_SEL": "0b01",  # main data
            "p_CHx_TDRV_SLICE5_CUR": "0b00",  # 800 uA
            "p_CHx_TDRV_SLICE5_SEL": "0b00",  # power down

            # TX CH ­— clocking
            "o_CHx_FF_TX_PCLK": tx_clk_o,  # Output from SERDES
            "i_CHx_FF_TXI_CLK": tx_clk_i,  # Input to SERDES
            "p_CHx_TX_GEAR_MODE": gearing_str,  # 1:2 gearbox
            "p_CHx_FF_TX_H_CLK_EN": gearing_str,  # disable DIV/1 output clock
            "p_CHx_FF_TX_F_CLK_DIS": gearing_str,  # enable  DIV/2 output clock
            "i_CHx_FFC_RATE_MODE_TX":
            self.divide_clk,  # Divide by 2 when set to 1

            # TX CH — data
            **{
                "o_CHx_FF_TX_D_%d" % n: self.tx_bus[n]
                for n in range(self.tx_bus.width)
            },  # Connect TX SERDES inputs to the signals
            "p_CHx_ENC_BYPASS": "******",  # Bypass 8b10b

            # CHx DET
            "i_CHx_FFC_PCIE_DET_EN": pcie_det_en,
            "i_CHx_FFC_PCIE_CT": pcie_ct,
            "o_CHx_FFS_PCIE_DONE": pcie_done,
            "o_CHx_FFS_PCIE_CON": pcie_con,

            # Bit Slip
            "i_CHx_FFC_CDR_EN_BITSLIP": self.slip,

            #"i_CHx_FFC_FB_LOOPBACK"  : 3,

            # SCI interface, see pages 52-55 in TN1261
            **{"i_D_SCIWDATA%d" % n: sci.sci_wdata[n]
               for n in range(8)},  # Data in
            **{"i_D_SCIADDR%d" % n: sci.sci_addr[n]
               for n in range(6)},  # Address
            **{"o_D_SCIRDATA%d" % n: sci.sci_rdata[n]
               for n in range(8)},  # Data out
            "i_D_SCIENAUX": sci.dual_sel,  # Select dual registers
            "i_D_SCISELAUX": sci.dual_sel,
            "i_CH1_SCIEN": sci.chan_sel,  # Select channel registers
            "i_CH1_SCISEL": sci.chan_sel,
            "i_D_SCIRD": sci.sci_rd,  # Read
            "i_D_SCIWSTN": sci.sci_wrn,  # Write
        }

        modified_ch_config = {}

        for key in ch_config:
            modified_ch_config[key.replace(
                "CHx", "CH0" if self.CH == 0 else "CH1")] = ch_config[key]

        m.submodules.dcu0 = Instance("DCUA", **dcu_config,
                                     **modified_ch_config)

        m.submodules.dcu0.attrs["LOC"] = "DCU0" if self.DCU == 0 else "DCU1"

        # Needed for Lattice Diamond, Trellis does not need this.
        m.submodules.dcu0.attrs["CHAN"] = "CH0" if self.CH == 0 else "CH1"
        m.submodules.dcu0.attrs["BEL"] = "X42/Y71/DCU"

        return m
Exemple #17
0
    def elaborate(self, platform):
        m = Module()

        # Memory ports.
        write_port = self.mem.write_port()
        read_port = self.mem.read_port(domain="sync")
        m.submodules += [write_port, read_port]

        # If necessary, create synchronized versions of the relevant signals.
        if self.samples_pretrigger >= 2:
            delayed_inputs = Signal.like(self.inputs)
            m.submodules += FFSynchronizer(self.inputs,
                                           delayed_inputs,
                                           stages=self.samples_pretrigger)
        elif self.samples_pretrigger == 1:
            delayed_inputs = Signal.like(self.inputs)
            m.d.sync += delayed_inputs.eq(self.inputs)
        else:
            delayed_inputs = self.inputs

        # Counter that keeps track of our write position.
        write_position = Signal(range(0, self.sample_depth))

        # Set up our write port to capture the input signals,
        # and our read port to provide the output.
        m.d.comb += [
            write_port.data.eq(delayed_inputs),
            write_port.addr.eq(write_position),
            self.captured_sample.eq(read_port.data),
            read_port.addr.eq(self.captured_sample_number)
        ]

        # Don't sample unless our FSM asserts our sample signal explicitly.
        m.d.sync += write_port.en.eq(0)

        with m.FSM(name="ila_state") as fsm:

            m.d.comb += self.sampling.eq(~fsm.ongoing("IDLE"))

            # IDLE: wait for the trigger strobe
            with m.State('IDLE'):

                with m.If(self.trigger):
                    m.next = 'SAMPLE'

                    # Grab a sample as our trigger is asserted.
                    m.d.sync += [
                        write_port.en.eq(1),
                        write_position.eq(0),
                        self.complete.eq(0),
                    ]

            # SAMPLE: do our sampling
            with m.State('SAMPLE'):

                # Sample until we run out of samples.
                m.d.sync += [
                    write_port.en.eq(1),
                    write_position.eq(write_position + 1),
                ]

                # If this is the last sample, we're done. Finish up.
                with m.If(write_position + 1 == self.sample_depth):
                    m.next = "IDLE"

                    m.d.sync += [self.complete.eq(1), write_port.en.eq(0)]

        # Convert our sync domain to the domain requested by the user, if necessary.
        if self.domain != "sync":
            m = DomainRenamer(self.domain)(m)

        return m
Exemple #18
0
    def elaborate(self, platform):
        m = Module()
        pins = self._pins
        in_fifo  = self._in_fifo
        out_fifo = self._out_fifo

        jtag_oe = Signal(len(pins))
        jtag_o  = Signal(len(pins))
        jtag_i  = Signal(len(pins))
        m.d.comb += [
            Cat(pin.oe for pin in pins).eq(jtag_oe),
            Cat(pin.o  for pin in pins).eq(jtag_o),
        ]
        m.submodules += FFSynchronizer(Cat(pin.i for pin in pins), jtag_i)

        timer = Signal(range(self._period_cyc))
        cmd   = Signal(8)
        data  = Signal(16)

        with m.FSM():
            with m.State("RECV-COMMAND"):
                with m.If(out_fifo.readable):
                    m.d.comb += out_fifo.re.eq(1)
                    m.d.sync += cmd.eq(out_fifo.dout)
                    with m.If(out_fifo.dout == CMD_W):
                        m.d.sync += timer.eq(self._period_cyc - 1)
                        m.next = "WAIT"
                    with m.Elif(out_fifo.dout == CMD_I):
                        m.next = "SAMPLE"
                    with m.Else():
                        m.next = "RECV-DATA-1"

            with m.State("RECV-DATA-1"):
                with m.If(out_fifo.readable):
                    m.d.comb += out_fifo.re.eq(1)
                    m.d.sync += data[0:8].eq(out_fifo.dout)
                    m.next = "RECV-DATA-2"

            with m.State("RECV-DATA-2"):
                with m.If(out_fifo.readable):
                    m.d.comb += out_fifo.re.eq(1)
                    m.d.sync += data[8:16].eq(out_fifo.dout)
                    m.next = "DRIVE"

            with m.State("DRIVE"):
                with m.If(cmd == CMD_OE):
                    m.d.sync += jtag_oe.eq(data)
                with m.Elif(cmd == CMD_O):
                    m.d.sync += jtag_o.eq( data)
                with m.Elif(cmd == CMD_L):
                    m.d.sync += jtag_o.eq(~data & jtag_o)
                with m.Elif(cmd == CMD_H):
                    m.d.sync += jtag_o.eq( data | jtag_o)
                m.next = "RECV-COMMAND"

            with m.State("WAIT"):
                with m.If(timer == 0):
                    m.next = "RECV-COMMAND"
                with m.Else():
                    m.d.sync += timer.eq(timer - 1)

            with m.State("SAMPLE"):
                m.d.sync += data.eq(jtag_i)
                m.next = "SEND-DATA-1"

            with m.State("SEND-DATA-1"):
                with m.If(in_fifo.writable):
                    m.d.comb += in_fifo.we.eq(1)
                    m.d.comb += in_fifo.din.eq(data[0:8])
                    m.next = "SEND-DATA-2"

            with m.State("SEND-DATA-2"):
                with m.If(in_fifo.writable):
                    m.d.comb += in_fifo.we.eq(1)
                    m.d.comb += in_fifo.din.eq(data[8:16])
                    m.next = "RECV-COMMAND"

        return m
Exemple #19
0
 def create_synchronizer(signal, output):
     return FFSynchronizer(signal, output, o_domain=o_domain, stages=stages)