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

Use MMCME4_ADV in USPMMCM to enable finer-grained clock output ctrl #2117

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
66 changes: 65 additions & 1 deletion litex/soc/cores/clock/xilinx_usp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from litex.soc.cores.clock.common import *
from litex.soc.cores.clock.xilinx_common import *
from typing import Dict, Any
import math

# Xilinx / Ultrascale Plus -------------------------------------------------------------------------

Expand Down Expand Up @@ -106,7 +108,69 @@ def do_finalize(self):
self.params["p_CLKOUT{}_DIVIDE".format(n)] = config["clkout{}_divide".format(n)]
self.params["p_CLKOUT{}_PHASE".format(n)] = config["clkout{}_phase".format(n)]
self.params["o_CLKOUT{}".format(n)] = clk
self.specials += Instance("MMCME2_ADV", **self.params)
self.specials += Instance("MMCME4_ADV", **self.params)

def compute_config(self) -> Dict[str, Any]:
"""
Computes the MMCM configuration based on input parameters.

Returns:
Dict[str, Any]: A dictionary containing MMCM configuration parameters.

Raises:
ValueError: If no valid MMCM configuration is found.
"""
vco_min_margin = self.vco_freq_range[0] * (1 + self.vco_margin)
vco_max_margin = self.vco_freq_range[1] * (1 - self.vco_margin)
# ref: https://docs.amd.com/r/en-US/ug572-ultrascale-clocking/MMCM-Attributes
# CLKFBOUT_MULT_F: 2.0 to 128.0 with step 0.125
clkfbout_mult_f_values = [x / 8 for x in range(16, 1025)]

for divclk_divide in range(*self.divclk_divide_range):
for clkfbout_mult in reversed(clkfbout_mult_f_values):
vco_freq = self.clkin_freq * clkfbout_mult / divclk_divide
if not (vco_min_margin <= vco_freq <= vco_max_margin):
continue # vco_freq out of range

config: Dict[str, Any] = {
"divclk_divide": divclk_divide,
"clkfbout_mult": clkfbout_mult,
"vco": vco_freq
}
all_valid = True
for n, (clk, f, p, m) in sorted(self.clkouts.items()):
valid = False

dividers = list(clkdiv_range(*self.clkout_divide_range))
# Add specific range dividers if they exist
if specific_div_range := getattr(self, f"clkout{n}_divide_range", None):
dividers.extend(clkdiv_range(*specific_div_range))

# For clkout0, CLKOUT[0]_DIVIDE_F also has range 2.0 to 128.0 with step 0.125
if n == 0:
dividers = [x / 8 for x in range(16, 1025)]

for d in dividers:
clk_freq = vco_freq / d
if not math.isclose(clk_freq, f, rel_tol=m):
# if abs(clk_freq - f) <= f * m:
continue

config[f"clkout{n}_freq"] = clk_freq
config[f"clkout{n}_divide"] = d
config[f"clkout{n}_phase"] = p
valid = True
break # Valid divider found

if not valid:
all_valid = False
break # Exit early if any clock output is invalid

if all_valid:
compute_config_log(self.logger, config)
return config

raise ValueError("No MMCM config found")


class USPIDELAYCTRL(LiteXModule):
Expand Down