| """ |
| This is an auxiliary script that is used to compute valid PLL values to set |
| the CPU frequency to a given value. The algorithm here appears as C code |
| for the machine.freq() function. |
| """ |
| |
| from __future__ import print_function |
| import re |
| |
| class MCU: |
| def __init__(self, range_sysclk, range_m, range_n, range_p, range_q, range_vco_in, range_vco_out): |
| self.range_sysclk = range_sysclk |
| self.range_m = range_m |
| self.range_n = range_n |
| self.range_p = range_p |
| self.range_q = range_q |
| self.range_vco_in = range_vco_in |
| self.range_vco_out = range_vco_out |
| |
| mcu_default = MCU( |
| range_sysclk=range(2, 216 + 1, 2), |
| range_m=range(2, 63 + 1), |
| range_n=range(192, 432 + 1), |
| range_p=range(2, 8 + 1, 2), |
| range_q=range(2, 15 + 1), |
| range_vco_in=range(1, 2 + 1), |
| range_vco_out=range(192, 432 + 1), |
| ) |
| |
| mcu_h7 = MCU( |
| range_sysclk=range(2, 400 + 1, 2), # above 400MHz currently unsupported |
| range_m=range(1, 63 + 1), |
| range_n=range(4, 512 + 1), |
| range_p=range(2, 128 + 1, 2), |
| range_q=range(1, 128 + 1), |
| range_vco_in=range(1, 16 + 1), |
| range_vco_out=range(150, 960 + 1), # 150-420=medium, 192-960=wide |
| ) |
| |
| def close_int(x): |
| return abs(x - round(x)) < 0.01 |
| |
| # original version that requires N/M to be an integer (for simplicity) |
| def compute_pll(hse, sys): |
| for P in (2, 4, 6, 8): # allowed values of P |
| Q = sys * P / 48 |
| NbyM = sys * P / hse |
| # N/M and Q must be integers |
| if not (close_int(NbyM) and close_int(Q)): |
| continue |
| # VCO_OUT must be between 192MHz and 432MHz |
| if not (192 <= hse * NbyM <= 432): |
| continue |
| # compute M |
| M = int(192 // NbyM) |
| while hse > 2 * M or NbyM * M < 192: |
| M += 1 |
| # VCO_IN must be between 1MHz and 2MHz (2MHz recommended) |
| if not (M <= hse): |
| continue |
| # compute N |
| N = NbyM * M |
| # N and Q are restricted |
| if not (192 <= N <= 432 and 2 <= Q <= 15): |
| continue |
| # found valid values |
| assert NbyM == N // M |
| return (M, N, P, Q) |
| # no valid values found |
| return None |
| |
| # improved version that doesn't require N/M to be an integer |
| def compute_pll2(hse, sys, relax_pll48): |
| # Loop over the allowed values of P, looking for a valid PLL configuration |
| # that gives the desired "sys" frequency. |
| fallback = None |
| for P in mcu.range_p: |
| # VCO_OUT must be between 192MHz and 432MHz |
| if not sys * P in mcu.range_vco_out: |
| continue |
| NbyM = float(sys * P) / hse # float for Python 2 |
| # scan M |
| M_min = mcu.range_n[0] // int(round(NbyM)) # starting value |
| while mcu.range_vco_in[-1] * M_min < hse: |
| M_min += 1 |
| # VCO_IN must be >=1MHz, but higher is better for stability so start high (low M) |
| for M in range(M_min, hse + 1): |
| # compute N |
| N = NbyM * M |
| # N must be an integer |
| if not close_int(N): |
| continue |
| N = round(N) |
| # N is restricted |
| if N not in mcu.range_n: |
| continue |
| Q = float(sys * P) / 48 # float for Python 2 |
| # Q must be an integer in a set range |
| if close_int(Q) and round(Q) in mcu.range_q: |
| # found valid values |
| return (M, N, P, Q) |
| # Re-try Q to get at most 48MHz |
| Q = (sys * P + 47) // 48 |
| if Q not in mcu.range_q: |
| continue |
| if fallback is None: |
| # the values don't give 48MHz on PLL48 but are otherwise OK |
| fallback = M, N, P, Q |
| if relax_pll48: |
| # might have found values which don't give 48MHz on PLL48 |
| return fallback |
| else: |
| # no valid values found which give 48MHz on PLL48 |
| return None |
| |
| def compute_derived(hse, pll): |
| hse = float(hse) # float for Python 2 |
| M, N, P, Q = pll |
| vco_in = hse / M |
| vco_out = hse * N / M |
| pllck = hse / M * N / P |
| pll48ck = hse / M * N / Q |
| return (vco_in, vco_out, pllck, pll48ck) |
| |
| def verify_pll(hse, pll): |
| M, N, P, Q = pll |
| vco_in, vco_out, pllck, pll48ck = compute_derived(hse, pll) |
| |
| # verify ints |
| assert close_int(M) |
| assert close_int(N) |
| assert close_int(P) |
| assert close_int(Q) |
| |
| # verify range |
| assert M in mcu.range_m |
| assert N in mcu.range_n |
| assert P in mcu.range_p |
| assert Q in mcu.range_q |
| assert mcu.range_vco_in[0] <= vco_in <= mcu.range_vco_in[-1] |
| assert mcu.range_vco_out[0] <= vco_out <= mcu.range_vco_out[-1] |
| |
| def compute_pll_table(source_clk, relax_pll48): |
| valid_plls = [] |
| for sysclk in mcu.range_sysclk: |
| pll = compute_pll2(source_clk, sysclk, relax_pll48) |
| if pll is not None: |
| verify_pll(source_clk, pll) |
| valid_plls.append((sysclk, pll)) |
| return valid_plls |
| |
| def generate_c_table(hse, valid_plls): |
| valid_plls.sort() |
| if mcu.range_sysclk[-1] <= 0xff and mcu.range_m[-1] <= 0x3f and mcu.range_p[-1] // 2 - 1 <= 0x3: |
| typedef = 'uint16_t' |
| sys_mask = 0xff |
| m_shift = 10 |
| m_mask = 0x3f |
| p_shift = 8 |
| p_mask = 0x3 |
| else: |
| typedef = 'uint32_t' |
| sys_mask = 0xffff |
| m_shift = 24 |
| m_mask = 0xff |
| p_shift = 16 |
| p_mask = 0xff |
| print("#define PLL_FREQ_TABLE_SYS(pll) ((pll) & %d)" % (sys_mask,)) |
| print("#define PLL_FREQ_TABLE_M(pll) (((pll) >> %d) & %d)" % (m_shift, m_mask)) |
| print("#define PLL_FREQ_TABLE_P(pll) (((((pll) >> %d) & %d) + 1) * 2)" % (p_shift, p_mask)) |
| print("typedef %s pll_freq_table_t;" % (typedef,)) |
| print("// (M, P/2-1, SYS) values for %u MHz source" % hse) |
| print("static const pll_freq_table_t pll_freq_table[%u] = {" % (len(valid_plls),)) |
| for sys, (M, N, P, Q) in valid_plls: |
| print(" (%u << %u) | (%u << %u) | %u," % (M, m_shift, P // 2 - 1, p_shift, sys), end='') |
| if M >= 2: |
| vco_in, vco_out, pllck, pll48ck = compute_derived(hse, (M, N, P, Q)) |
| print(" // M=%u N=%u P=%u Q=%u vco_in=%.2f vco_out=%.2f pll48=%.2f" |
| % (M, N, P, Q, vco_in, vco_out, pll48ck), end='' |
| ) |
| print() |
| print("};") |
| |
| def print_table(hse, valid_plls): |
| print("HSE =", hse, "MHz") |
| print("sys : M N P Q : VCO_IN VCO_OUT PLLCK PLL48CK") |
| out_format = "%3u : %2u %.1f %.2f %.2f : %5.2f %6.2f %6.2f %6.2f" |
| for sys, pll in valid_plls: |
| print(out_format % ((sys,) + pll + compute_derived(hse, pll))) |
| print("found %u valid configurations" % len(valid_plls)) |
| |
| def search_header_for_hsx_values(filename, vals): |
| regex_inc = re.compile(r'#include "(boards/[A-Za-z0-9_./]+)"') |
| regex_def = re.compile(r'#define +(HSE_VALUE|HSI_VALUE) +\((\(uint32_t\))?([0-9]+)\)') |
| with open(filename) as f: |
| for line in f: |
| line = line.strip() |
| m = regex_inc.match(line) |
| if m: |
| # Search included file |
| search_header_for_hsx_values(m.group(1), vals) |
| continue |
| m = regex_def.match(line) |
| if m: |
| # Found HSE_VALUE or HSI_VALUE |
| val = int(m.group(3)) // 1000000 |
| if m.group(1) == 'HSE_VALUE': |
| vals[0] = val |
| else: |
| vals[1] = val |
| return vals |
| |
| def main(): |
| global mcu |
| global out_format |
| |
| # parse input args |
| import sys |
| argv = sys.argv[1:] |
| |
| c_table = False |
| mcu_series = 'f4' |
| hse = None |
| hsi = None |
| |
| while True: |
| if argv[0] == '-c': |
| c_table = True |
| argv.pop(0) |
| elif argv[0] == '-m': |
| argv.pop(0) |
| mcu_series = argv.pop(0).lower() |
| else: |
| break |
| |
| if len(argv) != 1: |
| print("usage: pllvalues.py [-c] [-m <mcu_series>] <hse in MHz>") |
| sys.exit(1) |
| |
| if argv[0].startswith("file:"): |
| # extract HSE_VALUE, and optionally HSI_VALUE, from header file |
| hse, hsi = search_header_for_hsx_values(argv[0][5:], [None, None]) |
| if hse is None: |
| raise ValueError("%s does not contain a definition of HSE_VALUE" % argv[0]) |
| if hsi is not None and hsi > 16: |
| # Currently, a HSI value greater than 16MHz is not supported |
| hsi = None |
| else: |
| # HSE given directly as an integer |
| hse = int(argv[0]) |
| |
| # Select MCU parameters |
| if mcu_series == 'h7': |
| mcu = mcu_h7 |
| else: |
| mcu = mcu_default |
| |
| # Relax constraight on PLLQ being 48MHz on F7 and H7 MCUs, which have separate PLLs for 48MHz |
| relax_pll48 = mcu_series in ('f7', 'h7') |
| |
| hse_valid_plls = compute_pll_table(hse, relax_pll48) |
| if hsi is not None: |
| hsi_valid_plls = compute_pll_table(hsi, relax_pll48) |
| |
| if c_table: |
| print('#if MICROPY_HW_CLK_USE_HSI') |
| if hsi is not None: |
| hsi_valid_plls.append((hsi, (0, 0, 2, 0))) |
| generate_c_table(hsi, hsi_valid_plls) |
| print('#else') |
| if hsi is not None: |
| hse_valid_plls.append((hsi, (0, 0, 2, 0))) |
| hse_valid_plls.append((hse, (1, 0, 2, 0))) |
| generate_c_table(hse, hse_valid_plls) |
| print('#endif') |
| else: |
| print_table(hse, hse_valid_plls) |
| |
| if __name__ == "__main__": |
| main() |