From f69e3e583f2f3c904e588dad7fddd1d0b7a5d0e5 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Wed, 16 Feb 2022 12:30:20 +0000 Subject: [PATCH] update ECP5 PLL to accept parameters for setting arbitrary clock frequencies --- src/crg.py | 171 +++++++++++++++++++++++++++++++++++++++-------------- src/ls2.py | 4 +- 2 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/crg.py b/src/crg.py index eb3c651..fd4566b 100644 --- a/src/crg.py +++ b/src/crg.py @@ -1,40 +1,120 @@ -# Copyright (c) 2020 LambdaConcept -# Copyright (c) 2021 Luke Kenneth Casson Leighton +# This file is Copyright (c) 2020 LambdaConcept -from nmigen import (Module, Elaboratable, Instance, Signal, ClockDomain, - ClockSignal, ResetSignal) +from nmigen import * __ALL__ = ["ECPIX5CRG"] - class PLL(Elaboratable): - def __init__(self, clkin, clksel=Signal(shape=2, reset=2), - clkout1=Signal(), clkout2=Signal(), - clkout3=Signal(), clkout4=Signal(), lock=Signal(), - CLKI_DIV=1, CLKFB_DIV=2, CLK1_DIV=3, CLK2_DIV=24): + nclkouts_max = 3 + clki_div_range = (1, 128+1) + clkfb_div_range = (1, 128+1) + clko_div_range = (1, 128+1) + clki_freq_range = ( 8e6, 400e6) + clko_freq_range = (3.125e6, 400e6) + vco_freq_range = ( 400e6, 800e6) + + def __init__(self, clkin, + clksel=Signal(shape=2, reset=2), + reset=Signal(reset_less=True), + locked=Signal()): self.clkin = clkin - self.clkout1 = clkout1 - self.clkout2 = clkout2 - self.clkout3 = clkout3 - self.clkout4 = clkout4 + self.clkin_freq = None self.clksel = clksel - self.lock = lock - self.CLKI_DIV = CLKI_DIV - self.CLKFB_DIV = CLKFB_DIV - self.CLKOP_DIV = CLK1_DIV - self.CLKOS_DIV = CLK2_DIV - self.ports = [ + self.locked = locked + self.reset = reset + self.nclkouts = 0 + self.clkouts = {} + self.config = {} + self.params = {} + + def ports(self): + return [ self.clkin, - self.clkout1, - self.clkout2, - self.clkout3, - self.clkout4, self.clksel, self.lock, - ] + ] + list(self.clkouts.values()) + + def set_clkin_freq(self, freq): + (clki_freq_min, clki_freq_max) = self.clki_freq_range + assert freq >= clki_freq_min + assert freq <= clki_freq_max + self.clkin_freq = freq + + def create_clkout(self, cd, freq, phase=0, margin=1e-2): + (clko_freq_min, clko_freq_max) = self.clko_freq_range + assert freq >= clko_freq_min + assert freq <= clko_freq_max + assert self.nclkouts < self.nclkouts_max + self.clkouts[self.nclkouts] = (cd, freq, phase, margin) + #create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) + self.nclkouts += 1 + + def compute_config(self): + config = {} + for clki_div in range(*self.clki_div_range): + config["clki_div"] = clki_div + for clkfb_div in range(*self.clkfb_div_range): + all_valid = True + vco_freq = self.clkin_freq/clki_div*clkfb_div*1 # clkos3_div=1 + (vco_freq_min, vco_freq_max) = self.vco_freq_range + if vco_freq >= vco_freq_min and vco_freq <= vco_freq_max: + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + valid = False + for d in range(*self.clko_div_range): + clk_freq = vco_freq/d + if abs(clk_freq - f) <= f*m: + config["clko{}_freq".format(n)] = clk_freq + config["clko{}_div".format(n)] = d + config["clko{}_phase".format(n)] = p + valid = True + break + if not valid: + all_valid = False + else: + all_valid = False + if all_valid: + config["vco"] = vco_freq + config["clkfb_div"] = clkfb_div + #compute_config_log(self.logger, config) + return config + raise ValueError("No PLL config found") def elaborate(self, platform): + config = self.compute_config() clkfb = Signal() + self.params.update( + # attributes + a_FREQUENCY_PIN_CLKI = str(self.clkin_freq/1e6), + a_ICP_CURRENT = "6", + a_LPF_RESISTOR = "16", + a_MFG_ENABLE_FILTEROPAMP = "1", + a_MFG_GMCREF_SEL = "2", + # parameters + p_FEEDBK_PATH = "INT_OS3", # CLKOS3 rsvd for feedback with div=1. + p_CLKOS3_ENABLE = "ENABLED", + p_CLKOS3_DIV = 1, + p_CLKFB_DIV = config["clkfb_div"], + p_CLKI_DIV = config["clki_div"], + # reset, input clock, lock-achieved output + i_RST = self.reset, + i_CLKI = self.clkin, + o_LOCK = self.locked, + ) + for n, (clk, f, p, m) in sorted(self.clkouts.items()): + n_to_l = {0: "P", 1: "S", 2: "S2"} + div = config["clko{}_div".format(n)] + cphase = int(p*(div + 1)/360 + div) + self.params["p_CLKO{}_ENABLE".format(n_to_l[n])] = "ENABLED" + self.params["p_CLKO{}_DIV".format(n_to_l[n])] = div + self.params["p_CLKO{}_FPHASE".format(n_to_l[n])] = 0 + self.params["p_CLKO{}_CPHASE".format(n_to_l[n])] = cphase + self.params["o_CLKO{}".format(n_to_l[n])] = clk + + m = Module() + pll = Instance("EHXPLLL", **self.params) + m.submodules.pll = pll + return m + pll = Instance("EHXPLLL", p_OUTDIVIDER_MUXA='DIVA', p_OUTDIVIDER_MUXB='DIVB', @@ -70,14 +150,11 @@ class PLL(Elaboratable): o_CLKOS3=self.clkout4, o_LOCK=self.lock, ) - m = Module() - m.submodules += pll - return m class ECPIX5CRG(Elaboratable): - def __init__(self): - ... + def __init__(self, sys_clk_freq=100e6): + self.sys_clk_freq = sys_clk_freq def elaborate(self, platform): m = Module() @@ -94,11 +171,16 @@ class ECPIX5CRG(Elaboratable): gsr1 = Signal() m.submodules += [ - Instance("FD1S3AX", p_GSR="DISABLED", i_CK=ClockSignal("rawclk"), - i_D=~reset, o_Q=gsr0), - Instance("FD1S3AX", p_GSR="DISABLED", i_CK=ClockSignal("rawclk"), - i_D=gsr0, o_Q=gsr1), - Instance("SGSR", i_CLK=ClockSignal("rawclk"), i_GSR=gsr1), + Instance("FD1S3AX", p_GSR="DISABLED", + i_CK=ClockSignal("rawclk"), + i_D=~reset, + o_Q=gsr0), + Instance("FD1S3AX", p_GSR="DISABLED", + i_CK=ClockSignal("rawclk"), + i_D=gsr0, + o_Q=gsr1), + Instance("SGSR", i_CLK=ClockSignal("rawclk"), + i_GSR=gsr1), ] # Power-on delay (655us) @@ -110,16 +192,15 @@ class ECPIX5CRG(Elaboratable): # Generating sync2x (200Mhz) and init (25Mhz) from clk100 cd_sync2x = ClockDomain("sync2x", local=False) - cd_sync2x_unbuf = ClockDomain("sync2x_unbuf", local=False, - reset_less=True) + cd_sync2x_unbuf = ClockDomain("sync2x_unbuf", + local=False, reset_less=True) cd_init = ClockDomain("init", local=False) cd_sync = ClockDomain("sync", local=False) cd_dramsync = ClockDomain("dramsync", local=False) - m.submodules.pll = pll = PLL(ClockSignal("rawclk"), - CLKI_DIV=1, CLKFB_DIV=2, - CLK1_DIV=3, CLK2_DIV=24, - clkout1=ClockSignal("sync2x_unbuf"), - clkout2=ClockSignal("init")) + m.submodules.pll = pll = PLL(ClockSignal("rawclk"), reset=reset) + pll.set_clkin_freq(100e6) + pll.create_clkout(ClockSignal("sync2x_unbuf"), 2*self.sys_clk_freq) + pll.create_clkout(ClockSignal("init"), 25e6) m.submodules += Instance("ECLKSYNCB", i_ECLKI = ClockSignal("sync2x_unbuf"), i_STOP = 0, @@ -129,9 +210,11 @@ class ECPIX5CRG(Elaboratable): m.domains += cd_init m.domains += cd_sync m.domains += cd_dramsync - m.d.comb += ResetSignal("init").eq(~pll.lock|~pod_done) - m.d.comb += ResetSignal("sync").eq(~pll.lock|~pod_done) - m.d.comb += ResetSignal("dramsync").eq(~pll.lock|~pod_done) + reset_ok = Signal(reset_less=True) + m.d.comb += reset_ok.eq(~pll.locked|~pod_done) + m.d.comb += ResetSignal("init").eq(reset_ok) + m.d.comb += ResetSignal("sync").eq(reset_ok) + m.d.comb += ResetSignal("dramsync").eq(reset_ok) # # Generating sync (100Mhz) from sync2x diff --git a/src/ls2.py b/src/ls2.py index 7d948f6..cd3b4c8 100644 --- a/src/ls2.py +++ b/src/ls2.py @@ -64,7 +64,7 @@ class DDR3SoC(SoC, Elaboratable): firmware = "firmware/main.bin" # set up clock request generator - self.crg = ECPIX5CRG() + self.crg = ECPIX5CRG(clk_freq) # set up CPU, with 64-to-32-bit downconverters self.cpu = ExternalCore(name="ext_core") @@ -116,7 +116,7 @@ class DDR3SoC(SoC, Elaboratable): # DRAM Module if ddr_pins is not None: self.ddrphy = DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins, - sys_clk_freq=100e6)) + sys_clk_freq=clk_freq)) self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr) ddrmodule = MT41K256M16(clk_freq, "1:2") # match DDR3 ASIC P/N -- 2.30.2