Merge pull request #965 from thirtythreeforty/ecp5-pll-x4

ECP5PLL: implement 4-output solver
This commit is contained in:
enjoy-digital 2021-07-21 19:08:18 +02:00 committed by GitHub
commit 751e99690e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 29 deletions

View File

@ -2,6 +2,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-2020 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2021 George Hilliard <thirtythreeforty@gmail.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
from migen import * from migen import *
@ -12,21 +13,24 @@ from litex.soc.cores.clock.common import *
# Lattice / ECP5 ----------------------------------------------------------------------------------- # Lattice / ECP5 -----------------------------------------------------------------------------------
class ECP5PLL(Module): class ECP5PLL(Module):
nclkouts_max = 3 nclkouts_max = 4
clki_div_range = (1, 128+1) clki_div_range = (1, 128+1)
clkfb_div_range = (1, 128+1) clkfb_div_range = (1, 128+1)
clko_div_range = (1, 128+1) clko_div_range = (1, 128+1)
clki_freq_range = ( 8e6, 400e6) clki_freq_range = ( 8e6, 400e6)
clko_freq_range = (3.125e6, 400e6) clko_freq_range = (3.125e6, 400e6)
vco_freq_range = ( 400e6, 800e6) vco_freq_range = ( 400e6, 800e6)
pfd_freq_range = ( 10e6, 400e6)
def __init__(self): def __init__(self):
self.logger = logging.getLogger("ECP5PLL") self.logger = logging.getLogger("ECP5PLL")
self.logger.info("Creating ECP5PLL.") self.logger.info("Creating ECP5PLL.")
self.reset = Signal() self.reset = Signal()
self.locked = Signal() self.locked = Signal()
self.stdby = Signal()
self.clkin_freq = None self.clkin_freq = None
self.vcxo_freq = None self.vcxo_freq = None
self.dpa_en = False
self.nclkouts = 0 self.nclkouts = 0
self.clkouts = {} self.clkouts = {}
self.config = {} self.config = {}
@ -44,13 +48,13 @@ class ECP5PLL(Module):
self.clkin_freq = freq self.clkin_freq = freq
register_clkin_log(self.logger, clkin, freq) register_clkin_log(self.logger, clkin, freq)
def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True): def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True, uses_dpa=True):
(clko_freq_min, clko_freq_max) = self.clko_freq_range (clko_freq_min, clko_freq_max) = self.clko_freq_range
assert freq >= clko_freq_min assert freq >= clko_freq_min
assert freq <= clko_freq_max assert freq <= clko_freq_max
assert self.nclkouts < self.nclkouts_max assert self.nclkouts < self.nclkouts_max
clkout = Signal() clkout = Signal()
self.clkouts[self.nclkouts] = (clkout, freq, phase, margin) self.clkouts[self.nclkouts] = (clkout, freq, phase, margin, uses_dpa)
if with_reset: if with_reset:
self.specials += AsyncResetSynchronizer(cd, ~self.locked) self.specials += AsyncResetSynchronizer(cd, ~self.locked)
self.comb += cd.clk.eq(clkout) self.comb += cd.clk.eq(clkout)
@ -59,35 +63,76 @@ class ECP5PLL(Module):
def compute_config(self): def compute_config(self):
config = {} 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): 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 config["clki_div"] = clki_div
for clkfb_div in range(*self.clkfb_div_range): for clkfb_div in range(*self.clkfb_div_range):
all_valid = True # pick a suitable feedback clock
vco_freq = self.clkin_freq/clki_div*clkfb_div*1 # clkos3_div=1 found_fb = None
(vco_freq_min, vco_freq_max) = self.vco_freq_range for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()):
if vco_freq >= vco_freq_min and vco_freq <= vco_freq_max: if dpa and self.dpa_en:
for n, (clk, f, p, m) in sorted(self.clkouts.items()): # cannot use clocks whose phase the user will change
valid = False continue
for d in range(*self.clko_div_range): for d in range(*self.clko_div_range):
clk_freq = vco_freq/d vco_freq = self.clkin_freq/clki_div*clkfb_div*d
if abs(clk_freq - f) <= f*m: clk_freq = vco_freq/d
config["clko{}_freq".format(n)] = clk_freq if in_range(vco_freq, self.vco_freq_range) \
config["clko{}_div".format(n)] = d and abs(clk_freq - f) <= f*m:
config["clko{}_phase".format(n)] = p found_fb = n
valid = True found_clk(n, f, d, p)
break break
if not valid: if found_fb is not None:
all_valid = False break
else: 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, 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:
all_valid = False
if all_valid: if all_valid:
if found_fb > self.nclkouts:
self.create_clkout(ClockDomain('feedback'), vco_freq / clkfb_div)
config["vco"] = vco_freq config["vco"] = vco_freq
config["clkfb"] = found_fb
config["clkfb_div"] = clkfb_div config["clkfb_div"] = clkfb_div
compute_config_log(self.logger, config) compute_config_log(self.logger, config)
return config return config
raise ValueError("No PLL config found") raise ValueError("No PLL config found")
def expose_dpa(self): def expose_dpa(self):
self.dpa_en = True
self.phase_sel = Signal(2) self.phase_sel = Signal(2)
self.phase_dir = Signal() self.phase_dir = Signal()
self.phase_step = Signal() self.phase_step = Signal()
@ -106,8 +151,8 @@ class ECP5PLL(Module):
def do_finalize(self): def do_finalize(self):
config = self.compute_config() config = self.compute_config()
clkfb = Signal()
locked = Signal() locked = Signal()
n_to_l = {0: "P", 1: "S", 2: "S2", 3: "S3"}
self.params.update( self.params.update(
attr=[ attr=[
("FREQUENCY_PIN_CLKI", str(self.clkin_freq/1e6)), ("FREQUENCY_PIN_CLKI", str(self.clkin_freq/1e6)),
@ -117,18 +162,14 @@ class ECP5PLL(Module):
("MFG_GMCREF_SEL", "2")], ("MFG_GMCREF_SEL", "2")],
i_RST = self.reset, i_RST = self.reset,
i_CLKI = self.clkin, i_CLKI = self.clkin,
i_STDBY = self.stdby,
o_LOCK = locked, o_LOCK = locked,
p_FEEDBK_PATH = "INT_OS3", # CLKOS3 reserved for feedback with div=1. p_FEEDBK_PATH = "INT_O{}".format(n_to_l[config['clkfb']]),
p_CLKOS3_ENABLE = "ENABLED",
p_CLKOS3_DIV = 1,
p_CLKOS3_FPHASE = 0,
p_CLKOS3_CPHASE = 23,
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) in sorted(self.clkouts.items()): for n, (clk, f, p, m, dpa) in sorted(self.clkouts.items()):
n_to_l = {0: "P", 1: "S", 2: "S2"}
div = config["clko{}_div".format(n)] div = config["clko{}_div".format(n)]
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["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED"

View File

@ -117,7 +117,8 @@ class TestClock(unittest.TestCase):
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):
pll.create_clkout(ClockDomain("clkout{}".format(i)), 200e6) pll.create_clkout(ClockDomain("clkout{}".format(i)), 200e6, uses_dpa=(i != 0))
pll.expose_dpa()
pll.compute_config() pll.compute_config()
# Lattice / NX # Lattice / NX