-
Notifications
You must be signed in to change notification settings - Fork 568
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
soc/cores/clock: initial GW5A support
GW5A has different PLLs than GW1N/GW2A, with multiple individual ODIV's. GW5A-25 has a different PLL with GW5A[S]T-138, with lack of dynamic control. Add basic support for them. Signed-off-by: Icenowy Zheng <[email protected]>
- Loading branch information
Showing
1 changed file
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
# | ||
# This file is part of LiteX. | ||
# | ||
# Copyright (c) 2021-2022 Florent Kermarrec <[email protected]> | ||
# SPDX-License-Identifier: BSD-2-Clause | ||
|
||
from migen import * | ||
from migen.genlib.resetsync import AsyncResetSynchronizer | ||
|
||
from litex.gen import * | ||
|
||
from litex.soc.cores.clock.common import * | ||
|
||
# GoWin / GW5APLL ---------------------------------------------------------------------------------- | ||
|
||
class GW5APLL(LiteXModule): | ||
nclkouts_max = 7 | ||
|
||
def __init__(self, devicename, device, vco_margin=0): | ||
self.logger = logging.getLogger("GW5APLL") | ||
self.logger.info("Creating GW5APLL.".format()) | ||
self.device = device | ||
self.devicename = devicename | ||
self.vco_margin = vco_margin | ||
self.reset = Signal() | ||
self.locked = Signal() | ||
self.clkin_freq = None | ||
self.vcxo_freq = None | ||
self.nclkouts = 0 | ||
self.clkouts = {} | ||
self.config = {} | ||
self.params = {} | ||
self.vco_freq_range = self.get_vco_freq_range(device) | ||
self.pfd_freq_range = self.get_pfd_freq_range(device) | ||
|
||
@staticmethod | ||
def get_vco_freq_range(device): | ||
vco_freq_range = None | ||
if device.startswith('GW5A-') or device.startswith('GW5AT-') or device.startswith('GW5AST-'): | ||
vco_freq_range = (800e6, 2000e6) # datasheet values | ||
if vco_freq_range is None: | ||
raise ValueError(f"Unsupported device {device}.") | ||
return vco_freq_range | ||
|
||
@staticmethod | ||
def get_pfd_freq_range(device): | ||
pfd_freq_range = None | ||
if device.startswith('GW5A-') or device.startswith('GW5AT-') or device.startswith('GW5AST-'): | ||
pfd_freq_range = (10e6, 400e6) # datasheet values | ||
if pfd_freq_range is None: | ||
raise ValueError(f"Unsupported device {device}.") | ||
return pfd_freq_range | ||
|
||
def register_clkin(self, clkin, freq): | ||
self.clkin = Signal() | ||
if isinstance(clkin, (Signal, ClockSignal)): | ||
self.comb += self.clkin.eq(clkin) | ||
else: | ||
raise ValueError | ||
self.clkin_freq = freq | ||
register_clkin_log(self.logger, clkin, freq) | ||
|
||
def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True): | ||
assert self.nclkouts < self.nclkouts_max | ||
clkout = Signal() | ||
self.clkouts[self.nclkouts] = (clkout, freq, phase, margin) | ||
if with_reset: | ||
self.specials += AsyncResetSynchronizer(cd, ~self.locked) | ||
self.comb += cd.clk.eq(clkout) | ||
create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) | ||
self.nclkouts += 1 | ||
|
||
def compute_config(self): | ||
configs = [] # corresponding VCO/FBDIV/IDIV/ODIV params + diff | ||
|
||
for idiv in range(1, 64): | ||
pfd_freq = self.clkin_freq/idiv | ||
pfd_freq_min, pfd_freq_max = self.pfd_freq_range | ||
if (pfd_freq < pfd_freq_min) or (pfd_freq > pfd_freq_max): | ||
continue | ||
for fdiv in range(1, 64): | ||
for mdiv in range(2,128): | ||
vco_freq = self.clkin_freq/idiv*fdiv*mdiv | ||
(vco_freq_min, vco_freq_max) = self.vco_freq_range | ||
if (vco_freq >= vco_freq_min*(1 + self.vco_margin) and | ||
vco_freq <= vco_freq_max*(1 - self.vco_margin)): | ||
okay = True | ||
config = {} | ||
for n, (clk, f, p, m) in self.clkouts.items(): | ||
odiv = round(vco_freq/f) | ||
out_freq = vco_freq/odiv | ||
diff = abs(out_freq - f) / f | ||
pe = round(p * odiv / 360) | ||
if abs((360.0 * pe / odiv) - p) / 360 > m: | ||
okay = False | ||
if diff > m: | ||
okay = False | ||
else: | ||
config["odiv%d" % n] = odiv | ||
config["diff%d" % n] = diff | ||
config["pe%d" % n] = int(p * odiv / 360) | ||
config["pe%d_fine" % n] = round(p * odiv * 8 / 360) % 8 | ||
if okay: | ||
config["idiv"] = idiv | ||
config["vco"] = vco_freq | ||
config["fdiv"] = fdiv | ||
config["mdiv"] = mdiv | ||
configs += [config] | ||
|
||
if len(configs) == 0: | ||
raise ValueError("No PLL config found") | ||
|
||
best_config = None | ||
best_diff_sum = 0 | ||
for i in range(0,len(configs)): | ||
curr_diff_sum = 0 | ||
for n, clkout in self.clkouts.items(): | ||
curr_diff_sum += configs[i]["diff%d" % n] | ||
|
||
if i == 0 or curr_diff_sum < best_diff_sum: | ||
best_diff_sum = curr_diff_sum | ||
best_config = configs[i] | ||
|
||
return best_config | ||
|
||
def do_finalize(self): | ||
assert hasattr(self, "clkin") | ||
assert len(self.clkouts) > 0 and len(self.clkouts) <= self.nclkouts_max | ||
config = self.compute_config() | ||
# Based on UG306-1.0 Note. | ||
self.params.update( | ||
# Parameters. | ||
p_FCLKIN = str(self.clkin_freq/1e6), # Clk Input frequency (MHz). | ||
p_IDIV_SEL = config["idiv"], # Static IDIV value (1-64). | ||
p_FBDIV_SEL = config["fdiv"], # Static FBDIV value (1-64). | ||
p_ODIV0_SEL = 8, # Static ODIV value (1-128). | ||
p_ODIV0_FRAC_SEL = 0, # Static ODIV0 fractional value (0-7)/8 | ||
p_ODIV1_SEL = 8, # Static ODIV1 value | ||
p_ODIV2_SEL = 8, # Static ODIV2 value | ||
p_ODIV3_SEL = 8, # Static ODIV3 value | ||
p_ODIV4_SEL = 8, # Static ODIV4 value | ||
p_ODIV5_SEL = 8, # Static ODIV5 value | ||
p_ODIV6_SEL = 8, # Static ODIV6 value | ||
p_MDIV_SEL = config["mdiv"], # Static MDIV value (2-128). | ||
p_MDIV_FRAC_SEL = 0, # Static MDIV fractional value (0-7)/8 | ||
p_CLKOUT0_EN = "FALSE", # Disable CLKOUT0. | ||
p_CLKOUT1_EN = "FALSE", # Disable CLKOUT1. | ||
p_CLKOUT2_EN = "FALSE", # Disable CLKOUT2. | ||
p_CLKOUT3_EN = "FALSE", # Disable CLKOUT3. | ||
p_CLKOUT4_EN = "FALSE", # Disable CLKOUT4. | ||
p_CLKOUT5_EN = "FALSE", # Disable CLKOUT5. | ||
p_CLKOUT6_EN = "FALSE", # Disable CLKOUT6. | ||
p_CLKOUT0_DT_DIR = 1, # Static CLKOUT0 duty control direction (0-down, 1-up) | ||
p_CLKOUT1_DT_DIR = 1, # Static CLKOUT1 duty control direction (0-down, 1-up) | ||
p_CLKOUT2_DT_DIR = 1, # Static CLKOUT2 duty control direction (0-down, 1-up) | ||
p_CLKOUT3_DT_DIR = 1, # Static CLKOUT3 duty control direction (0-down, 1-up) | ||
p_CLKOUT0_DT_STEP = 0, # Static CLKOUT0 duty control step (0,1,2,4)*50ps | ||
p_CLKOUT1_DT_STEP = 0, # Static CLKOUT1 duty control step (0,1,2,4)*50ps | ||
p_CLKOUT2_DT_STEP = 0, # Static CLKOUT2 duty control step (0,1,2,4)*50ps | ||
p_CLKOUT3_DT_STEP = 0, # Static CLKOUT3 duty control step (0,1,2,4)*50ps | ||
p_CLK0_IN_SEL = 0, # Select ODIV0 source (0-VCO, 1-CLKIN) | ||
p_CLK0_OUT_SEL = 0, # Select CLKOUT0 source (0-ODIV0, 1-CLKIN) | ||
p_CLK1_IN_SEL = 0, # Select ODIV1 source (0-VCO, 1-CLKIN) | ||
p_CLK1_OUT_SEL = 0, # Select CLKOUT1 source (0-ODIV1, 1-CLKIN) | ||
p_CLK2_IN_SEL = 0, # Select ODIV2 source (0-VCO, 1-CLKIN) | ||
p_CLK2_OUT_SEL = 0, # Select CLKOUT2 source (0-ODIV2, 1-CLKIN) | ||
p_CLK3_IN_SEL = 0, # Select ODIV3 source (0-VCO, 1-CLKIN) | ||
p_CLK3_OUT_SEL = 0, # Select CLKOUT3 source (0-ODIV3, 1-CLKIN) | ||
p_CLK4_IN_SEL = 0, # Select ODIV4 source (0-VCO, 1-CLKIN) | ||
p_CLK4_OUT_SEL = 0, # Select CLKOUT4 source (0-ODIV4, 1-CLKIN) | ||
p_CLK5_IN_SEL = 0, # Select ODIV5 source (0-VCO, 1-CLKIN) | ||
p_CLK5_OUT_SEL = 0, # Select CLKOUT5 source (0-ODIV5, 1-CLKIN) | ||
p_CLKFB_SEL = "INTERNAL", # Clk Feedback type (INTERNAL, EXTERNAL). | ||
p_DYN_DPA_EN = "FALSE", # Disable dynamic phase shift. | ||
p_CLKOUT0_PE_COARSE= 0, # Static CLKOUT0 phase shift coarse config | ||
p_CLKOUT0_PE_FINE = 0, # Static CLKOUT0 phase shift fine config | ||
p_CLKOUT1_PE_COARSE= 0, # Static CLKOUT1 phase shift coarse config | ||
p_CLKOUT1_PE_FINE = 0, # Static CLKOUT1 phase shift fine config | ||
p_CLKOUT2_PE_COARSE= 0, # Static CLKOUT2 phase shift coarse config | ||
p_CLKOUT2_PE_FINE = 0, # Static CLKOUT2 phase shift fine config | ||
p_CLKOUT3_PE_COARSE= 0, # Static CLKOUT3 phase shift coarse config | ||
p_CLKOUT3_PE_FINE = 0, # Static CLKOUT3 phase shift fine config | ||
p_CLKOUT4_PE_COARSE= 0, # Static CLKOUT4 phase shift coarse config | ||
p_CLKOUT4_PE_FINE = 0, # Static CLKOUT4 phase shift fine config | ||
p_CLKOUT5_PE_COARSE= 0, # Static CLKOUT5 phase shift coarse config | ||
p_CLKOUT5_PE_FINE = 0, # Static CLKOUT5 phase shift fine config | ||
p_CLKOUT6_PE_COARSE= 0, # Static CLKOUT6 phase shift coarse config | ||
p_CLKOUT6_PE_FINE = 0, # Static CLKOUT6 phase shift fine config | ||
p_DYN_PE0_SEL = "FALSE", # Static CLKOUT0 phase shift. | ||
p_DYN_PE1_SEL = "FALSE", # Static CLKOUT1 phase shift. | ||
p_DYN_PE2_SEL = "FALSE", # Static CLKOUT2 phase shift. | ||
p_DYN_PE3_SEL = "FALSE", # Static CLKOUT3 phase shift. | ||
p_DYN_PE4_SEL = "FALSE", # Static CLKOUT4 phase shift. | ||
p_DYN_PE5_SEL = "FALSE", # Static CLKOUT5 phase shift. | ||
p_DYN_PE6_SEL = "FALSE", # Static CLKOUT6 phase shift. | ||
p_DE0_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE1_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE2_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE3_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE4_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE5_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_DE6_EN = "FALSE", # Disable CLKOUT0 duty cycle adjust | ||
p_RESET_I_EN = "FALSE", # - | ||
p_RESET_O_EN = "FALSE", # - | ||
p_SSC_EN = "FALSE", # Disable spread spectrun control. | ||
|
||
# Inputs. | ||
i_CLKIN = self.clkin, # Clk Input. | ||
i_CLKFB = 0, # Clk Feedback. | ||
i_RESET = self.reset, # PLL Reset. | ||
i_PLLPWD = 0, # PLL Power Down. | ||
i_RESET_I = 0, # PLL Partial Reset (for testing) | ||
i_RESET_O = 0, # PLL Partial Reset (for testing) | ||
i_PSDIR = 0, # Dynamic Phase Select direction. | ||
i_PSSEL = Constant(0, 3), # Dynamic Phase Select channel control. | ||
i_PSPULSE = 0, # Dynamic Phase Select pulse. | ||
i_SSCPOL = 0, # Spread Spectrum polarity. | ||
i_SSCON = 0, # Spread Spectrum enable. | ||
i_SSCMDSEL = Constant(0, 7), # Dynamic SSC MDIV integer control. | ||
i_SSCMDSEL_FRAC = Constant(0, 3), # Dynamic SSC MDIV fractional control. | ||
|
||
o_LOCK = self.locked, | ||
o_CLKOUT0 = Open(), | ||
o_CLKOUT1 = Open(), | ||
o_CLKOUT2 = Open(), | ||
o_CLKOUT3 = Open(), | ||
o_CLKOUT4 = Open(), | ||
o_CLKOUT5 = Open(), | ||
o_CLKOUT6 = Open(), | ||
o_CLKFBOUT = Open() | ||
) | ||
|
||
if self.device.startswith('GW5A-'): # GW5A-25, uses PLLA | ||
instance_name = 'PLLA' | ||
self.params.update( | ||
i_MDCLK = 0, | ||
i_MDOPC = Constant(0, 2), | ||
i_MDAINC = 0, | ||
i_MDWDI = Constant(0, 8), | ||
) | ||
else: # GW5A{,S}T, uses PLL | ||
instance_name = 'PLL' | ||
self.params.update( | ||
p_DYN_IDIV_SEL = "FALSE", # Disable dynamic IDIV. | ||
p_DYN_FBDIV_SEL = "FALSE", # Disable dynamic FBDIV. | ||
p_DYN_ODIV0_SEL = "FALSE", # Disable dynamic ODIV0. | ||
p_DYN_ODIV1_SEL = "FALSE", # Disable dynamic ODIV1. | ||
p_DYN_ODIV2_SEL = "FALSE", # Disable dynamic ODIV2. | ||
p_DYN_ODIV3_SEL = "FALSE", # Disable dynamic ODIV3. | ||
p_DYN_ODIV4_SEL = "FALSE", # Disable dynamic ODIV4. | ||
p_DYN_ODIV5_SEL = "FALSE", # Disable dynamic ODIV5. | ||
p_DYN_ODIV6_SEL = "FALSE", # Disable dynamic ODIV6. | ||
p_DYN_DT0_SEL = "FALSE", # Static CLKOUT0 duty control. | ||
p_DYN_DT1_SEL = "FALSE", # Static CLKOUT1 duty control. | ||
p_DYN_DT2_SEL = "FALSE", # Static CLKOUT2 duty control. | ||
p_DYN_DT3_SEL = "FALSE", # Static CLKOUT3 duty control. | ||
p_DYN_ICP_SEL = "FALSE", # Static ICP_SEL. | ||
# ICP_SEL determined by the toolchain | ||
p_DYN_LPF_SEL = "FALSE", # Static LPF_RES/LPF_CAP; | ||
# LPF_RES/LPF_CAP determined by the toolchain | ||
|
||
i_FBDSEL = Constant(0, 6), # Dynamic FBDIV control. | ||
i_IDSEL = Constant(0, 6), # Dynamic IDIV control. | ||
i_MDSEL = Constant(0, 7), # Dynamic MDIV integer control. | ||
i_MDSEL_FRAC = Constant(0, 3), # Dynamic MDIV fractional control. | ||
i_ODSEL0 = Constant(0, 7), # Dynamic ODIV0 integer control. | ||
i_ODSEL0_FRAC = Constant(0, 3), # Dynamic ODIV0 fractional control. | ||
i_ODSEL1 = Constant(0, 7), # Dynamic ODIV1 control. | ||
i_ODSEL2 = Constant(0, 7), # Dynamic ODIV2 control. | ||
i_ODSEL3 = Constant(0, 7), # Dynamic ODIV3 control. | ||
i_ODSEL4 = Constant(0, 7), # Dynamic ODIV4 control. | ||
i_ODSEL5 = Constant(0, 7), # Dynamic ODIV5 control. | ||
i_ODSEL6 = Constant(0, 7), # Dynamic ODIV6 control. | ||
i_DT0 = Constant(0, 4), # Dynamic duty cycle control for CLKOUT0. | ||
i_DT1 = Constant(0, 4), # Dynamic duty cycle control for CLKOUT1. | ||
i_DT2 = Constant(0, 4), # Dynamic duty cycle control for CLKOUT2. | ||
i_DT3 = Constant(0, 4), # Dynamic duty cycle control for CLKOUT3. | ||
i_ENCLK0 = 1, # Dynamic CLKOUT0 enable. | ||
i_ENCLK1 = 1, # Dynamic CLKOUT1 enable. | ||
i_ENCLK2 = 1, # Dynamic CLKOUT2 enable. | ||
i_ENCLK3 = 1, # Dynamic CLKOUT3 enable. | ||
i_ENCLK4 = 1, # Dynamic CLKOUT4 enable. | ||
i_ENCLK5 = 1, # Dynamic CLKOUT5 enable. | ||
i_ENCLK6 = 1, # Dynamic CLKOUT6 enable. | ||
i_ICPSEL = Constant(0, 6), # Dynamic ICP current control. | ||
i_LPFRES = Constant(0, 3), # Dynamic LPFRES control. | ||
i_LPFCAP = Constant(0, 2), # Dynamic LPFCAP control. | ||
) | ||
|
||
for i in range(0, len(self.clkouts)): | ||
clk, f, p, m = self.clkouts[i] | ||
self.params["o_CLKOUT%d" % i] = clk | ||
self.params["p_CLKOUT%d_EN" % i] = "TRUE" | ||
self.params["p_ODIV%d_SEL" % i] = config["odiv%d" % i] | ||
self.params["p_CLKOUT%d_PE_COARSE" % i] = config["pe%d" % i] | ||
self.params["p_CLKOUT%d_PE_FINE" % i] = config["pe%d_fine" % i] | ||
|
||
self.specials += Instance(instance_name, **self.params) |