Skip to content

Commit

Permalink
soc/cores/clock: initial GW5A support
Browse files Browse the repository at this point in the history
GW5A has a different PLL with GW1N/GW2A, with multiple individual
ODIV's.

Add basic support for it.

Signed-off-by: Icenowy Zheng <[email protected]>
  • Loading branch information
Icenowy committed Aug 11, 2023
1 parent 577674b commit 332e05b
Showing 1 changed file with 288 additions and 0 deletions.
288 changes: 288 additions & 0 deletions litex/soc/cores/clock/gowin_gw5a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
#
# 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):
# TODO: Support GW5A-25, and multiple outputs
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('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('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_DYN_IDIV_SEL = "FALSE", # Disable dynamic IDIV.
p_IDIV_SEL = config["idiv"], # Static IDIV value (1-64).
p_DYN_FBDIV_SEL = "FALSE", # Disable dynamic FBDIV.
p_FBDIV_SEL = config["fdiv"], # Static FBDIV value (1-64).
p_DYN_ODIV0_SEL = "FALSE", # Disable dynamic ODIV0.
p_ODIV0_SEL = 8, # Static ODIV value (1-128).
p_ODIV0_FRAC_SEL = 0, # Static ODIV0 fractional value (0-7)/8
p_DYN_ODIV1_SEL = "FALSE", # Disable dynamic ODIV1.
p_ODIV1_SEL = 8, # Static ODIV1 value
p_DYN_ODIV2_SEL = "FALSE", # Disable dynamic ODIV2.
p_ODIV2_SEL = 8, # Static ODIV2 value
p_DYN_ODIV3_SEL = "FALSE", # Disable dynamic ODIV3.
p_ODIV3_SEL = 8, # Static ODIV3 value
p_DYN_ODIV4_SEL = "FALSE", # Disable dynamic ODIV4.
p_ODIV4_SEL = 8, # Static ODIV4 value
p_DYN_ODIV5_SEL = "FALSE", # Disable dynamic ODIV5.
p_ODIV5_SEL = 8, # Static ODIV5 value
p_DYN_ODIV6_SEL = "FALSE", # Disable dynamic ODIV6.
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_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_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_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
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_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_ICPSEL = Constant(0, 6), # Dynamic ICP current control.
i_LPFRES = Constant(0, 3), # Dynamic LPFRES control.
i_LPFCAP = Constant(0, 2), # Dynamic LPFCAP control.
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_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_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()
)

# TODO: select PLLA for GW2A-25
instance_name = 'PLL'

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)

0 comments on commit 332e05b

Please sign in to comment.