replace the simulation Clock-Reset-Generator with one that is more general.
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 24 Feb 2022 17:55:05 +0000 (17:55 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 24 Feb 2022 17:55:05 +0000 (17:55 +0000)
the icarus verilog simulation now passes where previously it did not

gram/simulation/crg.py

index deab928d7661530fe3ef861145d657673d86b035..aa44c37954ea3c4c5cc643da41d83805873c17fd 100644 (file)
-# This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
+# 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 *
+
+from nmigen import (Elaboratable, Module, Signal, ClockDomain, Instance,
+                    ClockSignal, ResetSignal)
 
 __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)
+        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',
@@ -64,14 +165,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()
@@ -88,13 +186,20 @@ 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)
-        podcnt = Signal(3, reset=2**3-1)
+        podcnt = Signal(3, reset=-1)
         pod_done = Signal()
         with m.If(podcnt != 0):
             m.d.rawclk += podcnt.eq(podcnt-1)
@@ -102,12 +207,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=1, CLK2_DIV=4,
-            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,
@@ -117,18 +225,22 @@ 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
-        
+
         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