beginnings of arty a7 clock-reset-generator
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 20 Mar 2022 09:56:46 +0000 (09:56 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 20 Mar 2022 09:56:46 +0000 (09:56 +0000)
src/arty_crg.py [new file with mode: 0644]

diff --git a/src/arty_crg.py b/src/arty_crg.py
new file mode 100644 (file)
index 0000000..58a5024
--- /dev/null
@@ -0,0 +1,450 @@
+# Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
+# Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# Copyright (c) 2018-2020 Florent Kermarrec <florent@enjoy-digital.fr>
+# Copyright (c) 2019 Michael Betz <michibetz@gmail.com>
+#
+# 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