soc/cores/clock/gowin_gw1n: add support for CLKOUTP, CLKOUTD, CLKOUTD3, phase and divisor

This commit is contained in:
Gwenhael Goavec-Merou 2023-03-18 12:04:50 +01:00
parent 6ee39b4712
commit 5f1a6026c8
1 changed files with 79 additions and 27 deletions

View File

@ -48,7 +48,7 @@ class GW1NOSC(Module):
# GoWin / GW1NPLL ---------------------------------------------------------------------------------- # GoWin / GW1NPLL ----------------------------------------------------------------------------------
class GW1NPLL(Module): class GW1NPLL(Module):
nclkouts_max = 1 nclkouts_max = 4
def __init__(self, devicename, device, vco_margin=0): def __init__(self, devicename, device, vco_margin=0):
self.logger = logging.getLogger("GW1NPLL") self.logger = logging.getLogger("GW1NPLL")
@ -120,6 +120,9 @@ class GW1NPLL(Module):
self.nclkouts += 1 self.nclkouts += 1
def compute_config(self): 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 configs = [] # corresponding VCO/FBDIV/IDIV/ODIV params + diff
for idiv in range(1, 64): for idiv in range(1, 64):
@ -134,9 +137,8 @@ class GW1NPLL(Module):
(vco_freq_min, vco_freq_max) = self.vco_freq_range (vco_freq_min, vco_freq_max) = self.vco_freq_range
if (vco_freq >= vco_freq_min*(1 + self.vco_margin) and if (vco_freq >= vco_freq_min*(1 + self.vco_margin) and
vco_freq <= vco_freq_max*(1 - self.vco_margin)): vco_freq <= vco_freq_max*(1 - self.vco_margin)):
for _n, (clk, f, p, _m) in sorted(self.clkouts.items()): diff = abs(out_freq - freq_max)
diff = abs(out_freq - f) if diff <= freq_max*m:
if diff <= f*_m:
configs.append({ configs.append({
"diff" : diff, "diff" : diff,
"idiv" : idiv, "idiv" : idiv,
@ -146,11 +148,66 @@ class GW1NPLL(Module):
}) })
if len(configs) == 0: if len(configs) == 0:
raise ValueError("No PLL config found") 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): def do_finalize(self):
assert hasattr(self, "clkin") assert hasattr(self, "clkin")
assert len(self.clkouts) == 1 assert len(self.clkouts) > 0 and len(self.clkouts) <= 4
config = self.compute_config() config = self.compute_config()
# Based on UG286-1.3E Note. # Based on UG286-1.3E Note.
self.params.update( self.params.update(
@ -163,7 +220,7 @@ class GW1NPLL(Module):
p_FBDIV_SEL = config["fdiv"]-1, # Static FBDIV value (1-64). p_FBDIV_SEL = config["fdiv"]-1, # Static FBDIV value (1-64).
p_DYN_ODIV_SEL = "false", # Disable dynamic ODIV. p_DYN_ODIV_SEL = "false", # Disable dynamic ODIV.
p_ODIV_SEL = config["odiv"], # Static ODIV value. p_ODIV_SEL = config["odiv"], # Static ODIV value.
p_PSDA_SEL = "0000", # - p_PSDA_SEL = config["PSDA_SEL"], # -
p_DYN_DA_EN = "false", # - p_DYN_DA_EN = "false", # -
p_DUTYDA_SEL = "1000", # - p_DUTYDA_SEL = "1000", # -
p_CLKOUT_FT_DIR = 1, # - p_CLKOUT_FT_DIR = 1, # -
@ -174,9 +231,7 @@ class GW1NPLL(Module):
p_CLKOUT_BYPASS = "false", # Clk Input to CLKOUT bypass. p_CLKOUT_BYPASS = "false", # Clk Input to CLKOUT bypass.
p_CLKOUTP_BYPASS = "false", # Clk Input to CLKOUTP bypass. p_CLKOUTP_BYPASS = "false", # Clk Input to CLKOUTP bypass.
p_CLKOUTD_BYPASS = "false", # Clk Input to CLKOUTD bypass. p_CLKOUTD_BYPASS = "false", # Clk Input to CLKOUTD bypass.
p_DYN_SDIV_SEL = 2, # Disable dynamic SDIV. p_DYN_SDIV_SEL = config["SDIV_SEL"], # Disable dynamic SDIV.
p_CLKOUTD_SRC = "CLKOUT", # Recopy CLKOUT to CLKOUTD.
p_CLKOUTD3_SRC = "CLKOUT", # Recopy CLKOUT to CLKOUTD3.
# Inputs. # Inputs.
i_CLKIN = self.clkin, # Clk Input. i_CLKIN = self.clkin, # Clk Input.
@ -205,13 +260,10 @@ class GW1NPLL(Module):
i_RESET_I = 0, # IDIV reset. i_RESET_I = 0, # IDIV reset.
i_RESET_S = 0, # SDIV and DIV3 reset. i_RESET_S = 0, # SDIV and DIV3 reset.
) )
clk0, f0, p0, m0 = self.clkouts[0] for clk_name in ["CLKOUT", "CLKOUTP", "CLKOUTD", "CLKOUTD3"]:
self.params.update( self.params[f"o_{clk_name}"] = config.get(clk_name, Open()) # Clock output.
# Outputs. if clk_name in ["CLKOUTD", "CLKOUTD3"]: # Recopy CLKOUTx to CLKOUTDx
o_LOCK = self.locked, # PLL lock status. self.params[f"p_{clk_name}_SRC"] = config.get(f"{clk_name}_SRC", "CLKOUT")
o_CLKOUT = clk0, # Clock output.
o_CLKOUTP = Open(), # Clock output (With phase and duty cycle adjustement). self.params.update(o_LOCK=self.locked) # PLL lock status.
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).
)
self.specials += Instance(instance_name, **self.params) self.specials += Instance(instance_name, **self.params)