Merge pull request #965 from thirtythreeforty/ecp5-pll-x4
ECP5PLL: implement 4-output solver
This commit is contained in:
commit
751e99690e
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue