soc/cores/clock/efinix: calc PLL parameters for Trion when feedback != INTERNAL

This commit is contained in:
Gwenhael Goavec-Merou 2023-12-06 17:02:53 +01:00
parent 08a62d4b5f
commit 491a207a37
1 changed files with 205 additions and 4 deletions

View File

@ -45,6 +45,7 @@ class EFINIXPLL(LiteXModule):
block["locked"] = self.name + "_locked"
block["rstn"] = self.name + "_rstn"
block["version"] = version
block["feedback"] = -1
if len(dyn_phase_shift_pads) > 0:
block["shift_ena"] = dyn_phase_shift_pads["shift_ena"]
block["shift"] = dyn_phase_shift_pads["shift"]
@ -99,7 +100,7 @@ class EFINIXPLL(LiteXModule):
self.logger.info("Use {}".format(colorer(block["resource"], "green")))
def create_clkout(self, cd, freq, phase=0, margin=0, name="", with_reset=True, dyn_phase=False):
def create_clkout(self, cd, freq, phase=0, margin=0, name="", with_reset=True, dyn_phase=False, is_feedback=False):
assert self.nclkouts < self.nclkouts_max
clk_out_name = f"{self.name}_clkout{self.nclkouts}" if name == "" else name
@ -116,9 +117,14 @@ class EFINIXPLL(LiteXModule):
create_clkout_log(self.logger, clk_out_name, freq, margin, self.nclkouts)
block = self.platform.toolchain.ifacewriter.get_block(self.name)
if is_feedback:
assert block["feedback"] == -1
block["feedback"] = self.nclkouts
self.nclkouts += 1
block = self.platform.toolchain.ifacewriter.get_block(self.name)
block["clk_out"].append([clk_out_name, freq, phase, margin, dyn_phase])
def extra(self, extra):
@ -126,13 +132,176 @@ class EFINIXPLL(LiteXModule):
block["extra"] = extra
def compute_config(self):
pass
import math
block = self.platform.toolchain.ifacewriter.get_block(self.name)
if block["feedback"] == -1:
return
clks_out = {}
for clk_id, clk in enumerate(block["clk_out"]):
clks_out[clk_id] = {"freq": clk[1], "phase": clk[2]}
n_out = self.nclkouts
clk_in_freq = block["input_freq"]
clk_fb_id = block["feedback"]
device = self.platform.device
vco_range = self.get_vco_freq_range(device)
pfd_range = self.get_pfd_freq_range(device)
pll_range = self.get_pll_freq_range(device)
if n_out > 1:
O_fact = [2, 4, 8]
else:
O_fact = [1, 2, 4, 8]
# Pre-Divider (N).
# -----------------
# F_PFD is between 10e6 and 100e6
# so limit search to only acceptable factors
N_min = int(math.ceil(clk_in_freq / pfd_range[1]))
N_max = int(math.floor(clk_in_freq / pfd_range[0]))
## limit
### when fin is below FPLL_MAX min is < 1
if N_min < 1:
N_min = 1
### when fin is above FPLL_MIN and/or near max possible freq max is > 15
if N_max > 15:
N_max = 15
# Multiplier (M).
# ---------------
## 1. needs to know all ffbk * o * cfbk acceptable to max FVCO range
oc_range = []
oc_min = 256*8
oc_max = 0
clk_fb_freq = clks_out[clk_fb_id]["freq"]
clk_fb_phase = clks_out[clk_fb_id]["phase"]
c_range = self.get_c_range(device, clk_fb_phase)
# FIXME: c_range must be limited to min/max
for c in c_range: # 1. iterate around C factor and check fPLL
if clk_fb_freq * c < pll_range[0] or clk_fb_freq > pll_range[1]:
continue
for o in O_fact:
oc = o * c
fvco_tmp = clk_fb_freq * oc
if fvco_tmp >= vco_range[0] and fvco_tmp <= vco_range[1]:
oc_range.append([o, c])
# again get range
if oc > oc_max:
oc_max = oc
if oc < oc_min:
oc_min = oc
## 2. compute FVCO equation with informations already obtained
## ie try all possible Fpfd freqs and try to find M with FVCO respect
## when params are valid try to find Cx for each clock output enabled
params_list = []
for n in range(N_min, N_max + 1):
fpfd_tmp = clk_in_freq / n
# limit range using FVCO_MAX & FVCO_MIN
# fVCO = fPFD * M * O * Cfbk
# so:
# fVCO
# M = ---------------
# fPFD * O * Cfbf
#
M_min = int(math.ceil(vco_range[0] / (fpfd_tmp * oc_max)))
M_max = int(math.floor(vco_range[1] / (fpfd_tmp * oc_min)))
if M_min < 1:
M_min = 1
if M_max > 255:
M_max = 255
for m in range(M_min, M_max + 1):
for oc in oc_range:
[o, c] = oc
fvco_tmp = fpfd_tmp * m * o * c
if fvco_tmp >= vco_range[0] and fvco_tmp <= vco_range[1]:
# m * o * c must be below 256
if m * o * c > 255:
continue
fpll_tmp = fvco_tmp / o
cx_list = []
for clk_id, clk_cfg in clks_out.items():
found = False
c_div = self.get_c_range(device, clk_cfg["phase"])
c_list = []
for cx in c_div:
if clk_id == clk_fb_id and cx != c:
continue
clk_out = fpll_tmp / cx
# if a C is found: no need to search more
if clk_out == clk_cfg["freq"]:
cx_list.append(cx)
found = True
break
# no solution found for this clk: params are uncompatibles
if found == False:
break
if len(cx_list) == 2:
params_list.append([n, m, o, c, cx_list[0], cx_list[1]])
vco_max_freq = 0
o_div_max = 0
params_list2 = []
for p in params_list:
(n, m, o, c, c0, c1) = p
fpfd_tmp = clk_in_freq / n
fvco_tmp = fpfd_tmp * m * o * c
if o > o_div_max:
o_div_max = o
# Interface designer always select high VCO freq
if fvco_tmp > vco_max_freq:
vco_max_freq = fvco_tmp
params_list2.clear()
fpll_tmp = fvco_tmp / o
if fvco_tmp == vco_max_freq:
params_list2.append({
"fvco" : fvco_tmp,
"fpll" : fpll_tmp,
"fpfd" : fpfd_tmp,
"M" : m,
"N" : n,
"O" : o,
"Cfbk" : c,
"c0" : c0,
"c1" : c1,
})
# Again: Interface Designer prefers high O divider.
# -------------------------------------------------
final_list = []
for p in params_list2:
if p["O"] == o_div_max:
final_list.append(p)
assert len(final_list) != 0
# Select first parameters set.
# ----------------------------
final_list = final_list[0]
# Fill block with PLL configuration parameters.
# ---------------------------------------------
block["M"] = final_list["M"]
block["N"] = final_list["N"]
block["O"] = final_list["O"]
block["VCO_FREQ"] = final_list["fvco"]
for i in range(self.nclkouts):
block[f"CLKOUT{i}_DIV"] = final_list[f"c{i}"]
def set_configuration(self):
pass
def do_finalize(self):
pass
# FIXME
if not self.platform.family == "Trion":
return
# compute PLL configuration and arbitrary select first result
self.compute_config()
# Efinix / TITANIUMPLL -----------------------------------------------------------------------------
@ -147,3 +316,35 @@ class TRIONPLL(EFINIXPLL):
nclkouts_max = 3
def __init__(self, platform):
EFINIXPLL.__init__(self, platform, version="V1_V2")
@staticmethod
def get_vco_freq_range(device):
FVCO_MIN = 500e6
FVCO_MAX = 3600e6 # Local/Core, 1600 otherwise
#FVCO_MIN = 1600e6
return (FVCO_MIN, FVCO_MAX)
@staticmethod
def get_pfd_freq_range(device):
FPFD_MIN = 10e6
FPFD_MAX = 100e6
return (FPFD_MIN, FPFD_MAX)
@staticmethod
def get_pll_freq_range(device):
FPLL_MIN = 62.5e6
FPLL_MAX = 1800e6 # when all C are < 64 else 1400
return (FPLL_MIN, FPLL_MAX)
@staticmethod
def get_c_range(device, phase=0):
if phase == 0:
return [i for i in range(1, 257)]
return {
45: [4],
90: [2, 4, 6],
135: [4],
180: [2],
270: [2]
}[phase]