move 2x-clock-and-dividing into separate function in ECP5 CRG
[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 #
6 # Based on code from LambaConcept, from the gram example which is BSD-2-License
7 # https://github.com/jeanthom/gram/tree/master/examples
8 #
9 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
10 # under EU Grants 871528 and 957073, under the LGPLv3+ License
11
12
13 from nmigen import (Elaboratable, Module, Signal, ClockDomain, Instance,
14 ClockSignal, ResetSignal, Const)
15
16 __all__ = ["ECP5CRG"]
17
18
19 class PLL(Elaboratable):
20 nclkouts_max = 3
21 clki_div_range = (1, 128+1)
22 clkfb_div_range = (1, 128+1)
23 clko_div_range = (1, 128+1)
24 clki_freq_range = ( 8e6, 400e6)
25 clko_freq_range = (3.125e6, 400e6)
26 vco_freq_range = ( 400e6, 800e6)
27
28 def __init__(self, clkin,
29 clksel=Signal(shape=2, reset=2),
30 reset=Signal(reset_less=True),
31 locked=Signal()):
32 self.clkin = clkin
33 self.clkin_freq = None
34 self.clksel = clksel
35 self.locked = locked
36 self.reset = reset
37 self.nclkouts = 0
38 self.clkouts = {}
39 self.config = {}
40 self.params = {}
41
42 def ports(self):
43 return [
44 self.clkin,
45 self.clksel,
46 self.lock,
47 ] + list(self.clkouts.values())
48
49 def set_clkin_freq(self, freq):
50 (clki_freq_min, clki_freq_max) = self.clki_freq_range
51 assert freq >= clki_freq_min
52 assert freq <= clki_freq_max
53 self.clkin_freq = freq
54
55 def create_clkout(self, cd, freq, phase=0, margin=1e-2):
56 (clko_freq_min, clko_freq_max) = self.clko_freq_range
57 assert freq >= clko_freq_min
58 assert freq <= clko_freq_max
59 assert self.nclkouts < self.nclkouts_max
60 self.clkouts[self.nclkouts] = (cd, freq, phase, margin)
61 #create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts)
62 print("clock domain", cd.domain, freq, margin, self.nclkouts)
63 self.nclkouts += 1
64
65 def compute_config(self):
66 config = {}
67 for clki_div in range(*self.clki_div_range):
68 config["clki_div"] = clki_div
69 for clkfb_div in range(*self.clkfb_div_range):
70 all_valid = True
71 vco_freq = self.clkin_freq/clki_div*clkfb_div*1 # clkos3_div=1
72 (vco_freq_min, vco_freq_max) = self.vco_freq_range
73 if vco_freq >= vco_freq_min and vco_freq <= vco_freq_max:
74 for n, (clk, f, p, m) in sorted(self.clkouts.items()):
75 valid = False
76 for d in range(*self.clko_div_range):
77 clk_freq = vco_freq/d
78 if abs(clk_freq - f) <= f*m:
79 config["clko{}_freq".format(n)] = clk_freq
80 config["clko{}_div".format(n)] = d
81 config["clko{}_phase".format(n)] = p
82 valid = True
83 break
84 if not valid:
85 all_valid = False
86 else:
87 all_valid = False
88 if all_valid:
89 config["vco"] = vco_freq
90 config["clkfb_div"] = clkfb_div
91 #compute_config_log(self.logger, config)
92 print ("PLL config", config)
93 return config
94 raise ValueError("No PLL config found")
95
96 def elaborate(self, platform):
97 config = self.compute_config()
98 clkfb = Signal()
99 self.params.update(
100 # attributes
101 a_FREQUENCY_PIN_CLKI = str(self.clkin_freq/1e6),
102 a_ICP_CURRENT = "6",
103 a_LPF_RESISTOR = "16",
104 a_MFG_ENABLE_FILTEROPAMP = "1",
105 a_MFG_GMCREF_SEL = "2",
106 # parameters
107 p_FEEDBK_PATH = "INT_OS3", # CLKOS3 rsvd for feedback with div=1.
108 p_CLKOS3_ENABLE = "ENABLED",
109 p_CLKOS3_DIV = 1,
110 p_CLKFB_DIV = config["clkfb_div"],
111 p_CLKI_DIV = config["clki_div"],
112 # reset, input clock, lock-achieved output
113 i_RST = self.reset,
114 i_CLKI = self.clkin,
115 o_LOCK = self.locked,
116 )
117 # for each clock-out, set additional parameters
118 for n, (clk, f, p, m) in sorted(self.clkouts.items()):
119 n_to_l = {0: "P", 1: "S", 2: "S2"}
120 div = config["clko{}_div".format(n)]
121 cphase = int(p*(div + 1)/360 + div)
122 self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED"
123 self.params["p_CLKO{}_DIV".format(n_to_l[n])] = div
124 self.params["p_CLKO{}_FPHASE".format(n_to_l[n])] = 0
125 self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase
126 self.params["o_CLKO{}".format(n_to_l[n])] = clk
127
128 m = Module()
129 print ("params", self.params)
130 pll = Instance("EHXPLLL", **self.params)
131 m.submodules.pll = pll
132 return m
133
134 pll = Instance("EHXPLLL",
135 p_OUTDIVIDER_MUXA='DIVA',
136 p_OUTDIVIDER_MUXB='DIVB',
137 p_CLKOP_ENABLE='ENABLED',
138 p_CLKOS_ENABLE='ENABLED',
139 p_CLKOS2_ENABLE='DISABLED',
140 p_CLKOS3_ENABLE='DISABLED',
141 p_CLKOP_DIV=self.CLKOP_DIV,
142 p_CLKOS_DIV=self.CLKOS_DIV,
143 p_CLKFB_DIV=self.CLKFB_DIV,
144 p_CLKI_DIV=self.CLKI_DIV,
145 p_FEEDBK_PATH='INT_OP',
146 p_CLKOP_TRIM_POL="FALLING",
147 p_CLKOP_TRIM_DELAY=0,
148 p_CLKOS_TRIM_POL="FALLING",
149 p_CLKOS_TRIM_DELAY=0,
150 i_CLKI=self.clkin,
151 i_RST=0,
152 i_STDBY=0,
153 i_PHASESEL0=0,
154 i_PHASESEL1=0,
155 i_PHASEDIR=0,
156 i_PHASESTEP=0,
157 i_PHASELOADREG=0,
158 i_PLLWAKESYNC=0,
159 i_ENCLKOP=1,
160 i_ENCLKOS=1,
161 i_ENCLKOS2=0,
162 i_ENCLKOS3=0,
163 o_CLKOP=self.clkout1,
164 o_CLKOS=self.clkout2,
165 o_CLKOS2=self.clkout3,
166 o_CLKOS3=self.clkout4,
167 o_LOCK=self.lock,
168 )
169
170
171 class ECP5CRG(Elaboratable):
172 def __init__(self, sys_clk_freq=100e6, pod_bits=25):
173 self.sys_clk_freq = sys_clk_freq
174 self.pod_bits = pod_bits
175
176 def do_2x_phase_clock_create(self, m, pll, name, freq):
177 """creates a domain that can be used with xdr=4 platform resources.
178
179 this requires creating a domain at *twice* the frequency,
180 then halving it. the doubled-frequency can then go to the
181 eclk parts of a 4-phase IOPad:
182 pads.a.o_clk.eq(ClockSignal("dramsync")),
183 pads.a.o_fclk.eq(ClockSignal("dramsync2x")),
184 """
185
186 # Generating sync2x from extclk
187 cd_2x = ClockDomain("%s2x" % name, local=False)
188 cd_2x_unbuf = ClockDomain("%s2x_unbuf" % name,
189 local=False, reset_less=True)
190 cd_ = ClockDomain("%s" % name, local=False)
191
192 # create PLL clocks
193 pll.create_clkout(ClockSignal("%s2x_unbuf" % name), 2*freq)
194 m.submodules += Instance("ECLKSYNCB",
195 i_ECLKI = ClockSignal("%s2x_unbuf" % name),
196 i_STOP = 0,
197 o_ECLKO = ClockSignal("%s2x" % name))
198 m.domains += cd_2x_unbuf
199 m.domains += cd_2x
200 m.domains += cd_
201
202 # # Generating sync from sync2x
203 m.submodules += Instance("CLKDIVF",
204 p_DIV="2.0",
205 i_ALIGNWD=0,
206 i_CLKI=ClockSignal("%s2x" % name),
207 i_RST=0,
208 o_CDIVX=ClockSignal("%s" % name))
209
210 def elaborate(self, platform):
211 m = Module()
212
213 # Get 100Mhz from oscillator
214 extclk = platform.request(platform.default_clk)
215 cd_rawclk = ClockDomain("rawclk", local=True, reset_less=True)
216 m.d.comb += cd_rawclk.clk.eq(extclk)
217 m.domains += cd_rawclk
218
219 # Reset
220 if platform.default_rst is not None:
221 reset = platform.request(platform.default_rst).i
222 else:
223 reset = Const(0) # whoops
224
225 gsr0 = Signal()
226 gsr1 = Signal()
227
228 m.submodules += [
229 Instance("FD1S3AX", p_GSR="DISABLED",
230 i_CK=ClockSignal("rawclk"),
231 i_D=~reset,
232 o_Q=gsr0),
233 Instance("FD1S3AX", p_GSR="DISABLED",
234 i_CK=ClockSignal("rawclk"),
235 i_D=gsr0,
236 o_Q=gsr1),
237 Instance("SGSR", i_CLK=ClockSignal("rawclk"),
238 i_GSR=gsr1),
239 ]
240
241 # PLL
242 m.submodules.pll = pll = PLL(ClockSignal("rawclk"), reset=~reset)
243
244 # Power-on delay (655us)
245 podcnt = Signal(self.pod_bits, reset=-1)
246 pod_done = Signal()
247 with m.If((podcnt != 0) & pll.locked):
248 m.d.rawclk += podcnt.eq(podcnt-1)
249 m.d.rawclk += pod_done.eq(podcnt == 0)
250
251 # and reset which only drops when the PLL is done and pod completes
252 reset_ok = Signal(reset_less=True)
253 m.d.comb += reset_ok.eq(~pll.locked|~pod_done)
254
255 # create PLL input clock from platform default frequency
256 pll.set_clkin_freq(platform.default_clk_frequency)
257
258 # create 25 mhz "init" clock, straight (no 2x phase stuff)
259 cd_init = ClockDomain("init", local=False)
260 pll.create_clkout(ClockSignal("init"), 25e6)
261 m.domains += cd_init
262 m.d.comb += ResetSignal("init").eq(reset_ok)
263
264 # Generating sync2x and sync from extclk, which is *only* how
265 # xdr=4 can be requested on the sync domain
266 self.do_2x_phase_clock_create(m, pll, "sync", self.sys_clk_freq)
267 m.d.comb += ResetSignal("sync").eq(reset_ok)
268
269 # temporarily set dram sync clock exactly equal to main sync,
270 # which turns out to be a dog's dinner botch-job, because
271 # GRAM's ECP5PHY uses dramsync for the pads.o_clk but
272 # sync2x for the pads.o_fclk. sigh.
273 # TODO: this to be replaced with
274 # self.do_2x_phase_clock_create(m, pll, "dramsync", self.dram_clk_freq)
275 # and then a suitable DomainRenamer for *both* dramsync *and*
276 # dramsync2x applied
277 cd_dramsync = ClockDomain("dramsync", local=False)
278 m.domains += cd_dramsync
279 m.d.comb += ClockSignal("dramsync").eq(ClockSignal("sync"))
280 m.d.comb += ResetSignal("dramsync").eq(reset_ok)
281
282 return m
283