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:
commit
cd933da3ac
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue