From 5f1a6026c86df171e701752c34101013d011f0f2 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sat, 18 Mar 2023 12:04:50 +0100 Subject: [PATCH] soc/cores/clock/gowin_gw1n: add support for CLKOUTP, CLKOUTD, CLKOUTD3, phase and divisor --- litex/soc/cores/clock/gowin_gw1n.py | 106 +++++++++++++++++++++------- 1 file changed, 79 insertions(+), 27 deletions(-) diff --git a/litex/soc/cores/clock/gowin_gw1n.py b/litex/soc/cores/clock/gowin_gw1n.py index b0700dec1..b61894aff 100644 --- a/litex/soc/cores/clock/gowin_gw1n.py +++ b/litex/soc/cores/clock/gowin_gw1n.py @@ -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)