clock/lattice_ecp5/ECP5PLL: implement 4-output solver
Reimplement the configuration loop to allow all 4 outputs to be used by the user, if one of them is suitable for use as VCO feedback. The new strategy is to first iterate over requested outputs to see if any of them can be used as a feedback source. If one can, it is selected, and if no output is suitable, it attempts to instantiate one. Once the feedback path is selected, the VCO frequency is known and it attempts to calculate the remaining outputs' settings. In addition, this implementation now respects datasheet limits in two new ways: - It respects the post-input-divider minimum frequency of 10MHz - It respects the max output frequency of 400MHz for instantiated feedback outputs I am slightly unhappy with the seemingly-repetitive for loops. However each one has slightly different sematics and I don't see a way to combine them that doesn't hinder readability.
This commit is contained in:
parent
8b9f03efba
commit
34ba649f38
|
@ -2,6 +2,7 @@
|
|||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2018-2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# Copyright (c) 2021 George Hilliard <thirtythreeforty@gmail.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from migen import *
|
||||
|
@ -12,13 +13,14 @@ from litex.soc.cores.clock.common import *
|
|||
# Lattice / ECP5 -----------------------------------------------------------------------------------
|
||||
|
||||
class ECP5PLL(Module):
|
||||
nclkouts_max = 3
|
||||
nclkouts_max = 4
|
||||
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)
|
||||
pfd_freq_range = ( 10e6, 400e6)
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("ECP5PLL")
|
||||
|
@ -60,29 +62,66 @@ class ECP5PLL(Module):
|
|||
|
||||
def compute_config(self):
|
||||
config = {}
|
||||
|
||||
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):
|
||||
if not in_range(self.clkin_freq / clki_div, self.pfd_freq_range):
|
||||
continue
|
||||
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
|
||||
# pick a suitable feedback clock
|
||||
found_fb = None
|
||||
for n, (clk, f, p, m) in sorted(self.clkouts.items()):
|
||||
for d in range(*self.clko_div_range):
|
||||
vco_freq = self.clkin_freq/clki_div*clkfb_div*d
|
||||
clk_freq = vco_freq/d
|
||||
if in_range(vco_freq, self.vco_freq_range) \
|
||||
and abs(clk_freq - f) <= f*m:
|
||||
found_fb = n
|
||||
found_clk(n, f, d, p)
|
||||
break
|
||||
if found_fb is not None:
|
||||
break
|
||||
else:
|
||||
all_valid = False
|
||||
# none found, try to use a new output
|
||||
for d in range(*self.clko_div_range):
|
||||
vco_freq = self.clkin_freq/clki_div*clkfb_div*d
|
||||
clk_freq = vco_freq/d
|
||||
if self.nclkouts < self.nclkouts_max \
|
||||
and in_range(vco_freq, self.vco_freq_range) \
|
||||
and in_range(clk_freq, self.clko_freq_range):
|
||||
found_fb = self.nclkouts
|
||||
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) 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:
|
||||
all_valid = False
|
||||
if all_valid:
|
||||
if found_fb > self.nclkouts:
|
||||
self.create_clkout(ClockDomain('feedback'), vco_freq / clkfb_div)
|
||||
config["vco"] = vco_freq
|
||||
config["clkfb"] = found_fb
|
||||
config["clkfb_div"] = clkfb_div
|
||||
compute_config_log(self.logger, config)
|
||||
return config
|
||||
|
@ -107,8 +146,8 @@ class ECP5PLL(Module):
|
|||
|
||||
def do_finalize(self):
|
||||
config = self.compute_config()
|
||||
clkfb = Signal()
|
||||
locked = Signal()
|
||||
n_to_l = {0: "P", 1: "S", 2: "S2", 3: "S3"}
|
||||
self.params.update(
|
||||
attr=[
|
||||
("FREQUENCY_PIN_CLKI", str(self.clkin_freq/1e6)),
|
||||
|
@ -120,17 +159,12 @@ class ECP5PLL(Module):
|
|||
i_CLKI = self.clkin,
|
||||
i_STDBY = self.stdby,
|
||||
o_LOCK = locked,
|
||||
p_FEEDBK_PATH = "INT_OS3", # CLKOS3 reserved for feedback with div=1.
|
||||
p_CLKOS3_ENABLE = "ENABLED",
|
||||
p_CLKOS3_DIV = 1,
|
||||
p_CLKOS3_FPHASE = 0,
|
||||
p_CLKOS3_CPHASE = 23,
|
||||
p_FEEDBK_PATH = "INT_O{}".format(n_to_l[config['clkfb']]),
|
||||
p_CLKFB_DIV = config["clkfb_div"],
|
||||
p_CLKI_DIV = config["clki_div"]
|
||||
)
|
||||
self.comb += self.locked.eq(locked & ~self.reset)
|
||||
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 - 1)
|
||||
self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED"
|
||||
|
|
Loading…
Reference in New Issue