def __init__(self, layout, cd_from="sys", cd_to="sys", depth=None, with_common_rst=False): self.sink = Endpoint(layout) self.source = Endpoint(layout) # # # # Same Clk Domains. if cd_from == cd_to: # No adaptation. self.comb += self.sink.connect(self.source) # Different Clk Domains. else: if with_common_rst: # Create intermediate Clk Domains and generate a common Rst. _cd_id = id( self ) # FIXME: Improve, used to allow build with anonymous modules. _cd_rst = Signal() _cd_from = ClockDomain(f"from{_cd_id}") _cd_to = ClockDomain(f"to{_cd_id}") self.clock_domains += _cd_from, _cd_to self.comb += [ _cd_from.clk.eq(ClockSignal(cd_from)), _cd_to.clk.eq(ClockSignal(cd_to)), _cd_rst.eq(ResetSignal(cd_from) | ResetSignal(cd_to)) ] cd_from = _cd_from.name cd_to = _cd_to.name # Use common Rst on both Clk Domains (through AsyncResetSynchronizer). self.specials += [ AsyncResetSynchronizer(_cd_from, _cd_rst), AsyncResetSynchronizer(_cd_to, _cd_rst), ] # Add Asynchronous FIFO cdc = AsyncFIFO(layout, depth) cdc = ClockDomainsRenamer({"write": cd_from, "read": cd_to})(cdc) self.submodules += cdc # Sink -> AsyncFIFO -> Source. self.comb += self.sink.connect(cdc.sink) self.comb += cdc.source.connect(self.source)
def __init__(self, jtag=None, device=None, data_width=8, clock_domain="sys", chain=1, platform=None): """JTAG PHY Provides a simple JTAG to LiteX stream module to easily stream data to/from the FPGA over JTAG. Wire format: data_width + 2 bits, LSB first. Host to Target: - TX ready : bit 0 - RX data: : bit 1 to data_width - RX valid : bit data_width + 1 Target to Host: - RX ready : bit 0 - TX data : bit 1 to data_width - TX valid : bit data_width + 1 """ self.sink = sink = stream.Endpoint([("data", data_width)]) self.source = source = stream.Endpoint([("data", data_width)]) # # # # JTAG TAP --------------------------------------------------------------------------------- if jtag is None: jtag_tdi_delay = 0 # Xilinx. if XilinxJTAG.get_primitive(device) is not None: jtag = XilinxJTAG(primitive=XilinxJTAG.get_primitive(device), chain=chain) jtag_tdi_delay = XilinxJTAG.get_tdi_delay(device) # Lattice. elif device[:5] == "LFE5U": jtag = ECP5JTAG() # Altera/Intel. elif AlteraJTAG.get_primitive(device) is not None: platform.add_reserved_jtag_decls() jtag = AlteraJTAG(primitive=AlteraJTAG.get_primitive(device), pads=platform.get_reserved_jtag_pads()) else: print(device) raise NotImplementedError self.submodules.jtag = jtag # JTAG clock domain ------------------------------------------------------------------------ self.clock_domains.cd_jtag = ClockDomain() self.comb += ClockSignal("jtag").eq(jtag.tck) self.specials += AsyncResetSynchronizer(self.cd_jtag, ResetSignal(clock_domain)) # JTAG clock domain crossing --------------------------------------------------------------- if clock_domain != "jtag": tx_cdc = stream.AsyncFIFO([("data", data_width)], 4) tx_cdc = ClockDomainsRenamer({ "write": clock_domain, "read": "jtag" })(tx_cdc) rx_cdc = stream.AsyncFIFO([("data", data_width)], 4) rx_cdc = ClockDomainsRenamer({ "write": "jtag", "read": clock_domain })(rx_cdc) self.submodules.tx_cdc = tx_cdc self.submodules.rx_cdc = rx_cdc self.comb += [ sink.connect(tx_cdc.sink), rx_cdc.source.connect(source) ] sink, source = tx_cdc.source, rx_cdc.sink # JTAG TDI/TDO Delay ----------------------------------------------------------------------- jtag_tdi = jtag.tdi jtag_tdo = jtag.tdo if jtag_tdi_delay: jtag_tdi_sr = Signal(data_width + 2 - jtag_tdi_delay) self.sync.jtag += If(jtag.shift, jtag_tdi_sr.eq(Cat(jtag.tdi, jtag_tdi_sr))) jtag_tdi = jtag_tdi_sr[-1] # JTAG Xfer FSM ---------------------------------------------------------------------------- valid = Signal() ready = Signal() data = Signal(data_width) count = Signal(max=data_width) fsm = FSM(reset_state="XFER-READY") fsm = ClockDomainsRenamer("jtag")(fsm) fsm = ResetInserter()(fsm) self.submodules += fsm self.comb += fsm.reset.eq(jtag.reset | jtag.capture) fsm.act( "XFER-READY", jtag_tdo.eq(ready), If(jtag.shift, sink.ready.eq(jtag_tdi), NextValue(valid, sink.valid), NextValue(data, sink.data), NextValue(count, 0), NextState("XFER-DATA"))) fsm.act( "XFER-DATA", jtag_tdo.eq(data), If(jtag.shift, NextValue(count, count + 1), NextValue(data, Cat(data[1:], jtag_tdi)), If(count == (data_width - 1), NextState("XFER-VALID")))) fsm.act( "XFER-VALID", jtag_tdo.eq(valid), If(jtag.shift, source.valid.eq(jtag_tdi), source.data.eq(data), NextValue(ready, source.ready), NextState("XFER-READY")))
def __init__(self, jtag=None, device=None, data_width=8, clock_domain="sys"): """JTAG PHY Provides a simple JTAG to LiteX stream module to easily stream data to/from the FPGA over JTAG. Wire format: data_width + 2 bits, LSB first. Host to Target: - TX ready : bit 0 - RX data: : bit 1 to data_width - RX valid : bit data_width + 1 Target to Host: - RX ready : bit 0 - TX data : bit 1 to data_width - TX valid : bit data_width + 1 """ self.sink = sink = stream.Endpoint([("data", data_width)]) self.source = source = stream.Endpoint([("data", data_width)]) # # # valid = Signal() data = Signal(data_width) count = Signal(max=data_width) # JTAG TAP --------------------------------------------------------------------------------- if jtag is None: if device[:3] == "xc6": jtag = S6JTAG() elif device[:3] == "xc7": jtag = S7JTAG() elif device[:4] in ["xcku", "xcvu"]: jtag = USJTAG() else: raise NotImplementedError self.submodules += jtag # JTAG clock domain ------------------------------------------------------------------------ self.clock_domains.cd_jtag = ClockDomain() self.comb += ClockSignal("jtag").eq(jtag.tck) self.specials += AsyncResetSynchronizer(self.cd_jtag, ResetSignal("sys")) # JTAG clock domain crossing --------------------------------------------------------------- if clock_domain != "jtag": tx_cdc = stream.AsyncFIFO([("data", data_width)], 4) tx_cdc = ClockDomainsRenamer({ "write": clock_domain, "read": "jtag" })(tx_cdc) rx_cdc = stream.AsyncFIFO([("data", data_width)], 4) rx_cdc = ClockDomainsRenamer({ "write": "jtag", "read": clock_domain })(rx_cdc) self.submodules += tx_cdc, rx_cdc self.comb += [ sink.connect(tx_cdc.sink), rx_cdc.source.connect(source) ] sink, source = tx_cdc.source, rx_cdc.sink # JTAG Xfer FSM ---------------------------------------------------------------------------- fsm = FSM(reset_state="XFER-READY") fsm = ClockDomainsRenamer("jtag")(fsm) fsm = ResetInserter()(fsm) self.submodules += fsm self.comb += fsm.reset.eq(jtag.reset | jtag.capture) fsm.act( "XFER-READY", jtag.tdo.eq(source.ready), If(jtag.shift, sink.ready.eq(jtag.tdi), NextValue(valid, sink.valid), NextValue(data, sink.data), NextValue(count, 0), NextState("XFER-DATA"))) fsm.act( "XFER-DATA", jtag.tdo.eq(data), If(jtag.shift, NextValue(count, count + 1), NextValue(data, Cat(data[1:], jtag.tdi)), If(count == (data_width - 1), NextState("XFER-VALID")))) fsm.act( "XFER-VALID", jtag.tdo.eq(valid), If(jtag.shift, source.valid.eq(jtag.tdi), NextState("XFER-READY"))) self.comb += source.data.eq(data)
def __init__(self, S=8, D=2, MIRROR_BITS=False, CLK_EDGE_ALIGNED=True, BITSLIPS=0): """ S = serialization factor (bits per frame) D = number of parallel lanes MIRROR_BITS = False first bit of the serial stream clocked in ends up in the LSB of data_outs CLK_EDGE_ALIGNED = True Clock and data lanes are in-phase CLK_EDGE_ALIGNED = False Clock is 90 deg shifted to data (clock transitions in middle of data-eye) BITSLIPS: Number of bitslips to trigger after initialization See Sp6Common.py for input output ports """ ### # Add data lanes and control signals Sp6Common.__init__(self, S, D, MIRROR_BITS, BITSLIPS, {"p_DATA_RATE": "DDR"}, {"p_DATA_RATE": "DDR"}) self.specials += AsyncResetSynchronizer(self.sample, self.reset) # ------------------------------------- # Iserdes DDR clocking scheme # ------------------------------------- dcoo_p = Signal() dcoo_n = Signal() self.specials += Instance("IBUFDS_DIFF_OUT", i_I=self.dco_p, i_IB=self.dco_n, o_O=dcoo_p, o_OB=dcoo_n) dco_del_p = Signal() dco_del_n = Signal() # Add 90 degree phase shift to clock if it is not edge aligned self.idelay_default["p_IDELAY_TYPE"] = \ "FIXED" if CLK_EDGE_ALIGNED else "VARIABLE_FROM_HALF_MAX" self.specials += Instance("IODELAY2", p_SERDES_MODE="MASTER", i_IDATAIN=dcoo_p, i_CAL=self.idelay_cal_m, o_DATAOUT=dco_del_p, **self.idelay_default) self.specials += Instance("IODELAY2", p_SERDES_MODE="SLAVE", i_IDATAIN=dcoo_n, i_CAL=self.idelay_cal_s, o_DATAOUT=dco_del_n, **self.idelay_default) divclk = Signal() self.specials += Instance("BUFIO2_2CLK", p_DIVIDE=S, i_I=dco_del_p, i_IB=dco_del_n, o_IOCLK=self.ioclk_p, o_DIVCLK=divclk, o_SERDESSTROBE=self.serdesstrobe) self.specials += Instance("BUFG", i_I=divclk, o_O=ClockSignal("sample")) self.specials += Instance("BUFIO2", p_I_INVERT="FALSE", p_DIVIDE_BYPASS="******", p_USE_DOUBLER="FALSE", i_I=dco_del_n, o_IOCLK=self.ioclk_n)
def __init__(self, platform): n_bits = 16 n_frame = 14 t_clk = 4. self.submodules.link = Link( clk=platform.request("eem0_p", 0), data=[platform.request("eem0_p", i + 1) for i in range(6)], platform=platform, t_clk=t_clk) cd_sys = ClockDomain("sys") self.clock_domains += cd_sys self.comb += [ cd_sys.rst.eq(ResetSignal("word")), cd_sys.clk.eq(ClockSignal("word")), ] # platform.add_period_constraint(cd_sys.clk, t_clk*link.n_div) self.submodules.frame = Frame() self.comb += self.frame.word.eq(self.link.word) adr = Signal(4) cfg = Record([ ("rst", 1), ("afe_pwr_n", 1), ("dac_clr", 1), ("clr_err", 1), ("led", 8), ("typ", 1), ("reserved", 7), ]) cfg_comb = Record(cfg.layout) unlock = Signal(reset=1) # slow MISO lane, TBD sdo = Signal() self.specials += [ Instance( "SB_IO", p_PIN_TYPE=C(0b010100, 6), # output registered p_IO_STANDARD="SB_LVCMOS", i_OUTPUT_CLK=ClockSignal("word"), o_PACKAGE_PIN=platform.request("eem0_p", 7), i_D_OUT_0=sdo), # falling SCK Instance( "SB_IO", p_PIN_TYPE=C(0b011100, 6), # output registered inverted p_IO_STANDARD="SB_LVCMOS", i_OUTPUT_CLK=ClockSignal("word"), o_PACKAGE_PIN=platform.request("eem0_n", 7), i_D_OUT_0=sdo), # falling SCK ] sr = Signal(n_frame, reset_less=True) # status register status = Signal((1 << len(adr)) * len(sr)) status_ = Cat( C(0xfa, 8), # ID platform.request("cbsel"), platform.request("sw"), platform.request("hw_rev"), C(0, 3), # gw version unlock, self.link.delay, self.link.align_err, self.frame.crc_err, cfg.raw_bits()) assert len(status_) <= len(status) self.comb += [ cfg_comb.raw_bits().eq(self.frame.body), status.eq(status_), sdo.eq(sr[-1]), # MSB first adr.eq(self.frame.body[len(cfg):]), ] have_crc_err = Signal() self.sync += [ sr[1:].eq(sr), If( self.frame.stb, cfg.raw_bits().eq(cfg_comb.raw_bits()), # grab data from status register according to address sr.eq( Array([ status[i * n_frame:(i + 1) * n_frame] for i in range(1 << len(adr)) ])[adr]), ), If( cfg.clr_err, unlock.eq(0), self.frame.crc_err.eq(0), ), have_crc_err.eq(self.frame.crc_err != 0), ] have_align_err = Signal() self.sync.word += [ If( cfg.clr_err, self.link.align_err.eq(0), ), have_align_err.eq(self.link.align_err != 0) ] # set up spi clock to 7*t_clk*3/4 = 21 ns cd_spi = ClockDomain("spi") self.clock_domains += cd_spi divr = 3 - 1 divf = 4 - 1 divq = 4 t_out = t_clk * self.link.n_div * (divr + 1) / (divf + 1) # *2**divq assert t_out >= 20., t_out assert t_out < t_clk * self.link.n_div platform.add_period_constraint(cd_spi.clk, t_out) # calculate the minimum delay between the 28 ns word clock # and the 21 ns SPI clock: 7ns, if this is large enough we don't need a # synchronizer (see below) print( "Check 'Max delay posedge sys_clk -> posedge spi_clk' <", min((i * t_out) % (t_clk * self.link.n_div) for i in range(1, (divf + 1) // gcd(divr + 1, divf + 1)))) t_idle = n_frame * t_clk * self.link.n_div - (n_bits + 1) * t_out assert t_idle >= 0, t_idle t_vco = t_out / 2**divq assert .533 <= 1 / t_vco <= 1.066, 1 / t_vco locked = Signal() self.specials += [ Instance( "SB_PLL40_CORE", p_FEEDBACK_PATH="DELAY", p_DIVR=divr, # input p_DIVF=divf, # feedback p_DIVQ=divq, # vco p_FILTER_RANGE=1, p_PLLOUT_SELECT="GENCLK", p_ENABLE_ICEGATE=1, p_DELAY_ADJUSTMENT_MODE_FEEDBACK="DYNAMIC", p_FDA_FEEDBACK=0xf, p_DELAY_ADJUSTMENT_MODE_RELATIVE="DYNAMIC", p_FDA_RELATIVE=0xf, i_BYPASS=0, i_RESETB=~(cfg.rst | ResetSignal("word")), i_DYNAMICDELAY=Cat(self.link.delay, self.link.delay_relative), i_REFERENCECLK=ClockSignal("link"), i_LATCHINPUTVALUE=0, o_LOCK=locked, o_PLLOUTGLOBAL=cd_spi.clk, # o_PLLOUTCORE=, ), AsyncResetSynchronizer(cd_spi, ~locked), ] self.submodules.int0 = ClockDomainsRenamer("spi")(Interpolator)( n_channels=16) self.submodules.int1 = ClockDomainsRenamer("spi")(Interpolator)( n_channels=16) # no cdc, assume timing is synchronous and comensurate such that # max data delay sys-spi < min sys-spi clock delay over all alignments body = Cat( self.int0.en_in, self.int1.en_in, self.int0.x, self.int1.x, ) self.sync.spi += [ # this should be comb but the stb path is long self.int0.stb_in.eq(self.frame.stb), self.int1.stb_in.eq(self.frame.stb), body.eq(self.frame.body[-len(body):]), self.int0.typ.eq(cfg_comb.typ), self.int1.typ.eq(cfg_comb.typ), ] self.submodules.spi = MultiSPI(platform) assert len(body) == len(self.spi.data) assert len(cfg) + len(adr) + len(self.spi.data) == len(self.frame.body) body = Cat( self.int0.en_out, self.int1.en_out, self.int0.y, self.int1.y, ) assert len(body) == len(self.spi.data) self.comb += [ self.spi.stb.eq(self.int0.stb_out & self.int0.cic.ce), self.spi.data.eq(body), # self.spi.data.eq(self.frame.body[-len(self.spi.data):]), # self.spi.stb.eq(self.frame.stb), ] self.comb += [ platform.request("dac_clr_n").eq(~cfg.dac_clr), platform.request("en_afe_pwr").eq(~cfg.afe_pwr_n), #platform.request("test_point", 2).eq(self.link.delay[0]), #platform.request("test_point", 3).eq(self.link.delay[1]), #platform.request("test_point", 3).eq(self.spi.busy), #platform.request("test_point", 4).eq(self.frame.stb), Cat(platform.request("user_led", i) for i in range(9)).eq( Cat( cfg.led, ResetSignal("spi") | have_align_err | have_crc_err, # RED )), ] self.specials += [ Instance( "SB_IO", p_PIN_TYPE=C(0b010000, 6), # output registered DDR p_IO_STANDARD="SB_LVCMOS", i_OUTPUT_CLK=ClockSignal("link"), #i_CLOCK_ENABLE=1, o_PACKAGE_PIN=platform.request("test_point", 0), i_D_OUT_0=1, i_D_OUT_1=0), Instance( "SB_IO", p_PIN_TYPE=C(0b010000, 6), # output registered DDR p_IO_STANDARD="SB_LVCMOS", i_OUTPUT_CLK=ClockSignal("word"), #i_CLOCK_ENABLE=1, o_PACKAGE_PIN=platform.request("test_point", 1), i_D_OUT_0=1, i_D_OUT_1=0), ]
def __init__(self, platform): clk48_raw = platform.request("clk48") clk12 = Signal() reset_delay = Signal(12, reset=4095) self.clock_domains.cd_por = ClockDomain() self.reset = Signal() self.clock_domains.cd_sys = ClockDomain() self.clock_domains.cd_usb_12 = ClockDomain() self.clock_domains.cd_usb_48 = ClockDomain() platform.add_period_constraint(self.cd_usb_48.clk, 1e9 / 48e6) platform.add_period_constraint(self.cd_sys.clk, 1e9 / 12e6) platform.add_period_constraint(self.cd_usb_12.clk, 1e9 / 12e6) platform.add_period_constraint(clk48_raw, 1e9 / 48e6) # POR reset logic- POR generated from sys clk, POR logic feeds sys clk # reset. self.comb += [ self.cd_por.clk.eq(self.cd_sys.clk), self.cd_sys.rst.eq(reset_delay != 0), self.cd_usb_12.rst.eq(reset_delay != 0), ] # POR reset logic- POR generated from sys clk, POR logic feeds sys clk # reset. self.comb += [ self.cd_usb_48.rst.eq(reset_delay != 0), ] self.comb += self.cd_usb_48.clk.eq(clk48_raw) self.specials += Instance( "SB_PLL40_CORE", # Parameters p_DIVR=0, p_DIVF=15, p_DIVQ=5, p_FILTER_RANGE=1, p_FEEDBACK_PATH="SIMPLE", p_DELAY_ADJUSTMENT_MODE_FEEDBACK="FIXED", p_FDA_FEEDBACK=15, p_DELAY_ADJUSTMENT_MODE_RELATIVE="FIXED", p_FDA_RELATIVE=0, p_SHIFTREG_DIV_MODE=1, p_PLLOUT_SELECT="GENCLK_HALF", p_ENABLE_ICEGATE=0, # IO i_REFERENCECLK=clk48_raw, o_PLLOUTCORE=clk12, # o_PLLOUTGLOBAL = clk12, #i_EXTFEEDBACK, #i_DYNAMICDELAY, #o_LOCK, i_BYPASS=0, i_RESETB=1, #i_LATCHINPUTVALUE, #o_SDO, #i_SDI, ) self.comb += self.cd_sys.clk.eq(clk12) self.comb += self.cd_usb_12.clk.eq(clk12) self.sync.por += \ If(reset_delay != 0, reset_delay.eq(reset_delay - 1) ) self.specials += AsyncResetSynchronizer(self.cd_por, self.reset)
def __init__( self, S=8, D=2, M=2, MIRROR_BITS=False, CLK_EDGE_ALIGNED=True, BITSLIPS=0, DCO_PERIOD=2.0, **kwargs ): """ Clock and data lanes must be in-phase (edge aligned) S = serialization factor (bits per frame) D = number of parallel lanes M = clock multiplier (bits per DCO period) 1 for sdr, 2 for ddr, higher for a divided clock BITSLIPS: Number of bitslips to trigger after initialization DCO_PERIOD = period of dco_p/n in [ns] for PLL_ADV MIRROR_BITS = False: first bit of the serial stream clocked in ends up in the LSB of data_outs See Sp6Common.py for input output ports """ # Data recovered from the clock lane for frame alignment (in case of a divided clock) self.clk_data_out = Signal(8) # High when sample clock is stable self.pll_locked = Signal() ### # Add data lanes and control signals Sp6Common.__init__( self, S, D, MIRROR_BITS, BITSLIPS, {"p_DATA_RATE": "SDR"}, {"p_DATA_RATE": "SDR"} ) # Sync resets self.specials += AsyncResetSynchronizer(self.sample, ~self.pll_locked) # ------------------------------------- # Iserdes PLL clocking scheme # ------------------------------------- # ... with clk-data recovery self.dco = Signal() self.specials += DifferentialInput(self.dco_p, self.dco_n, self.dco) dco_m = Signal() dco_s = Signal() self.specials += Instance( "IODELAY2", p_SERDES_MODE="MASTER", p_IDELAY_TYPE="VARIABLE_FROM_HALF_MAX" if CLK_EDGE_ALIGNED else "FIXED", i_IDATAIN=self.dco, i_CAL=self.idelay_cal_m, i_CE=0, i_INC=0, o_DATAOUT=dco_m, **self.idelay_default ) self.specials += Instance( "IODELAY2", p_SERDES_MODE="SLAVE", p_IDELAY_TYPE="FIXED" if CLK_EDGE_ALIGNED else "VARIABLE_FROM_HALF_MAX", i_IDATAIN=self.dco, # We don't want periodic calibration here, would lead to clock glitches i_CAL=self.idelay_cal_m, i_CE=0, i_INC=0, o_DATAOUT=dco_s, **self.idelay_default ) cascade_up = Signal() cascade_down = Signal() dfb = Signal() cfb0 = Signal() self.specials += Instance( "ISERDES2", p_SERDES_MODE="MASTER", i_D=dco_m, i_SHIFTIN=cascade_up, o_Q4=self.clk_data_out[7], o_Q3=self.clk_data_out[6], o_Q2=self.clk_data_out[5], o_Q1=self.clk_data_out[4], o_SHIFTOUT=cascade_down, **self.iserdes_default ) # Using the delay matched BUFIO2 and BUFIO2FB, # the PLL will generate a ioclock which is phase- # aligned with the dco clock at the input of the # ISERDES self.specials += Instance( "ISERDES2", p_SERDES_MODE="SLAVE", i_D=dco_s, i_SHIFTIN=cascade_down, o_Q4=self.clk_data_out[3], o_Q3=self.clk_data_out[2], o_Q2=self.clk_data_out[1], o_Q1=self.clk_data_out[0], o_SHIFTOUT=cascade_up, o_DFB=dfb, o_CFB0=cfb0, **self.iserdes_default ) pll_clkin = Signal() pll_clkfbin = Signal() self.specials += Instance( "BUFIO2", p_DIVIDE_BYPASS="******", i_I=dfb, o_DIVCLK=pll_clkin ) self.specials += Instance( "BUFIO2FB", p_DIVIDE_BYPASS="******", i_I=cfb0, o_O=pll_clkfbin ) pll_clk0 = Signal() pll_clk2 = Signal() self.specials += Instance( "PLL_ADV", name="PLL_IOCLOCK", p_BANDWIDTH="OPTIMIZED", p_SIM_DEVICE="SPARTAN6", p_CLKIN1_PERIOD=DCO_PERIOD, p_CLKIN2_PERIOD=DCO_PERIOD, p_DIVCLK_DIVIDE=1, p_CLKFBOUT_MULT=M, p_CLKFBOUT_PHASE=0.0, p_CLKOUT0_DIVIDE=1, p_CLKOUT2_DIVIDE=S, p_CLKOUT0_DUTY_CYCLE=0.5, p_CLKOUT2_DUTY_CYCLE=0.5, p_CLKOUT0_PHASE=0.0, p_CLKOUT2_PHASE=0.0, p_COMPENSATION="SOURCE_SYNCHRONOUS", # p_COMPENSATION="INTERNAL", p_CLK_FEEDBACK="CLKOUT0", i_RST=self.reset, i_CLKINSEL=1, i_CLKIN1=pll_clkin, i_CLKIN2=0, i_CLKFBIN=pll_clkfbin, # o_CLKFBOUT=clkfbout, i_CLKFBIN=clkfbout, i_DADDR=Signal(5), i_DI=Signal(16), i_DEN=0, i_DWE=0, i_DCLK=0, o_CLKOUT0=pll_clk0, o_CLKOUT2=pll_clk2, o_LOCKED=self.pll_locked ) self.specials += Instance( "BUFPLL", p_DIVIDE=S, i_PLLIN=pll_clk0, i_GCLK=ClockSignal("sample"), i_LOCKED=self.pll_locked, o_IOCLK=self.ioclk_p, o_SERDESSTROBE=self.serdesstrobe ) self.specials += Instance( "BUFG", i_I=pll_clk2, o_O=ClockSignal("sample") ) # ioclk_n is not needed, hardwire it to zero self.comb += self.ioclk_n.eq(0)
def __init__(self, clk, data, platform, t_clk=4.): self.n_div = 7 n_lanes = len(data) # output word self.word = Signal(n_lanes * self.n_div) # clock alignment error counter self.align_err = Signal(8) # currently tuned sampling delay self.delay = Signal(4, reset=0x8) # clock aligned, word available self.stb = Signal() # link clocking cd_link = ClockDomain("link", reset_less=True) # t_clk*7, 3:4 duty platform.add_period_constraint(cd_link.clk, t_clk * self.n_div) self.clock_domains += cd_link cd_word = ClockDomain("word") # t_clk*7, 1:1 duty platform.add_period_constraint(cd_word.clk, t_clk * self.n_div) self.clock_domains += cd_word cd_sr = ClockDomain("sr", reset_less=True) # t_clk self.clock_domains += cd_sr divr = 1 - 1 divf = 1 - 1 divq = 2 t_out = t_clk * (divr + 1) / (divf + 1) assert t_out == t_clk platform.add_period_constraint(cd_sr.clk, t_out) t_vco = t_out / 2**divq assert .533 <= 1 / t_vco <= 1.066, 1 / t_vco # input clock PLL locked = Signal() self.delay_relative = Signal(4) self.specials += [ Instance( "SB_PLL40_2F_CORE", p_FEEDBACK_PATH= "PHASE_AND_DELAY", # out = in/r*f, vco = out*2**q p_DIVR=divr, # input p_DIVF=divf, # feedback p_DIVQ=divq, # vco p_FILTER_RANGE=3, p_SHIFTREG_DIV_MODE=int(self.n_div == 7), # div-by-7 p_DELAY_ADJUSTMENT_MODE_FEEDBACK="DYNAMIC", p_FDA_FEEDBACK=0xf, p_DELAY_ADJUSTMENT_MODE_RELATIVE="DYNAMIC", p_FDA_RELATIVE=0xf, p_PLLOUT_SELECT_PORTA="SHIFTREG_0deg", p_PLLOUT_SELECT_PORTB="GENCLK", p_ENABLE_ICEGATE_PORTA=0, p_ENABLE_ICEGATE_PORTB=0, i_BYPASS=0, i_RESETB=1, i_DYNAMICDELAY=Cat(self.delay, self.delay_relative), i_REFERENCECLK=ClockSignal("link"), i_LATCHINPUTVALUE=0, o_LOCK=locked, o_PLLOUTGLOBALA=cd_word.clk, o_PLLOUTGLOBALB=cd_sr.clk, # o_PLLOUTCOREA=, # o_PLLOUTCOREB=, ), AsyncResetSynchronizer(cd_word, ~locked), ] # input PIO registers # 2 bits per lane, data lanes plus one clock lane self.submodules.sr = SlipSR(n_lanes=2 * (n_lanes + 1), n_div=self.n_div) helper = Signal(n_lanes + 1) self.specials += [ # buffer it to the `link` domain and DDR-sample it with the `sr` clock Instance( "SB_GB_IO", p_PIN_TYPE=0b000000, # no output, i registered p_IO_STANDARD="SB_LVDS_INPUT", i_INPUT_CLK=cd_sr.clk, o_D_IN_0=self.sr.data[0], # rising o_D_IN_1=helper[0], # falling o_GLOBAL_BUFFER_OUTPUT=cd_link.clk, i_PACKAGE_PIN=clk, ), # help relax timings a bit to meet the t_clk/2 delay from # negedge sr to posedge sr: easier met in fabric than right after # PIO Instance( "SB_DFFN", i_D=helper[0], i_C=cd_sr.clk, o_Q=self.sr.data[n_lanes + 1], ), ] + [ [ Instance( "SB_IO", p_PIN_TYPE=0b000000, p_IO_STANDARD="SB_LVDS_INPUT", i_INPUT_CLK=cd_sr.clk, o_D_IN_0=self.sr.data[i + 1], # rising o_D_IN_1=helper[i + 1], # falling i_PACKAGE_PIN=data[i], ), # relax timing closure on falling edge samples, # same as clk lane above Instance( "SB_DFFN", i_D=helper[i + 1], i_C=cd_sr.clk, o_Q=self.sr.data[n_lanes + i + 2], ) ] for i in range(n_lanes) ] # clock aligned slip_good = Signal() mean_edges = 1 << 6 sigma_edges = 6 * mean_edges**.5 assert mean_edges > sigma_edges # need SNR # number of edges seen, one word headroom edges = Signal(max=2 * mean_edges + len(self.sr.word)) # number of edge samples matching the later non-edge value # i.e. number of edge samples biased to late sampling edges_late = Signal.like(edges) # timer to block slips and delay adjustments while others are # pending/pll settling settle = Signal(max=64, reset=63) settle_done = Signal() n = len(self.sr.data) lanes = [Signal(self.n_div) for i in range(n)] self.comb += [ # transpose link word for delay checking Cat(lanes).eq(Cat(self.sr.word[i::n] for i in range(n))), self.word.eq( Cat(self.sr.word[i * n + 1:i * n + 1 + n_lanes] for i in range(self.n_div))), settle_done.eq(settle == 0), self.stb.eq(slip_good), # be robust, just look for rising clock edge between 1 and 2 slip_good.eq(lanes[0][1:3] == 0b01), ] self.sync.word += [ # delay adjustment: # (indices are bit indices, youngest first, # reversed from clock cycle numbers) # With the timing helper DFFNs on the edge samples (see # above): # edge sample i is half a cycle older than ref sample i # and half a cycle younger than ref sample i + 1 edges.eq(edges + sum(ref[i + 1] ^ ref[i] for ref in lanes[:n_lanes + 1] for i in range(self.n_div - 1))), edges_late.eq(edges_late + sum( (ref[i + 1] ^ ref[i]) & ~(edge[i] ^ ref[i]) for ref, edge in zip(lanes[:n_lanes + 1], lanes[n_lanes + 1:]) for i in range(self.n_div - 1))), self.sr.slip_req.eq(0), If( ~settle_done, settle.eq(settle - 1), ).Else( # slip adjustment and delay adjustment can happen in parallel # share a hold-off timer for convenience If( edges >= 2 * mean_edges, edges.eq(0), edges_late.eq(0), # many late samples If( (edges_late >= int(mean_edges + sigma_edges)) & # saturate delay (self.delay != 0xf), # if the edge sample matches the sample after the edge # then sampling is late: # increment the feedback delay for earlier sampling self.delay.eq(self.delay + 1), settle.eq(settle.reset), ), # few late samples If( (edges_late < int(mean_edges - sigma_edges)) & (self.delay != 0), self.delay.eq(self.delay - 1), settle.eq(settle.reset), ), ), If( ~slip_good, self.sr.slip_req.eq(1), self.align_err.eq(self.align_err + 1), settle.eq(settle.reset), )) ]