Merge pull request #1651 from trabucayre/gowinpll_complete_support

soc/cores/clock/gowin_gw1n: add support for CLKOUTP, CLKOUTD, CLKOUTD3, phase and divisor
This commit is contained in:
enjoy-digital 2023-03-20 19:41:12 +01:00 committed by GitHub
commit cd933da3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 79 additions and 27 deletions

View File

@ -48,7 +48,7 @@ class GW1NOSC(Module):
# GoWin / GW1NPLL ----------------------------------------------------------------------------------
class GW1NPLL(Module):
nclkouts_max = 1
nclkouts_max = 4
def __init__(self, devicename, device, vco_margin=0):
self.logger = logging.getLogger("GW1NPLL")
@ -120,6 +120,9 @@ class GW1NPLL(Module):
self.nclkouts += 1
def compute_config(self):
# extract the highest frequency and associated margin
freq_max, m = max([(f, n) for (_, f, _, n) in self.clkouts.values()], key=lambda p: p[1])
configs = [] # corresponding VCO/FBDIV/IDIV/ODIV params + diff
for idiv in range(1, 64):
@ -134,28 +137,82 @@ class GW1NPLL(Module):
(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)):
for _n, (clk, f, p, _m) in sorted(self.clkouts.items()):
diff = abs(out_freq - f)
if diff <= f*_m:
configs.append({
"diff" : diff,
"idiv" : idiv,
"odiv" : odiv,
"vco" : vco_freq,
"fdiv" : fdiv
})
diff = abs(out_freq - freq_max)
if diff <= freq_max*m:
configs.append({
"diff" : diff,
"idiv" : idiv,
"odiv" : odiv,
"vco" : vco_freq,
"fdiv" : fdiv
})
if len(configs) == 0:
raise ValueError("No PLL config found")
return configs[min([(i, v["diff"]) for i, v in enumerate(configs)], key=lambda p: p[1])[0]]
# select better combo (where diff f_real-f_req is the smallest)
config = configs[min([(i, v["diff"]) for i, v in enumerate(configs)], key=lambda p: p[1])[0]]
# out_freq using the selected configuration
out_freq = self.clkin_freq*config["fdiv"] / config["idiv"]
# Phase
phases = list({p for (_, _, p, _) in self.clkouts.values() if p != 0})
assert len(phases) < 2 # only zero or one phase possible
# configure phase param
config["PSDA_SEL"] = f"{int(phases[0] // 22.5):04b}" if len(phases) == 1 else "0000"
# frequencies
# CLKOUT & CLKOUTP : VCODIV / 1
# CLKOUTD3 : VCODIV / 3
# CLKOUTD : VCODIV / an even value [2-128]
# FIXME: bypass may used to directly connect output clock to the input
freqs_div = [freq_max // f for (_, f, _, _) in self.clkouts.values() if freq_max // f != 1]
if len(freqs_div) > 2:
raise ValueError("Gowin PLL can't have more than two divisor")
clkoutd_div = [d for d in freqs_div if d != 3] # extracts divisor by an even value
if (len(freqs_div) == 2 and freqs_div.count(3) == 2) or (len(clkoutd_div) == 2) or \
(len(clkoutd_div) == 1 and clkoutd_div[0] % 2 != 0):
raise ValueError("Gowin PLL has two divisor: one /3 and an even divisor between 2 and 128")
# configure sdiv for CLKOUTD (if it's required)
config["SDIV_SEL"] = int(clkoutd_div[0]) if len(clkoutd_div) == 1 else 2
for c, (clock, freq, phase, margin) in self.clkouts.items():
th_div = int(freq_max // freq) # divisor to apply
r_freq = out_freq / th_div # real frequency
diff_f = abs(r_freq - freq) # diff between obtained and requested
# check if value fit criterion
if diff_f > r_freq*margin:
raise ValueError(f"Can't obtain requested frequency {diff_f} > {r_freq * margin}")
if th_div == 1: # no divisor: may be CLKOUT or CLKOUTP
if phase == 0:
out = "" # CLKOUT
else:
if "CLKOUTP" in config.keys():
raise ValueError("Only one clock with freq == freq max and a phase != 0")
out = "P"
elif th_div == 3:
out = "D3"
else:
out = "D"
config.update({
f"CLKOUT{out}" : clock,
f"CLKOUT{out}_SRC" : "CLKOUT" if phase == 0 else "CLKOUTP",
})
return config
def do_finalize(self):
assert hasattr(self, "clkin")
assert len(self.clkouts) == 1
assert len(self.clkouts) > 0 and len(self.clkouts) <= 4
config = self.compute_config()
# Based on UG286-1.3E Note.
self.params.update(
# Parameters.
p_DEVICE = self.devicename, # FPGA Device.
p_DEVICE = self.devicename, # FPGA Device.
p_FCLKIN = str(self.clkin_freq/1e6), # Clk Input frequency (MHz).
p_DYN_IDIV_SEL = "false", # Disable dynamic IDIV.
p_IDIV_SEL = config["idiv"]-1, # Static IDIV value (1-64).
@ -163,7 +220,7 @@ class GW1NPLL(Module):
p_FBDIV_SEL = config["fdiv"]-1, # Static FBDIV value (1-64).
p_DYN_ODIV_SEL = "false", # Disable dynamic ODIV.
p_ODIV_SEL = config["odiv"], # Static ODIV value.
p_PSDA_SEL = "0000", # -
p_PSDA_SEL = config["PSDA_SEL"], # -
p_DYN_DA_EN = "false", # -
p_DUTYDA_SEL = "1000", # -
p_CLKOUT_FT_DIR = 1, # -
@ -174,9 +231,7 @@ class GW1NPLL(Module):
p_CLKOUT_BYPASS = "false", # Clk Input to CLKOUT bypass.
p_CLKOUTP_BYPASS = "false", # Clk Input to CLKOUTP bypass.
p_CLKOUTD_BYPASS = "false", # Clk Input to CLKOUTD bypass.
p_DYN_SDIV_SEL = 2, # Disable dynamic SDIV.
p_CLKOUTD_SRC = "CLKOUT", # Recopy CLKOUT to CLKOUTD.
p_CLKOUTD3_SRC = "CLKOUT", # Recopy CLKOUT to CLKOUTD3.
p_DYN_SDIV_SEL = config["SDIV_SEL"], # Disable dynamic SDIV.
# Inputs.
i_CLKIN = self.clkin, # Clk Input.
@ -205,13 +260,10 @@ class GW1NPLL(Module):
i_RESET_I = 0, # IDIV reset.
i_RESET_S = 0, # SDIV and DIV3 reset.
)
clk0, f0, p0, m0 = self.clkouts[0]
self.params.update(
# Outputs.
o_LOCK = self.locked, # PLL lock status.
o_CLKOUT = clk0, # Clock output.
o_CLKOUTP = Open(), # Clock output (With phase and duty cycle adjustement).
o_CLKOUTD = Open(), # Clock divided from CLKOUT and CLKOUTP (controlled by SDIV).
o_CLKOUTD3 = Open(), # Clock divided from CLKOUT and CLKOUTP (constant division of 3).
)
for clk_name in ["CLKOUT", "CLKOUTP", "CLKOUTD", "CLKOUTD3"]:
self.params[f"o_{clk_name}"] = config.get(clk_name, Open()) # Clock output.
if clk_name in ["CLKOUTD", "CLKOUTD3"]: # Recopy CLKOUTx to CLKOUTDx
self.params[f"p_{clk_name}_SRC"] = config.get(f"{clk_name}_SRC", "CLKOUT")
self.params.update(o_LOCK=self.locked) # PLL lock status.
self.specials += Instance(instance_name, **self.params)