clock/lattice_ecp5: Fix and rework 4-output solver implementation.

The implementation was causing regressions on actual designs, rework done:
- Only keep a common iteration loop as before.
- Add iteration on CLKO dividers (to fall in the VCO range).
- Do the iterations as before, if while doing it we find a clock suitable for feedback: just use it.
- If no feedback clock has been found: create it (if at least one free output available, if not raise an error).
This commit is contained in:
Florent Kermarrec 2021-07-26 13:38:14 +02:00
parent 751e99690e
commit 1ce48a973b
2 changed files with 52 additions and 70 deletions

View File

@ -1,7 +1,7 @@
# #
# This file is part of LiteX. # This file is part of LiteX.
# #
# Copyright (c) 2018-2020 Florent Kermarrec <florent@enjoy-digital.fr> # Copyright (c) 2018-2021 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2021 George Hilliard <thirtythreeforty@gmail.com> # Copyright (c) 2021 George Hilliard <thirtythreeforty@gmail.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
@ -63,72 +63,54 @@ class ECP5PLL(Module):
def compute_config(self): def compute_config(self):
config = {} config = {}
# Iterate on CLKI dividers...
def in_range(n, r):
(r_min, r_max) = r
return n >= r_min and n <= r_max
def found_clk(n, f, d, p):
config["clko{}_freq".format(n)] = f
config["clko{}_div".format(n)] = d
config["clko{}_phase".format(n)] = p
for clki_div in range(*self.clki_div_range): for clki_div in range(*self.clki_div_range):
if not in_range(self.clkin_freq / clki_div, self.pfd_freq_range): # Check if in PFD range.
(pfd_freq_min, pfd_freq_max) = self.pfd_freq_range
if not (pfd_freq_min <= self.clkin_freq/clki_div <= pfd_freq_max):
continue continue
config["clki_div"] = clki_div config["clki_div"] = clki_div
# Iterate on CLKO dividers... (to get us in VCO range)
for clkfb_div in range(*self.clkfb_div_range): for clkofb_div in range(*self.clko_div_range):
# pick a suitable feedback clock # Iterate on CLKFB dividers...
found_fb = None for clkfb_div in range(*self.clkfb_div_range):
for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()): vco_freq = (self.clkin_freq/clki_div)*clkfb_div*clkofb_div
if dpa and self.dpa_en: (vco_freq_min, vco_freq_max) = self.vco_freq_range
# cannot use clocks whose phase the user will change all_valid = True
continue # If in VCO range, find dividers for all outputs.
for d in range(*self.clko_div_range): if vco_freq_min <= vco_freq <= vco_freq_max:
vco_freq = self.clkin_freq/clki_div*clkfb_div*d config["clkfb"] = None
clk_freq = vco_freq/d for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()):
if in_range(vco_freq, self.vco_freq_range) \ valid = False
and abs(clk_freq - f) <= f*m: for d in range(*self.clko_div_range):
found_fb = n clk_freq = vco_freq/d
found_clk(n, f, d, p) # If output is valid, save config.
break if abs(clk_freq - f) <= f*m:
if found_fb is not None: config["clko{}_freq".format(n)] = clk_freq
break config["clko{}_div".format(n)] = d
else: config["clko{}_phase".format(n)] = p
# none found, try to use a new output valid = True
for d in range(*self.clko_div_range): # Check if ouptut can be used as feedback, if so use it.
vco_freq = self.clkin_freq/clki_div*clkfb_div*d # (We cannot use clocks with dynamic phase adjustment enabled)
clk_freq = vco_freq/d if (d == clkofb_div) and (not (dpa and self.dpa_en)):
if self.nclkouts < self.nclkouts_max \ config["clkfb"] = n
and in_range(vco_freq, self.vco_freq_range) \ break
and in_range(clk_freq, self.clko_freq_range): if not valid:
found_fb = self.nclkouts all_valid = False
found_clk(found_fb, clk_freq, d, 0)
break
else:
continue
# vco_freq is known, compute remaining clocks' output settings
all_valid = True
for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()):
if n == found_fb:
continue # already picked this one
for d in range(*self.clko_div_range):
clk_freq = vco_freq/d
if abs(clk_freq - f) <= f*m:
found_clk(n, f, d, p)
break
else: else:
all_valid = False all_valid = False
if all_valid: if all_valid:
if found_fb > self.nclkouts: # If no output suitable for feedback, create a new output for it.
self.create_clkout(ClockDomain('feedback'), vco_freq / clkfb_div) if config["clkfb"] is None:
config["vco"] = vco_freq # We need at least a free output...
config["clkfb"] = found_fb assert self.nclkouts < self.nclkouts_max
config["clkfb_div"] = clkfb_div config["clkfb"] = self.nclkouts
compute_config_log(self.logger, config) self.clkouts[self.nclkouts] = (Signal(), 0, 0, 0, 0)
return config config[f"clko{self.nclkouts}_div"] = int((vco_freq*clki_div)/(self.clkin_freq*clkfb_div))
config["vco"] = vco_freq
config["clkfb_div"] = clkfb_div
compute_config_log(self.logger, config)
return config
raise ValueError("No PLL config found") raise ValueError("No PLL config found")
def expose_dpa(self): def expose_dpa(self):
@ -164,17 +146,17 @@ class ECP5PLL(Module):
i_CLKI = self.clkin, i_CLKI = self.clkin,
i_STDBY = self.stdby, i_STDBY = self.stdby,
o_LOCK = locked, o_LOCK = locked,
p_FEEDBK_PATH = "INT_O{}".format(n_to_l[config['clkfb']]), p_FEEDBK_PATH = f"INT_O{n_to_l[config['clkfb']]}",
p_CLKFB_DIV = config["clkfb_div"], p_CLKFB_DIV = config["clkfb_div"],
p_CLKI_DIV = config["clki_div"] p_CLKI_DIV = config["clki_div"]
) )
self.comb += self.locked.eq(locked & ~self.reset) self.comb += self.locked.eq(locked & ~self.reset)
for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()): for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()):
div = config["clko{}_div".format(n)] div = config[f"clko{n}_div"]
cphase = int(p*(div + 1)/360 + div - 1) cphase = int(p*(div + 1)/360 + div - 1)
self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED" self.params[f"p_CLKO{n_to_l[n]}_ENABLE"] = "ENABLED"
self.params["p_CLKO{}_DIV".format(n_to_l[n])] = div self.params[f"p_CLKO{n_to_l[n]}_DIV"] = div
self.params["p_CLKO{}_FPHASE".format(n_to_l[n])] = 0 self.params[f"p_CLKO{n_to_l[n]}_FPHASE"] = 0
self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase self.params[f"p_CLKO{n_to_l[n]}_CPHASE"] = cphase
self.params["o_CLKO{}".format(n_to_l[n])] = clk self.params[f"o_CLKO{n_to_l[n]}"] = clk
self.specials += Instance("EHXPLLL", **self.params) self.specials += Instance("EHXPLLL", **self.params)

View File

@ -116,7 +116,7 @@ class TestClock(unittest.TestCase):
def test_ecp5pll(self): def test_ecp5pll(self):
pll = ECP5PLL() pll = ECP5PLL()
pll.register_clkin(Signal(), 100e6) pll.register_clkin(Signal(), 100e6)
for i in range(pll.nclkouts_max): for i in range(pll.nclkouts_max-1):
pll.create_clkout(ClockDomain("clkout{}".format(i)), 200e6, uses_dpa=(i != 0)) pll.create_clkout(ClockDomain("clkout{}".format(i)), 200e6, uses_dpa=(i != 0))
pll.expose_dpa() pll.expose_dpa()
pll.compute_config() pll.compute_config()