Separate nest and core clocks
[ls2.git] / src / ecp5_crg.py
1 # Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
2 # Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
3 # Copyright (c) 2018-2020 Florent Kermarrec <florent@enjoy-digital.fr>
4 # Copyright (c) 2019 Michael Betz <michibetz@gmail.com>
5 # Copyright (c) 2022 Raptor Engineering, LLC <support@raptorengineering.com>
6 #
7 # Based on code from LambaConcept, from the gram example which is BSD-2-License
8 # https://github.com/jeanthom/gram/tree/master/examples
9 #
10 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
11 # under EU Grants 871528 and 957073, under the LGPLv3+ License
12
13
14 from nmigen import (Elaboratable, Module, Signal, ClockDomain, Instance,
15 ClockSignal, ResetSignal, Const)
16
17 __all__ = ["ECP5CRG"]
18
19
20 class PLL(Elaboratable):
21 nclkouts_max = 3
22 clki_div_range = (1, 128+1)
23 clkfb_div_range = (1, 128+1)
24 clko_div_range = (1, 128+1)
25 clki_freq_range = ( 8e6, 400e6)
26 clko_freq_range = (3.125e6, 400e6)
27 vco_freq_range = ( 400e6, 800e6)
28
29 def __init__(self, clkin,
30 clksel=Signal(shape=2, reset=2),
31 reset=Signal(reset_less=True),
32 locked=Signal()):
33 self.clkin = clkin
34 self.clkin_freq = None
35 self.clksel = clksel
36 self.locked = locked
37 self.reset = reset
38 self.nclkouts = 0
39 self.clkouts = {}
40 self.config = {}
41 self.params = {}
42
43 def ports(self):
44 return [
45 self.clkin,
46 self.clksel,
47 self.lock,
48 ] + list(self.clkouts.values())
49
50 def set_clkin_freq(self, freq):
51 (clki_freq_min, clki_freq_max) = self.clki_freq_range
52 assert freq >= clki_freq_min
53 assert freq <= clki_freq_max
54 self.clkin_freq = freq
55
56 def create_clkout(self, cd, freq, phase=0, margin=1e-2):
57 (clko_freq_min, clko_freq_max) = self.clko_freq_range
58 assert freq >= clko_freq_min
59 assert freq <= clko_freq_max
60 assert self.nclkouts < self.nclkouts_max
61 self.clkouts[self.nclkouts] = (cd, freq, phase, margin)
62 #create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts)
63 print("clock domain", cd.domain, freq, margin, self.nclkouts)
64 self.nclkouts += 1
65
66 def compute_config(self):
67 config = {}
68 for clki_div in range(*self.clki_div_range):
69 config["clki_div"] = clki_div
70 for clkfb_div in range(*self.clkfb_div_range):
71 all_valid = True
72 vco_freq = self.clkin_freq/clki_div*clkfb_div*1 # clkos3_div=1
73 (vco_freq_min, vco_freq_max) = self.vco_freq_range
74 if vco_freq >= vco_freq_min and vco_freq <= vco_freq_max:
75 for n, (clk, f, p, m) in sorted(self.clkouts.items()):
76 valid = False
77 for d in range(*self.clko_div_range):
78 clk_freq = vco_freq/d
79 if abs(clk_freq - f) <= f*m:
80 config["clko{}_freq".format(n)] = clk_freq
81 config["clko{}_div".format(n)] = d
82 config["clko{}_phase".format(n)] = p
83 valid = True
84 break
85 if not valid:
86 all_valid = False
87 else:
88 all_valid = False
89 if all_valid:
90 config["vco"] = vco_freq
91 config["clkfb_div"] = clkfb_div
92 #compute_config_log(self.logger, config)
93 print ("PLL config", config)
94 return config
95 raise ValueError("No PLL config found")
96
97 def elaborate(self, platform):
98 config = self.compute_config()
99 clkfb = Signal()
100 self.params.update(
101 # attributes
102 a_FREQUENCY_PIN_CLKI = str(self.clkin_freq/1e6),
103 a_ICP_CURRENT = "6",
104 a_LPF_RESISTOR = "16",
105 a_MFG_ENABLE_FILTEROPAMP = "1",
106 a_MFG_GMCREF_SEL = "2",
107 # parameters
108 p_FEEDBK_PATH = "INT_OS3", # CLKOS3 rsvd for feedback with div=1.
109 p_CLKOS3_ENABLE = "ENABLED",
110 p_CLKOS3_DIV = 1,
111 p_CLKFB_DIV = config["clkfb_div"],
112 p_CLKI_DIV = config["clki_div"],
113 # reset, input clock, lock-achieved output
114 i_RST = self.reset,
115 i_CLKI = self.clkin,
116 o_LOCK = self.locked,
117 )
118 # for each clock-out, set additional parameters
119 for n, (clk, f, p, m) in sorted(self.clkouts.items()):
120 n_to_l = {0: "P", 1: "S", 2: "S2"}
121 div = config["clko{}_div".format(n)]
122 cphase = int(p*(div + 1)/360 + div)
123 self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED"
124 self.params["p_CLKO{}_DIV".format(n_to_l[n])] = div
125 self.params["p_CLKO{}_FPHASE".format(n_to_l[n])] = 0
126 self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase
127 self.params["o_CLKO{}".format(n_to_l[n])] = clk
128
129 m = Module()
130 print ("params", self.params)
131 pll = Instance("EHXPLLL", **self.params)
132 m.submodules.pll = pll
133 return m
134
135 pll = Instance("EHXPLLL",
136 p_OUTDIVIDER_MUXA='DIVA',
137 p_OUTDIVIDER_MUXB='DIVB',
138 p_CLKOP_ENABLE='ENABLED',
139 p_CLKOS_ENABLE='ENABLED',
140 p_CLKOS2_ENABLE='DISABLED',
141 p_CLKOS3_ENABLE='DISABLED',
142 p_CLKOP_DIV=self.CLKOP_DIV,
143 p_CLKOS_DIV=self.CLKOS_DIV,
144 p_CLKFB_DIV=self.CLKFB_DIV,
145 p_CLKI_DIV=self.CLKI_DIV,
146 p_FEEDBK_PATH='INT_OP',
147 p_CLKOP_TRIM_POL="FALLING",
148 p_CLKOP_TRIM_DELAY=0,
149 p_CLKOS_TRIM_POL="FALLING",
150 p_CLKOS_TRIM_DELAY=0,
151 i_CLKI=self.clkin,
152 i_RST=0,
153 i_STDBY=0,
154 i_PHASESEL0=0,
155 i_PHASESEL1=0,
156 i_PHASEDIR=0,
157 i_PHASESTEP=0,
158 i_PHASELOADREG=0,
159 i_PLLWAKESYNC=0,
160 i_ENCLKOP=1,
161 i_ENCLKOS=1,
162 i_ENCLKOS2=0,
163 i_ENCLKOS3=0,
164 o_CLKOP=self.clkout1,
165 o_CLKOS=self.clkout2,
166 o_CLKOS2=self.clkout3,
167 o_CLKOS3=self.clkout4,
168 o_LOCK=self.lock,
169 )
170
171
172 class ECP5CRG(Elaboratable):
173 def __init__(self, sys_clk_freq=100e6, core_clk_freq=100e6, pod_bits=25):
174 self.sys_clk_freq = sys_clk_freq
175 self.core_clk_freq = core_clk_freq
176 self.pod_bits = pod_bits
177
178 # DDR clock control signals
179 self.ddr_clk_stop = Signal()
180 self.ddr_clk_reset = Signal()
181
182 def elaborate(self, platform):
183 m = Module()
184
185 # Get 100Mhz from oscillator
186 extclk = platform.request(platform.default_clk)
187 cd_rawclk = ClockDomain("rawclk", local=True, reset_less=True)
188 m.d.comb += cd_rawclk.clk.eq(extclk)
189 m.domains += cd_rawclk
190
191 # Reset
192 if platform.default_rst is not None:
193 reset = platform.request(platform.default_rst).i
194 else:
195 reset = Const(0) # whoops
196
197 gsr0 = Signal()
198 gsr1 = Signal()
199
200 m.submodules += [
201 Instance("FD1S3AX", p_GSR="DISABLED",
202 i_CK=ClockSignal("rawclk"),
203 i_D=~reset,
204 o_Q=gsr0),
205 Instance("FD1S3AX", p_GSR="DISABLED",
206 i_CK=ClockSignal("rawclk"),
207 i_D=gsr0,
208 o_Q=gsr1),
209 Instance("SGSR", i_CLK=ClockSignal("rawclk"),
210 i_GSR=gsr1),
211 ]
212
213 # Power-on delay
214 podcnt = Signal(self.pod_bits, reset=-1)
215 pod_done = Signal()
216 with m.If(podcnt != 0):
217 m.d.rawclk += podcnt.eq(podcnt-1)
218 m.d.rawclk += pod_done.eq(podcnt == 0)
219
220 # PLL
221 m.submodules.pll = pll = PLL(ClockSignal("rawclk"), reset=~pod_done|~reset)
222
223 # Generating sync2x (200Mhz) and init (25Mhz) from extclk
224 # sync is our "nest" frequency, cpu is the core frequency
225 # On ASIC, core >= nest, on FPGA, cpu <= nest...
226 cd_sync2x = ClockDomain("sync2x", local=False)
227 cd_sync2x_unbuf = ClockDomain("sync2x_unbuf",
228 local=False, reset_less=True)
229 cd_init = ClockDomain("init", local=False)
230 cd_sync = ClockDomain("sync", local=False)
231 cd_cpu = ClockDomain("cpu", local=False)
232 cd_dramsync = ClockDomain("dramsync", local=False)
233
234 # create PLL clocks
235 pll.set_clkin_freq(platform.default_clk_frequency)
236 pll.create_clkout(ClockSignal("sync2x_unbuf"), 2*self.sys_clk_freq)
237 pll.create_clkout(ClockSignal("init"), 25e6)
238 if self.sys_clk_freq == self.core_clk_freq:
239 m.d.comb += ClockSignal("cpu").eq(ClockSignal("sync"))
240 else:
241 pll.create_clkout(ClockSignal("cpu"), self.core_clk_freq)
242 m.submodules += Instance("ECLKSYNCB",
243 i_ECLKI = ClockSignal("sync2x_unbuf"),
244 i_STOP = self.ddr_clk_stop,
245 o_ECLKO = ClockSignal("sync2x"))
246 m.domains += cd_sync2x_unbuf
247 m.domains += cd_sync2x
248 m.domains += cd_init
249 m.domains += cd_sync
250 m.domains += cd_cpu
251 m.domains += cd_dramsync
252 reset_ok = Signal(reset_less=True)
253 m.d.comb += reset_ok.eq(~pll.locked|~pod_done)
254 m.d.comb += ResetSignal("init").eq(reset_ok)
255 m.d.comb += ResetSignal("sync").eq(reset_ok)
256 m.d.comb += ResetSignal("cpu").eq(reset_ok)
257 m.d.comb += ResetSignal("dramsync").eq(reset_ok|self.ddr_clk_reset)
258
259 # # Generating sync (100Mhz) from sync2x
260
261 m.submodules += Instance("CLKDIVF",
262 p_DIV="2.0",
263 i_ALIGNWD=0,
264 i_CLKI=ClockSignal("sync2x"),
265 i_RST=ResetSignal("dramsync"),
266 o_CDIVX=ClockSignal("sync"))
267
268 # temporarily set dram sync clock exactly equal to main sync
269 m.d.comb += ClockSignal("dramsync").eq(ClockSignal("sync"))
270
271 return m
272