From 28a6cb43060aff42585ff880755a0237cf235d6a Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Sun, 20 Mar 2022 09:56:46 +0000 Subject: [PATCH] beginnings of arty a7 clock-reset-generator --- src/arty_crg.py | 450 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 src/arty_crg.py diff --git a/src/arty_crg.py b/src/arty_crg.py new file mode 100644 index 0000000..58a5024 --- /dev/null +++ b/src/arty_crg.py @@ -0,0 +1,450 @@ +# Copyright (c) 2020 LambdaConcept +# Copyright (c) 2021 Luke Kenneth Casson Leighton +# Copyright (c) 2018-2020 Florent Kermarrec +# Copyright (c) 2019 Michael Betz +# +# Based on code from LambaConcept, from the gram example which is BSD-2-License +# https://github.com/jeanthom/gram/tree/master/examples +# +# Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER +# under EU Grants 871528 and 957073, under the LGPLv3+ License + +from nmigen import (Elaboratable, Module, Signal, ClockDomain, Instance, + ClockSignal, ResetSignal) + +__ALL__ = ["ArtyCRG"] + + +# Helpers ----------------------------------------------------------------- + +def clkdiv_range(start, stop, step=1): + start = float(start) + stop = float(stop) + step = float(step) + current = start + while current < stop: + yield int(current) if math.floor(current) == current else current + current += step + +# Xilinx / Generic ----------------------------------------------------- + +class XilinxClocking(Module): + clkfbout_mult_frange = (2, 64+1) + clkout_divide_range = (1, 128+1) + + def __init__(self, vco_margin=0): + self.vco_margin = vco_margin + self.reset = Signal() + self.locked = Signal() + self.clkin_freq = None + self.vcxo_freq = None + self.nclkouts = 0 + self.clkouts = {} + self.config = {} + self.params = {} + + def register_clkin(self, clkin, freq): + self.clkin = Signal() + if isinstance(clkin, (Signal, ClockSignal)): + self.comb += self.clkin.eq(clkin) + elif isinstance(clkin, Record): + self.specials += DifferentialInput(clkin.p, clkin.n, self.clkin) + else: + raise ValueError + self.clkin_freq = freq + register_clkin_log(self.logger, clkin, freq) + + def create_clkout(self, cd, freq, phase=0, buf="bufg", + margin=1e-2, with_reset=True, ce=None): + assert self.nclkouts < self.nclkouts_max + clkout = Signal() + self.clkouts[self.nclkouts] = (clkout, freq, phase, margin) + if with_reset: + self.specials += AsyncResetSynchronizer(cd, ~self.locked | + self.reset) + if buf is None: + self.comb += cd.clk.eq(clkout) + else: + clkout_buf = Signal() + self.comb += cd.clk.eq(clkout_buf) + if buf == "bufg": + self.specials += Instance("BUFG", i_I=clkout, o_O=clkout_buf) + elif buf == "bufr": + self.specials += Instance("BUFR", i_I=clkout, o_O=clkout_buf) + elif buf == "bufgce": + if ce is None: + raise ValueError("BUFGCE requires user to provide " + "a clock enable ce Signal") + self.specials += Instance("BUFGCE", i_I=clkout, + o_O=clkout_buf, i_CE=ce) + elif buf == "bufio": + self.specials += Instance("BUFIO", i_I=clkout, + o_O=clkout_buf) + else: + raise ValueError("Unsupported clock buffer: {}".format(buf)) + create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) + self.nclkouts += 1 + assert hasattr(self, "clkin") + + def compute_config(self): + config = {} + print ("compute_config", self.divclk_divide_range) + print ("mult", self.clkfbout_mult_frange) + print ("divrange", self.clkout_divide_range) + for divclk_divide in range(*self.divclk_divide_range): + config["divclk_divide"] = divclk_divide + for clkfbout_mult in reversed(range(*self.clkfbout_mult_frange)): + all_valid = True + vco_freq = self.clkin_freq*clkfbout_mult/divclk_divide + (vco_freq_min, vco_freq_max) = self.vco_freq_range + if (vco_freq >= vco_freq_min*(1 + self.vco_margin) and + vco_freq <= vco_freq_max*(1 - self.vco_margin)): + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + valid = False + d_ranges = [self.clkout_divide_range] + r = getattr(self, "clkout%d_divide_range" % n, None) + if r is not None: + d_ranges += [r] + for d_range in d_ranges: + for d in clkdiv_range(*d_range): + clk_freq = vco_freq/d + if abs(clk_freq - f) <= f*m: + config["clkout%d_freq" % n] = clk_freq + config["clkout%d_divide" % n] = d + config["clkout%d_phase" % n] = p + valid = True + break + if valid: + break + if not valid: + all_valid = False + else: + all_valid = False + if all_valid: + config["vco"] = vco_freq + config["clkfbout_mult"] = clkfbout_mult + compute_config_log(self.logger, config) + return config + raise ValueError("No PLL config found") + + +# Xilinx / 7-Series -------------------------------------------------------- + +class S7PLL(XilinxClocking): + nclkouts_max = 6 + clkin_freq_range = (19e6, 800e6) + + def __init__(self, speedgrade=-1): + #self.logger = logging.getLogger("S7PLL") + print ("Creating S7PLL, speedgrade %d" % speedgrade) + XilinxClocking.__init__(self) + self.divclk_divide_range = (1, 56+1) + self.vco_freq_range = { + -1: (800e6, 1600e6), + -2: (800e6, 1866e6), + -3: (800e6, 2133e6), + }[speedgrade] + + def do_finalize(self): + XilinxClocking.do_finalize(self) + config = self.compute_config() + pll_fb = Signal() + self.params.update( + p_STARTUP_WAIT="FALSE", o_LOCKED=self.locked, i_RST=self.reset, + + # VCO + p_REF_JITTER1=0.01, p_CLKIN1_PERIOD=1e9/self.clkin_freq, + p_CLKFBOUT_MULT=config["clkfbout_mult"], + p_DIVCLK_DIVIDE=config["divclk_divide"], + i_CLKIN1=self.clkin, i_CLKFBIN=pll_fb, o_CLKFBOUT=pll_fb, + ) + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + self.params["p_CLKOUT{}_DIVIDE".format(n)] = \ + config["clkout{}_divide".format(n)] + self.params["p_CLKOUT{}_PHASE".format(n)] = \ + config["clkout{}_phase".format(n)] + self.params["o_CLKOUT{}".format(n)] = clk + self.specials += Instance("PLLE2_ADV", **self.params) + + + +class ECP5PLL(Elaboratable): + nclkouts_max = 3 + clki_div_range = (1, 128+1) + clkfb_div_range = (1, 128+1) + clko_div_range = (1, 128+1) + clki_freq_range = ( 8e6, 400e6) + clko_freq_range = (3.125e6, 400e6) + vco_freq_range = ( 400e6, 800e6) + + def __init__(self, clkin, + clksel=Signal(shape=2, reset=2), + reset=Signal(reset_less=True), + locked=Signal()): + self.clkin = clkin + self.clkin_freq = None + self.clksel = clksel + self.locked = locked + self.reset = reset + self.nclkouts = 0 + self.clkouts = {} + self.config = {} + self.params = {} + + def ports(self): + return [ + self.clkin, + self.clksel, + self.lock, + ] + list(self.clkouts.values()) + + def set_clkin_freq(self, freq): + (clki_freq_min, clki_freq_max) = self.clki_freq_range + assert freq >= clki_freq_min + assert freq <= clki_freq_max + self.clkin_freq = freq + + def create_clkout(self, cd, freq, phase=0, margin=1e-2): + (clko_freq_min, clko_freq_max) = self.clko_freq_range + assert freq >= clko_freq_min + assert freq <= clko_freq_max + assert self.nclkouts < self.nclkouts_max + self.clkouts[self.nclkouts] = (cd, freq, phase, margin) + #create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) + print("clock domain", cd.domain, freq, margin, self.nclkouts) + self.nclkouts += 1 + + def compute_config(self): + config = {} + for clki_div in range(*self.clki_div_range): + config["clki_div"] = clki_div + for clkfb_div in range(*self.clkfb_div_range): + all_valid = True + vco_freq = self.clkin_freq/clki_div*clkfb_div*1 # clkos3_div=1 + (vco_freq_min, vco_freq_max) = self.vco_freq_range + if vco_freq >= vco_freq_min and vco_freq <= vco_freq_max: + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + valid = False + for d in range(*self.clko_div_range): + clk_freq = vco_freq/d + if abs(clk_freq - f) <= f*m: + config["clko{}_freq".format(n)] = clk_freq + config["clko{}_div".format(n)] = d + config["clko{}_phase".format(n)] = p + valid = True + break + if not valid: + all_valid = False + else: + all_valid = False + if all_valid: + config["vco"] = vco_freq + config["clkfb_div"] = clkfb_div + #compute_config_log(self.logger, config) + print ("PLL config", config) + return config + raise ValueError("No PLL config found") + + def elaborate(self, platform): + config = self.compute_config() + clkfb = Signal() + self.params.update( + # attributes + a_FREQUENCY_PIN_CLKI = str(self.clkin_freq/1e6), + a_ICP_CURRENT = "6", + a_LPF_RESISTOR = "16", + a_MFG_ENABLE_FILTEROPAMP = "1", + a_MFG_GMCREF_SEL = "2", + # parameters + p_FEEDBK_PATH = "INT_OS3", # CLKOS3 rsvd for feedback with div=1. + p_CLKOS3_ENABLE = "ENABLED", + p_CLKOS3_DIV = 1, + p_CLKFB_DIV = config["clkfb_div"], + p_CLKI_DIV = config["clki_div"], + # reset, input clock, lock-achieved output + i_RST = self.reset, + i_CLKI = self.clkin, + o_LOCK = self.locked, + ) + # for each clock-out, set additional parameters + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + n_to_l = {0: "P", 1: "S", 2: "S2"} + div = config["clko{}_div".format(n)] + cphase = int(p*(div + 1)/360 + div) + self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED" + self.params["p_CLKO{}_DIV".format(n_to_l[n])] = div + self.params["p_CLKO{}_FPHASE".format(n_to_l[n])] = 0 + self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase + self.params["o_CLKO{}".format(n_to_l[n])] = clk + + m = Module() + print ("params", self.params) + pll = Instance("EHXPLLL", **self.params) + m.submodules.pll = pll + return m + + pll = Instance("EHXPLLL", + p_OUTDIVIDER_MUXA='DIVA', + p_OUTDIVIDER_MUXB='DIVB', + p_CLKOP_ENABLE='ENABLED', + p_CLKOS_ENABLE='ENABLED', + p_CLKOS2_ENABLE='DISABLED', + p_CLKOS3_ENABLE='DISABLED', + p_CLKOP_DIV=self.CLKOP_DIV, + p_CLKOS_DIV=self.CLKOS_DIV, + p_CLKFB_DIV=self.CLKFB_DIV, + p_CLKI_DIV=self.CLKI_DIV, + p_FEEDBK_PATH='INT_OP', + p_CLKOP_TRIM_POL="FALLING", + p_CLKOP_TRIM_DELAY=0, + p_CLKOS_TRIM_POL="FALLING", + p_CLKOS_TRIM_DELAY=0, + i_CLKI=self.clkin, + i_RST=0, + i_STDBY=0, + i_PHASESEL0=0, + i_PHASESEL1=0, + i_PHASEDIR=0, + i_PHASESTEP=0, + i_PHASELOADREG=0, + i_PLLWAKESYNC=0, + i_ENCLKOP=1, + i_ENCLKOS=1, + i_ENCLKOS2=0, + i_ENCLKOS3=0, + o_CLKOP=self.clkout1, + o_CLKOS=self.clkout2, + o_CLKOS2=self.clkout3, + o_CLKOS3=self.clkout4, + o_LOCK=self.lock, + ) + +# CRG ---------------------------------------------------------------- + +class ArtyA7CRG(Elaboratable): + def __init__(self, sys_clk_freq): + self.sys_clk_freq = sys_clk_freq + + def elaborate(self, platform): + m = Module() + + # Get 100Mhz from oscillator + clk100 = platform.request("clk100") + cd_rawclk = ClockDomain("rawclk", local=True, reset_less=True) + m.d.comb += cd_rawclk.clk.eq(clk100) + m.domains += cd_rawclk + + sync = ClockDomain("sync") + #sync2x = ClockDomain("sync2x", reset_less=True) + #sync4x = ClockDomain("sync4x", reset_less=True) + #sync4x_dqs = ClockDomain("sync4x_dqs", reset_less=True) + #cd_clk200 = ClockDomain("cd_clk200") + #cd_eth = ClockDomain("cd_eth") + dramsync = ClockDomain("dramsync") + + m.domains += sync + #m.domains += sync2x + #m.domains += sync4x + #m.domains += sync4x_dqs + #m.domains += cd_clk200 + #m.domains += cd_eth + m.domains += dramsync + + clk100_ibuf = Signal() + clk100_buf = Signal() + m.submodules += [ + Instance("IBUF", i_I=ckl100, o_O=clk100_ibuf), + Instance("BUFG", i_I=clk100_ibuf, o_O=clk100_buf) + ] + + self.submodules.pll = pll = S7PLL(speedgrade=-1) + reset = platform.request(platform.default_rst).i + self.comb += pll.reset.eq(~reset) + pll.register_clkin(clk100_buf, 100e6) + pll.create_clkout(self.sync, sys_clk_freq) + + platform.add_period_constraint(clk100_buf, 1e9/100e6) + platform.add_period_constraint(sync.clk, 1e9/sys_clk_freq) + platform.add_false_path_constraints(clk100_buf, sync.clk) + + # temporarily set dram sync clock exactly equal to main sync + m.d.comb += ClockSignal("dramsync").eq(ClockSignal("sync")) + + +class ECPIX5CRG(Elaboratable): + def __init__(self, sys_clk_freq=100e6): + self.sys_clk_freq = sys_clk_freq + + def elaborate(self, platform): + m = Module() + + # Get 100Mhz from oscillator + clk100 = platform.request("clk100") + cd_rawclk = ClockDomain("rawclk", local=True, reset_less=True) + m.d.comb += cd_rawclk.clk.eq(clk100) + m.domains += cd_rawclk + + # Reset + reset = platform.request(platform.default_rst).i + gsr0 = Signal() + gsr1 = Signal() + + m.submodules += [ + Instance("FD1S3AX", p_GSR="DISABLED", + i_CK=ClockSignal("rawclk"), + i_D=~reset, + o_Q=gsr0), + Instance("FD1S3AX", p_GSR="DISABLED", + i_CK=ClockSignal("rawclk"), + i_D=gsr0, + o_Q=gsr1), + Instance("SGSR", i_CLK=ClockSignal("rawclk"), + i_GSR=gsr1), + ] + + # Power-on delay (655us) + podcnt = Signal(3, reset=-1) + pod_done = Signal() + with m.If(podcnt != 0): + m.d.rawclk += podcnt.eq(podcnt-1) + m.d.rawclk += pod_done.eq(podcnt == 0) + + # Generating sync2x (200Mhz) and init (25Mhz) from clk100 + cd_sync2x = ClockDomain("sync2x", local=False) + cd_sync2x_unbuf = ClockDomain("sync2x_unbuf", + local=False, reset_less=True) + cd_init = ClockDomain("init", local=False) + cd_sync = ClockDomain("sync", local=False) + cd_dramsync = ClockDomain("dramsync", local=False) + m.submodules.pll = pll = ECP5PLL(ClockSignal("rawclk"), reset=~reset) + pll.set_clkin_freq(100e6) + pll.create_clkout(ClockSignal("sync2x_unbuf"), 2*self.sys_clk_freq) + pll.create_clkout(ClockSignal("init"), 25e6) + m.submodules += Instance("ECLKSYNCB", + i_ECLKI = ClockSignal("sync2x_unbuf"), + i_STOP = 0, + o_ECLKO = ClockSignal("sync2x")) + m.domains += cd_sync2x_unbuf + m.domains += cd_sync2x + m.domains += cd_init + m.domains += cd_sync + m.domains += cd_dramsync + reset_ok = Signal(reset_less=True) + m.d.comb += reset_ok.eq(~pll.locked|~pod_done) + m.d.comb += ResetSignal("init").eq(reset_ok) + m.d.comb += ResetSignal("sync").eq(reset_ok) + m.d.comb += ResetSignal("dramsync").eq(reset_ok) + + # # Generating sync (100Mhz) from sync2x + + m.submodules += Instance("CLKDIVF", + p_DIV="2.0", + i_ALIGNWD=0, + i_CLKI=ClockSignal("sync2x"), + i_RST=0, + o_CDIVX=ClockSignal("sync")) + + # temporarily set dram sync clock exactly equal to main sync + m.d.comb += ClockSignal("dramsync").eq(ClockSignal("sync")) + + return m -- 2.30.2