update ECP5 PLL to accept parameters for setting arbitrary clock frequencies
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 16 Feb 2022 12:30:20 +0000 (12:30 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 16 Feb 2022 12:30:20 +0000 (12:30 +0000)
src/crg.py
src/ls2.py

index eb3c651e558d194057cc143cf19a2858fdadd8cb..fd4566b87283722f6921d71a19d83091893fa77d 100644 (file)
-# Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
-# Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
 
-from nmigen import (Module, Elaboratable, Instance, Signal, ClockDomain,
-                    ClockSignal, ResetSignal)
+from nmigen import *
 
 __ALL__ = ["ECPIX5CRG"]
 
-
 class PLL(Elaboratable):
-    def __init__(self, clkin, clksel=Signal(shape=2, reset=2),
-                        clkout1=Signal(), clkout2=Signal(),
-                        clkout3=Signal(), clkout4=Signal(), lock=Signal(),
-                        CLKI_DIV=1, CLKFB_DIV=2, CLK1_DIV=3, CLK2_DIV=24):
+    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.clkout1 = clkout1
-        self.clkout2 = clkout2
-        self.clkout3 = clkout3
-        self.clkout4 = clkout4
+        self.clkin_freq = None
         self.clksel = clksel
-        self.lock = lock
-        self.CLKI_DIV = CLKI_DIV
-        self.CLKFB_DIV = CLKFB_DIV
-        self.CLKOP_DIV = CLK1_DIV
-        self.CLKOS_DIV = CLK2_DIV
-        self.ports = [
+        self.locked = locked
+        self.reset  = reset
+        self.nclkouts   = 0
+        self.clkouts    = {}
+        self.config     = {}
+        self.params     = {}
+
+    def ports(self):
+        return [
             self.clkin,
-            self.clkout1,
-            self.clkout2,
-            self.clkout3,
-            self.clkout4,
             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)
+        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)
+                    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 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()
+        pll = Instance("EHXPLLL", **self.params)
+        m.submodules.pll = pll
+        return m
+
         pll = Instance("EHXPLLL",
                        p_OUTDIVIDER_MUXA='DIVA',
                        p_OUTDIVIDER_MUXB='DIVB',
@@ -70,14 +150,11 @@ class PLL(Elaboratable):
                        o_CLKOS3=self.clkout4,
                        o_LOCK=self.lock,
                        )
-        m = Module()
-        m.submodules += pll
-        return m
 
 
 class ECPIX5CRG(Elaboratable):
-    def __init__(self):
-        ...
+    def __init__(self, sys_clk_freq=100e6):
+        self.sys_clk_freq = sys_clk_freq
 
     def elaborate(self, platform):
         m = Module()
@@ -94,11 +171,16 @@ class ECPIX5CRG(Elaboratable):
         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),
+            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)
@@ -110,16 +192,15 @@ class ECPIX5CRG(Elaboratable):
 
         # 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_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 = PLL(ClockSignal("rawclk"),
-                                     CLKI_DIV=1, CLKFB_DIV=2,
-                                     CLK1_DIV=3, CLK2_DIV=24,
-                                     clkout1=ClockSignal("sync2x_unbuf"),
-                                     clkout2=ClockSignal("init"))
+        m.submodules.pll = pll = PLL(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,
@@ -129,9 +210,11 @@ class ECPIX5CRG(Elaboratable):
         m.domains += cd_init
         m.domains += cd_sync
         m.domains += cd_dramsync
-        m.d.comb += ResetSignal("init").eq(~pll.lock|~pod_done)
-        m.d.comb += ResetSignal("sync").eq(~pll.lock|~pod_done)
-        m.d.comb += ResetSignal("dramsync").eq(~pll.lock|~pod_done)
+        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
 
index 7d948f6ba20308ac1569d9d5712b4ac4e1442809..cd3b4c8bd7c6066845d4c877d954ce3f68905299 100644 (file)
@@ -64,7 +64,7 @@ class DDR3SoC(SoC, Elaboratable):
             firmware = "firmware/main.bin"
 
         # set up clock request generator
-        self.crg = ECPIX5CRG()
+        self.crg = ECPIX5CRG(clk_freq)
 
         # set up CPU, with 64-to-32-bit downconverters
         self.cpu = ExternalCore(name="ext_core")
@@ -116,7 +116,7 @@ class DDR3SoC(SoC, Elaboratable):
         # DRAM Module
         if ddr_pins is not None:
             self.ddrphy = DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins,
-                                                    sys_clk_freq=100e6))
+                                                    sys_clk_freq=clk_freq))
             self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr)
 
             ddrmodule = MT41K256M16(clk_freq, "1:2") # match DDR3 ASIC P/N