Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Efinix PLL calc when feedback != INTERNAL #1850

Merged
merged 2 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions litex/build/efinix/ifacewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,11 @@ def generate_pll(self, block, partnumber, verbose=True):

else:
cmd += 'design.set_property("{}","EXT_CLK","EXT_CLK{}","PLL")\n'.format(name, block["clock_no"])
# FIXME: pll feedback
cmd += 'design.set_property("{}","FEEDBACK_MODE","INTERNAL","PLL")\n'.format(name)
if block["feedback"] != -1:
cmd += 'design.set_property("{}","FEEDBACK_MODE","{}","PLL")\n'.format(name, "CORE" if block["feedback"] == 0 else "LOCAL")
cmd += 'design.set_property("{}","FEEDBACK_CLK","CLK{}","PLL")\n'.format(name, block["feedback"])
else:
cmd += 'design.set_property("{}","FEEDBACK_MODE","INTERNAL","PLL")\n'.format(name)
cmd += 'design.assign_resource("{}","{}","PLL")\n'.format(name, block["resource"])


Expand Down Expand Up @@ -326,15 +329,26 @@ def generate_pll(self, block, partnumber, verbose=True):
else:
cmd += 'design.set_property("{}","CLKOUT{}_PHASE_SETTING","{}","PLL")\n'.format(name, i, clock[2] // 45)

cmd += "target_freq = {\n"
for i, clock in enumerate(block["clk_out"]):
cmd += ' "CLKOUT{}_FREQ": "{}",\n'.format(i, clock[1] / 1e6)
cmd += ' "CLKOUT{}_PHASE": "{}",\n'.format(i, clock[2])
if clock[4] == 1:
cmd += ' "CLKOUT{}_DYNPHASE_EN": "1",\n'.format(i)
cmd += "}\n"

cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n'.format(name)
if block["feedback"] == -1:
cmd += "target_freq = {\n"
for i, clock in enumerate(block["clk_out"]):
cmd += ' "CLKOUT{}_FREQ": "{}",\n'.format(i, clock[1] / 1e6)
cmd += ' "CLKOUT{}_PHASE": "{}",\n'.format(i, clock[2])
if clock[4] == 1:
cmd += ' "CLKOUT{}_DYNPHASE_EN": "1",\n'.format(i)
cmd += "}\n"

cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n'.format(name)
cmd += 'for c in calc_result:\n'
cmd += ' print(c)\n'
else:
cmd += 'design.set_property("{}","M","{}","PLL")\n'.format(name, block["M"])
cmd += 'design.set_property("{}","N","{}","PLL")\n'.format(name, block["N"])
cmd += 'design.set_property("{}","O","{}","PLL")\n'.format(name, block["O"])
for i, clock in enumerate(block["clk_out"]):
cmd += 'design.set_property("{}","CLKOUT{}_PHASE","{}","PLL")\n'.format(name, i, clock[2])
#cmd += 'design.set_property("{}","CLKOUT{}_FREQ","{}","PLL")\n'.format(name, i, clock[2])
cmd += 'design.set_property("{}","CLKOUT{}_DIV","{}","PLL")\n'.format(name, i, block[f"CLKOUT{i}_DIV"])

if "extra" in block:
cmd += block["extra"]
Expand Down
209 changes: 205 additions & 4 deletions litex/soc/cores/clock/efinix.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, platform,version="V1_V2", dyn_phase_shift_pads=[]):
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"]
Expand Down Expand Up @@ -99,7 +100,7 @@ def register_clkin(self, clkin, freq, name="", refclk_name="", lvds_input=False)

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
Expand All @@ -116,23 +117,191 @@ def create_clkout(self, cd, freq, phase=0, margin=0, name="", with_reset=True, d

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):
block = self.platform.toolchain.ifacewriter.get_block(self.name)
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 -----------------------------------------------------------------------------

Expand All @@ -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]