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>
6 # Based on code from LambaConcept, from the gram example which is BSD-2-License
7 # https://github.com/jeanthom/gram/tree/master/examples
9 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
10 # under EU Grants 871528 and 957073, under the LGPLv3+ License
13 from nmigen
import (Elaboratable
, Module
, Signal
, ClockDomain
, Instance
,
14 ClockSignal
, ResetSignal
, Const
)
19 class PLL(Elaboratable
):
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
)
28 def __init__(self
, clkin
,
29 clksel
=Signal(shape
=2, reset
=2),
30 reset
=Signal(reset_less
=True),
33 self
.clkin_freq
= None
47 ] + list(self
.clkouts
.values())
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
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
)
65 def compute_config(self
):
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
):
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()):
76 for d
in range(*self
.clko_div_range
):
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
89 config
["vco"] = vco_freq
90 config
["clkfb_div"] = clkfb_div
91 #compute_config_log(self.logger, config)
92 print ("PLL config", config
)
94 raise ValueError("No PLL config found")
96 def elaborate(self
, platform
):
97 config
= self
.compute_config()
101 a_FREQUENCY_PIN_CLKI
= str(self
.clkin_freq
/1e6
),
103 a_LPF_RESISTOR
= "16",
104 a_MFG_ENABLE_FILTEROPAMP
= "1",
105 a_MFG_GMCREF_SEL
= "2",
107 p_FEEDBK_PATH
= "INT_OS3", # CLKOS3 rsvd for feedback with div=1.
108 p_CLKOS3_ENABLE
= "ENABLED",
110 p_CLKFB_DIV
= config
["clkfb_div"],
111 p_CLKI_DIV
= config
["clki_div"],
112 # reset, input clock, lock-achieved output
115 o_LOCK
= self
.locked
,
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
129 print ("params", self
.params
)
130 pll
= Instance("EHXPLLL", **self
.params
)
131 m
.submodules
.pll
= pll
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,
163 o_CLKOP
=self
.clkout1
,
164 o_CLKOS
=self
.clkout2
,
165 o_CLKOS2
=self
.clkout3
,
166 o_CLKOS3
=self
.clkout4
,
171 class ECP5CRG(Elaboratable
):
172 def __init__(self
, sys_clk_freq
=100e6
, dram_clk_freq
=None,
173 pod_bits
=25, sync_bits
=26, need_bridge
=False):
174 """when dram_clk_freq=None, a dramsync domain is still created
175 but it is an alias of sync domain. likewise the 2x
177 self
.sys_clk_freq
= sys_clk_freq
178 self
.dram_clk_freq
= dram_clk_freq
179 self
.pod_bits
= pod_bits
# for init domain
180 self
.sync_bits
= sync_bits
# for all other domains
181 self
.need_bridge
= need_bridge
# insert ECLKBRIDGECS
182 assert pod_bits
<= sync_bits
, \
183 "power-on-delay bits %d should " \
184 " be less than sync_bits %d" % (pod_bits
, sync_bits
)
186 def phase2_domain(self
, m
, pll
, name
, freq
, esyncb
):
187 """creates a domain that can be used with xdr=4 platform resources.
189 this requires creating a domain at *twice* the frequency,
190 then halving it. the doubled-frequency can then go to the
191 eclk parts of a 4-phase IOPad:
192 pads.a.o_clk.eq(ClockSignal("dramsync")),
193 pads.a.o_fclk.eq(ClockSignal("dramsync2x")),
198 cd2x_ub
= cd2x
+"_unbuf"
200 # Generating sync2x from extclk
201 cd_2x
= ClockDomain(cd2x
, local
=False)
202 cd_2x_unbuf
= ClockDomain(cd2x_ub
, local
=False, reset_less
=True)
203 cd_
= ClockDomain("%s" % name
, local
=False)
206 pll
.create_clkout(ClockSignal(cd2x_ub
), 2*freq
)
209 sys2x_clk_ecsout
= Signal()
210 m
.submodules
["%s_eclkbridgecs" % cd
] = Instance("ECLKBRIDGECS",
211 i_CLK0
= ClockSignal(cd2x_ub
),
213 o_ECSOUT
= sys2x_clk_ecsout
)
214 m
.submodules
["%s_eclksyncb" % cd
] = Instance("ECLKSYNCB",
215 i_ECLKI
= sys2x_clk_ecsout
,
217 o_ECLKO
= ClockSignal(cd2x
))
219 m
.submodules
["%s_eclksyncb" % cd
] = Instance("ECLKSYNCB",
220 i_ECLKI
= ClockSignal(cd2x_ub
),
222 o_ECLKO
= ClockSignal(cd2x
))
224 m
.d
.comb
+= ClockSignal(cd2x
).eq(ClockSignal(cd2x_ub
)) # no esyncb
225 m
.domains
+= cd_2x_unbuf
229 # # Generating sync from sync2x
230 m
.submodules
["%s_clkdivf" % cd
] = Instance("CLKDIVF",
233 i_CLKI
=ClockSignal(cd2x
),
235 o_CDIVX
=ClockSignal(cd
))
237 def elaborate(self
, platform
):
240 # Get 100Mhz from oscillator
241 extclk
= platform
.request(platform
.default_clk
)
242 cd_rawclk
= ClockDomain("rawclk", local
=True, reset_less
=True)
243 m
.d
.comb
+= cd_rawclk
.clk
.eq(extclk
)
244 m
.domains
+= cd_rawclk
247 if platform
.default_rst
is not None:
248 reset
= platform
.request(platform
.default_rst
).i
250 reset
= Const(0) # whoops
255 m
.submodules
.gsr0
= Instance("FD1S3AX", p_GSR
="DISABLED",
256 i_CK
=ClockSignal("rawclk"),
259 m
.submodules
.gsr1
= Instance("FD1S3AX", p_GSR
="DISABLED",
260 i_CK
=ClockSignal("rawclk"),
263 m
.submodules
.sgsr
= Instance("SGSR", i_CLK
=ClockSignal("rawclk"),
267 m
.submodules
.pll
= pll
= PLL(ClockSignal("rawclk"), reset
=~reset
)
269 # Power-on delay (655us)
270 podcnt
= Signal(self
.pod_bits
, reset
=-1)
271 synccnt
= Signal(self
.sync_bits
, reset
=-1)
274 with m
.If((synccnt
!= 0) & pll
.locked
):
275 m
.d
.rawclk
+= synccnt
.eq(synccnt
-1)
276 with m
.If((podcnt
!= 0) & pll
.locked
):
277 m
.d
.rawclk
+= podcnt
.eq(podcnt
-1)
278 m
.d
.rawclk
+= pod_done
.eq(podcnt
== 0)
279 m
.d
.rawclk
+= sync_done
.eq(synccnt
== 0)
281 # and reset which only drops when the PLL is done and pod completes
282 reset_ok
= Signal(reset_less
=True)
283 sync_reset_ok
= Signal(reset_less
=True)
284 m
.d
.comb
+= reset_ok
.eq(~pll
.locked|~pod_done
)
285 m
.d
.comb
+= sync_reset_ok
.eq(~pll
.locked|~sync_done
)
287 # create PLL input clock from platform default frequency
288 pll
.set_clkin_freq(platform
.default_clk_frequency
)
290 # single or double main sync clock domain. double needs a 2nd PLL
291 # to match up with the CLKESYNCB, one per quadrant inside the ECP5
292 if self
.dram_clk_freq
is not None:
293 m
.domains
+= ClockDomain("sync_unbuf", local
=False, reset_less
=True)
294 m
.domains
+= ClockDomain("sync", local
=False)
295 pll
.create_clkout(ClockSignal("sync_unbuf"), self
.sys_clk_freq
)
296 m
.d
.comb
+= ClockSignal("sync").eq(ClockSignal("sync_unbuf"))
298 # Generating sync2x and sync from extclk, which is *only* how
299 # xdr=4 can be requested on the sync domain. also do not request
301 self
.phase2_domain(m
, pll
, "sync", self
.sys_clk_freq
, True)
302 m
.d
.comb
+= ResetSignal("sync2x").eq(sync_reset_ok
)
303 m
.d
.comb
+= ResetSignal("sync").eq(sync_reset_ok
)
305 # DRAM clock: if not requested set to sync, otherwise create with
306 # a CLKESYNCB (which is set to no-stop at the moment)
307 if self
.dram_clk_freq
is not None:
308 self
.phase2_domain(m
, pll
, "dramsync", self
.dram_clk_freq
, True)
310 # alias dramsync and dramsync2x to sync and sync2x
311 cd_dramsync
= ClockDomain("dramsync", local
=False)
312 m
.domains
+= cd_dramsync
313 m
.d
.comb
+= ClockSignal("dramsync").eq(ClockSignal("sync"))
315 cd_dramsync2x
= ClockDomain("dramsync2x", local
=False)
316 m
.domains
+= cd_dramsync2x
317 m
.d
.comb
+= ClockSignal("dramsync2x").eq(ClockSignal("sync2x"))
318 # resets for the dram domains
319 m
.d
.comb
+= ResetSignal("dramsync2x").eq(sync_reset_ok
)
320 m
.d
.comb
+= ResetSignal("dramsync").eq(sync_reset_ok
)
322 # create 25 mhz "init" clock, straight (no 2x phase stuff)
323 # this domain can be used before all others, has its own delay
325 cd_init
= ClockDomain("init", local
=False)
326 pll
.create_clkout(ClockSignal("init"), 25e6
)
328 m
.d
.comb
+= ResetSignal("init").eq(reset_ok
)