soc/cores/clock: add initial AlteraClocking/CycloneIV support.
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Tue, 7 Apr 2020 14:59:53 +0000 (16:59 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Tue, 7 Apr 2020 14:59:53 +0000 (16:59 +0200)
litex/soc/cores/clock.py
test/test_clock.py

index d264e201e436fb33fbd65437bc089418abc6bf8f..ed4161cf0cffa47112686be01b943f2767f6a498 100644 (file)
@@ -698,3 +698,126 @@ class ECP5PLL(Module):
             self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase
             self.params["o_CLKO{}".format(n_to_l[n])]        = clk
         self.specials += Instance("EHXPLLL", **self.params)
+
+# Altera / Generic ---------------------------------------------------------------------------------
+
+class AlteraClocking(Module, AutoCSR):
+    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, margin=1e-2, with_reset=True):
+        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)
+        self.comb += cd.clk.eq(clkout)
+        create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts)
+        self.nclkouts += 1
+
+    def compute_config(self):
+        config = {}
+        for n in range(*self.n_div_range):
+            config["n"] = n
+            for m in reversed(range(*self.m_div_range)):
+                all_valid = True
+                vco_freq = self.clkin_freq*m/n
+                (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
+                        for c in clkdiv_range(*self.c_div_range):
+                            clk_freq = vco_freq/c
+                            if abs(clk_freq - f) <= f*_m:
+                                config["clk{}_freq".format(_n)]   = clk_freq
+                                config["clk{}_divide".format(_n)] = c
+                                config["clk{}_phase".format(_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["m"]   = m
+                    compute_config_log(self.logger, config)
+                    return config
+        raise ValueError("No PLL config found")
+
+    def do_finalize(self):
+        assert hasattr(self, "clkin")
+        config = self.compute_config()
+        clks = Signal(self.nclkouts)
+        self.params.update(
+            p_BANDWIDTH_TYPE         = "AUTO",
+            p_COMPENSATE_CLOCK       = "CLK0",
+            p_INCLK0_INPUT_FREQUENCY = int(1e12/self.clkin_freq),
+            p_OPERATION_MODE         = "NORMAL",
+            i_INCLK                  = self.clkin,
+            o_CLK                    = clks,
+            i_ARESET                 = 0,
+            i_CLKENA                 = 2**self.nclkouts_max - 1,
+            i_EXTCLKENA              = 0xf,
+            i_FBIN                   = 1,
+            i_PFDENA                 = 1,
+            i_PLLENA                 = 1,
+            o_LOCKED                 = self.locked,
+        )
+        for n, (clk, f, p, m) in sorted(self.clkouts.items()):
+            clk_phase_ps = int((1e12/config["clk{}_freq".format(n)])*config["clk{}_phase".format(n)]/360)
+            self.params["p_CLK{}_DIVIDE_BY".format(n)]   = config["clk{}_divide".format(n)]
+            self.params["p_CLK{}_DUTY_CYCLE".format(n)]  = 50
+            self.params["p_CLK{}_MULTIPLY_BY".format(n)] = config["m"]
+            self.params["p_CLK{}_PHASE_SHIFT".format(n)] = clk_phase_ps
+            self.comb += clk.eq(clks[n])
+        self.specials += Instance("ALTPLL", **self.params)
+
+# Altera / CycloneIV -------------------------------------------------------------------------------
+
+class CycloneIVPLL(AlteraClocking):
+    nclkouts_max   = 5
+    n_div_range    = (1, 512+1)
+    m_div_range    = (1, 512+1)
+    c_div_range    = (1, 512+1)
+    vco_freq_range = (600e6, 1300e6)
+    def __init__(self, speedgrade="-6"):
+        self.logger = logging.getLogger("CycloneIVPLL")
+        self.logger.info("Creating CycloneIVPLL, {}.".format(colorer("speedgrade {}".format(speedgrade))))
+        AlteraClocking.__init__(self)
+        self.clkin_freq_range = {
+            "-6" : (5e6, 472.5e6),
+            "-7" : (5e6, 472.5e6),
+            "-8" : (5e6, 472.5e6),
+            "-8L": (5e6, 362e6),
+            "-9L": (5e6, 256e6),
+        }[speedgrade]
+        self.clko_freq_range = {
+            "-6" : (0, 472.5e6),
+            "-7" : (0, 450e6),
+            "-8" : (0, 402.5e6),
+            "-8L": (0, 362e6),
+            "-9L": (0, 265e6),
+        }[speedgrade]
index 630d360899ca96fe4548fb7d7d14a7bfbac0ebde..9762da7c8075d6943626d2633f99b36e96fa405b 100644 (file)
@@ -67,3 +67,11 @@ class TestClock(unittest.TestCase):
         for i in range(pll.nclkouts_max):
             pll.create_clkout(ClockDomain("clkout{}".format(i)), 200e6)
         pll.compute_config()
+
+    # Altera / CycloneIV
+    def test_cycloneivpll(self):
+        pll = CycloneIVPLL()
+        pll.register_clkin(Signal(), 50e6)
+        for i in range(pll.nclkouts_max):
+            pll.create_clkout(ClockDomain("clkout{}".format(i)), 100e6)
+        pll.compute_config()