From 38aa8f10e7c16b7f30b233f4c140d0d5d84286ed Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 3 Nov 2023 10:55:12 +0000 Subject: [PATCH 001/118] Tidy the pll_sim function --- python/sw_pll/sw_pll_sim.py | 48 ++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 30c92776..ba75391b 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -479,17 +479,23 @@ def plot_modulated_fft(self, filename, waveform): plt.savefig(filename, dpi=150) -def run_sim(target_output_frequency, nominal_ref_frequency, lut_lookup_function, lut_size, verbose=False): +def run_sim(target_output_frequency, + nominal_ref_frequency, + lut_lookup_function, + lut_size, + Kp, + Ki, + simulation_iterations, + ppm_shifts, + jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for + test_tone_hz=1000, + verbose=False): """ This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response of the sw_pll to changes in input reference frequency. A plot of the simulation is generated to allow visual inspection and tuning. """ - # PI loop control constants - Kp = 0.0 - Ki = 1.0 - ref_frequency = nominal_ref_frequency sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) @@ -501,14 +507,7 @@ def run_sim(target_output_frequency, nominal_ref_frequency, lut_lookup_function, freq_log = [] target_log = [] - - simulation_iterations = 1500 - - # Move the reference frequency about - iteration count, PPM change - ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) - # ppm_shifts = () # Straight run with no PPM deviation - - test_tone_hz = 1000 + audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) for count in range(simulation_iterations): @@ -516,8 +515,7 @@ def run_sim(target_output_frequency, nominal_ref_frequency, lut_lookup_function, output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate # Add some jitter to the output_count to test jitter compensation - # output_sample_jitter = 0 - output_sample_jitter = 100 * (np.random.sample() - 0.5) + output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) output_count_end_float += output_count_float_inc + output_sample_jitter # Compensate for the jitter period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc @@ -581,7 +579,7 @@ def run_sim(target_output_frequency, nominal_ref_frequency, lut_lookup_function, fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance """ -ref_to_loop_call_rate = 512 +ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz xtal_frequency = 24000000 profile_choice = 0 @@ -651,4 +649,20 @@ def run_sim(target_output_frequency, nominal_ref_frequency, lut_lookup_function, print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") print(f"LUT entries: {steps} ({steps*2} bytes)") - run_sim(target_output_frequency, nominal_ref_frequency, error_from_h.get_output_frequency_from_error, error_from_h.get_lut_size(), verbose=False) + + # PI loop control constants + Kp = 0.0 + Ki = 1.0 + simulation_iterations = 150 + ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) + ppm_shifts = () # Straight run with no PPM deviation + + run_sim(target_output_frequency, + nominal_ref_frequency, + error_from_h.get_output_frequency_from_error, + error_from_h.get_lut_size(), + Kp, + Ki, + simulation_iterations, + ppm_shifts, + verbose=False) From 7b4eea248a4a1facd011ea0036e7bebed3f027b6 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 8 Nov 2023 12:17:33 +0000 Subject: [PATCH 002/118] WIP decompose model into blocks --- python/sw_pll/app_pll_model.py | 203 +++++++++++++++++++++++ python/sw_pll/controller_model.py | 87 ++++++++++ python/sw_pll/dco_model.py | 259 ++++++++++++++++++++++++++++++ python/sw_pll/pfd_model.py | 251 +++++++++++++++++++++++++++++ 4 files changed, 800 insertions(+) create mode 100644 python/sw_pll/app_pll_model.py create mode 100644 python/sw_pll/controller_model.py create mode 100644 python/sw_pll/dco_model.py create mode 100644 python/sw_pll/pfd_model.py diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py new file mode 100644 index 00000000..b6c70a03 --- /dev/null +++ b/python/sw_pll/app_pll_model.py @@ -0,0 +1,203 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import subprocess +import re +from pathlib import Path + +register_file = "register_setup.h" # can be changed as needed. This contains the register setup params and is accessible via C in the firmware + +class app_pll_frac_calc: + """ + This class uses the formula in the XU316 datasheet to calculate the output frequency of the + application PLL (sometimes called secondary PLL) from the register settings provided. + It uses the checks specified in the datasheet to ensure the settings are valid, and will assert if not. + To keep the inherent jitter of the PLL output down to a minimum, it is recommended that R be kept small, + ideally = 0 (which equiates to 1) but reduces lock range. + """ + def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD_init, verbose=False): + self.input_frequency = input_frequency + self.F = F_init + self.R = R_init + self.OD = OD_init + self.ACD = ACD_init + self.f = f_init # fractional multiplier (+1.0) + self.p = p_init # fractional fivider (+1.0) + self.output_frequency = None + self.lock_status_state = 0 + self.verbose = verbose + + self.calc_frequency() + + def calc_frequency(self): + if self.verbose: + print(f"F: {self.F} R: {self.R} OD: {self.OD} ACD: {self.ACD} f: {self.f} p: {self.p}") + print(f"input_frequency: {self.input_frequency}") + assert self.F >= 1 and self.F <= 8191, f"Invalid F setting {self.F}" + assert type(self.F) is int, f"Error: F must be an INT" + assert self.R >= 0 and self.R <= 63, f"Invalid R setting {self.R}" + assert type(self.R) is int, f"Error: R must be an INT" + assert self.OD >= 0 and self.OD <= 7, f"Invalid OD setting {self.OD}" + assert type(self.OD) is int, f"Error: OD must be an INT" + + intermediate_freq = self.input_frequency * (self.F + 1.0) / 2.0 / (self.R + 1.0) + assert intermediate_freq >= 360000000.0 and intermediate_freq <= 1800000000.0, f"Invalid VCO freq: {intermediate_freq}" + # print(f"intermediate_freq: {intermediate_freq}") + + assert type(self.p) is int, f"Error: r must be an INT" + assert type(self.f) is int, f"Error: f must be an INT" + + assert self.p > self.f, "Error f is not < p: {self.f} {self.p}" + + # From XU316-1024-QF60A-xcore.ai-Datasheet_22.pdf + self.output_frequency = self.input_frequency * (self.F + 1.0 + ((self.f + 1) / (self.p + 1)) ) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1)) + + return self.output_frequency + + def get_output_frequency(self): + return self.output_frequency + + def update_all(self, F, R, OD, ACD, f, p): + self.F = F + self.R = R + self.OD = OD + self.ACD = ACD + self.f = f + self.p = p + return self.calc_frequency() + + def update_frac(self, f, p): + self.f = f + self.p = p + return self.calc_frequency() + + def update_frac_reg(self, reg): + """determine f and p from the register number and recalculate frequency""" + f = int((reg >> 8) & ((2**8)-1)) + p = int(reg & ((2**8)-1)) + return self.update_frac(f, p) + + # see /doc/sw_pll.rst for guidance on these settings +def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95): + """ + This is a wrapper function for pll_calc.py and allows it to be called programatically. + It contains sensible defaults for the arguments and abstracts some of the complexity away from + the underlying script. Configuring the PLL is not an exact science and there are many tradeoffs involved. + See sw_pll.rst for some of the tradeoffs involved and some example paramater sets. + + Once run, this function saves two output files: + - fractions.h which contains the fractional term lookup table, which is guarranteed monotonic (important for PI stability) + - register_setup.h which contains the PLL settings in comments as well as register settings for init in the application + + This function and the underlying call to pll_calc may take several seconds to complete since it searches a range + of possible solutions numerically. + """ + input_frequency_MHz = input_frequency / 1000000.0 + target_output_frequency_MHz = target_output_frequency / 1000000.0 + + calc_script = Path(__file__).parent/"pll_calc.py" + + # input freq, app pll, max denom, output freq, min phase comp freq, max ppm error, raw, fractional range, make header + cmd = f"{calc_script} -i {input_frequency_MHz} -a -m {max_denom} -t {target_output_frequency_MHz} -p 6.0 -e {int(ppm_max)} -r --fracmin {fracmin} --fracmax {fracmax} --header" + print(f"Running: {cmd}") + output = subprocess.check_output(cmd.split(), text=True) + + # Get each solution + solutions = [] + Fs = [] + regex = r"Found solution.+\nAPP.+\nAPP.+\nAPP.+" + matches = re.findall(regex, output) + + for solution in matches: + F = int(float(re.search(".+FD\s+(\d+.\d+).+", solution).groups()[0])) + solutions.append(solution) + Fs.append(F) + + possible_Fs = sorted(set(Fs)) + print(f"Available F values: {possible_Fs}") + + # Find first solution with F greater than F + idx = next(x for x, val in enumerate(Fs) if val > min_F) + solution = matches[idx] + + # Get actual PLL register bitfield settings and info + regex = r".+OUT (\d+\.\d+)MHz, VCO (\d+\.\d+)MHz, RD\s+(\d+), FD\s+(\d+.\d*)\s+\(m =\s+(\d+), n =\s+(\d+)\), OD\s+(\d+), FOD\s+(\d+), ERR (-*\d+.\d+)ppm.*" + match = re.search(regex, solution) + + if match: + vals = match.groups() + + output_frequency = (1000000.0 * float(vals[0])) + vco_freq = 1000000.0 * float(vals[1]) + + # Now convert to actual settings in register bitfields + F = int(float(vals[3]) - 1) # PLL integer multiplier + R = int(vals[2]) - 1 # PLL integer divisor + f = int(vals[4]) - 1 # PLL fractional multiplier + p = int(vals[5]) - 1 # PLL fractional divisor + OD = int(vals[6]) - 1 # PLL output divider + ACD = int(vals[7]) - 1 # PLL application clock divider + ppm = float(vals[8]) # PLL PPM error for requrested set frequency + + assert match, f"Could not parse output of: {cmd} output: {solution}" + + # Now get reg values and save to file + with open(register_file, "w") as reg_vals: + reg_vals.write(f"/* Autogenerated by {Path(__file__).name} using command:\n") + reg_vals.write(f" {cmd}\n") + reg_vals.write(f" Picked output solution #{idx}\n") + # reg_vals.write(f"\n{solution}\n\n") # This is verbose and contains the same info as below + reg_vals.write(f" Input freq: {input_frequency}\n") + reg_vals.write(f" F: {F}\n") + reg_vals.write(f" R: {R}\n") + reg_vals.write(f" f: {f}\n") + reg_vals.write(f" p: {p}\n") + reg_vals.write(f" OD: {OD}\n") + reg_vals.write(f" ACD: {ACD}\n") + reg_vals.write(f" Output freq: {output_frequency}\n") + reg_vals.write(f" VCO freq: {vco_freq} */\n") + reg_vals.write("\n") + + + for reg in ["APP PLL CTL REG", "APP PLL DIV REG", "APP PLL FRAC REG"]: + regex = rf"({reg})\s+(0[xX][A-Fa-f0-9]+)" + match = re.search(regex, solution) + if match: + val = match.groups()[1] + reg_name = reg.replace(" ", "_") + line = f"#define {reg_name} \t{val}\n" + reg_vals.write(line) + + + return output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm + +class pll_solution: + """ + Access to all the info from get_pll_solution, cleaning up temp files. + intended for programatic access from the tests + """ + def __init__(self, *args, **kwargs): + try: + self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs) + self.lut = parse_lut_h_file("fractions.h") + finally: + Path("fractions.h").unlink(missing_ok=True) + Path("register_setup.h").unlink(missing_ok=True) + + +if __name__ == '__main__': + """ + This module is not intended to be run directly. This is here for internal testing only. + """ + input_frequency = 24000000 + output_frequency = 24576000 + print(f"get_pll_solution input_frequency: {input_frequency} output_frequency: {output_frequency}...") + output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(24000000, 24576000) + print(f"got solution: \noutput_frequency: {output_frequency}\nvco_freq: {vco_freq}\nF: {F}\nR: {R}\nf: {f}\np: {p}\nOD: {OD}\nACD: {ACD}\nppm: {ppm}") + + app_pll = app_pll_frac_calc(input_frequency, F, R, f, p, OD, ACD) + print(f"Got output frequency: {app_pll.calc_frequency()}") + p = 10 + for f in range(p): + print(f"For f: {f} got frequency: {app_pll.update_frac(f, p)}") + diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py new file mode 100644 index 00000000..87aee072 --- /dev/null +++ b/python/sw_pll/controller_model.py @@ -0,0 +1,87 @@ +# Copyright 2022-2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import dco_model +import numpy as np + +class sw_pll_lut_pi_ctrl(dco_model.lut_dco): + """ + This class instantiates a control loop instance. It takes a lookup table function which can be generated + from the error_from_h class which allows it use the actual pre-calculated transfer function. + Once instantiated, the do_control method runs the control loop. + + This class forms the core of the simulator and allows the constants (K..) to be tuned to acheive the + desired response. The function run_sim allows for a plot of a step resopnse input which allows this + to be done visually. + """ + lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} + + def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): + + self.dco = dco_model.lut_dco() + self.lut_lookup_function = self.dco.get_lut() + lut_size = self.dco.get_lut_size() + + # By default set the nominal LUT index to half way + if base_lut_index is None: + base_lut_index = lut_size // 2 + self.base_lut_index = base_lut_index + + self.Kp = Kp + self.Ki = Ki + self.Kii = 0.0 if Kii is None else Kii + + self.diff = 0.0 # Most recent diff between expected and actual + self.error_accum = 0.0 # Integral of error + self.error_accum_accum = 0.0 # Double integral of error + self.total_error = 0.0 # Calculated total error + + # Set windup limit to the lut_size, which by default is double of the deflection from nominal + self.i_windup_limit = lut_size / self.Ki if self.Ki != 0.0 else 0.0 + self.ii_windup_limit = lut_size / self.Kii if self.Kii != 0.0 else 0.0 + + self.verbose = verbose + + if verbose: + print(f"Init sw_pll_lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") + + def get_dco_ctrl(self): + return self.error + + + def do_control_from_error(self, error): + """ + Calculate the LUT setting from the input error + """ + self.diff = error # Used by tests + + # clamp integral terms to stop them irrecoverably drifting off. + self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) + self.error_accum_accum = np.clip(self.error_accum_accum + self.error_accum, -self.ii_windup_limit, self.ii_windup_limit) + + error_p = self.Kp * error; + error_i = self.Ki * self.error_accum + error_ii = self.Kii * self.error_accum_accum + + self.error = error_p + error_i + error_ii + + if self.verbose: + print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.error}") + + dco_ctrl = self.base_lut_index - self.error + + return dco_ctrl + + + +if __name__ == '__main__': + """ + This module is not intended to be run directly. This is here for internal testing only. + """ + Kp = 1.0 + Ki = 0.1 + + sw_pll = sw_pll_lut_pi_ctrl(Kp, Ki, verbose=True) + for error_input in range(-10, 20): + dco_ctrl = sw_pll.do_control_from_error(error_input) + diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py new file mode 100644 index 00000000..196dff2b --- /dev/null +++ b/python/sw_pll/dco_model.py @@ -0,0 +1,259 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import app_pll_model +import matplotlib.pyplot as plt +import numpy as np +import os +import re + +""" +This file contains implementations of digitally controlled oscillators. +It uses the app_pll_model underneath to turn a control signal into a +calculated output frequency. + +It currently contains two implementations of DCO: + +- A lookup table version which is efficient in computation and offers + a range of frequencies based on a pre-calculated look up table (LUT) +- A Sigma Delta Modulator which typically uses a dedicated thread to + run the modulator but results in lower noise in the audio spectrum +""" + + +############################## +# LOOK UP TABLE IMPLEMENTATION +############################## + +class lut_dco: + """ + This class parses a pre-generated fractions.h file and builds a lookup table so that the values can be + used by the sw_pll simulation. It may be used directly but is generally used a sub class of error_to_pll_output_frequency. + """ + def __init__(self, header_file = "fractions.h", force_lut_gen=False, verbose=False): # fixed header_file name by pll_calc.py + """ + Constructor for the LUT DCO. Reads the pre-calculated header file and produces the LUT which contains + the pll fractional register settings (16b) for each of the entries + """ + if not os.path.exists(header_file): + assert False, f"Please initialize a lut_dco to produce a parsable header file {header_file}" + with open(header_file) as hdr: + header = hdr.readlines() + min_frac = 1.0 + max_frac = 0.0 + for line in header: + regex_ne = fr"frac_values_?\d*\[(\d+)].*" + match = re.search(regex_ne, line) + if match: + num_entries = int(match.groups()[0]) + # print(f"num_entries: {num_entries}") + lut = np.zeros(num_entries, dtype=np.uint16) + + regex_fr = r"0x([0-9A-F]+).+Index:\s+(\d+).+=\s(0.\d+)" + match = re.search(regex_fr, line) + if match: + reg, idx, frac = match.groups() + reg = int(reg, 16) + idx = int(idx) + frac = float(frac) + min_frac = frac if frac < min_frac else min_frac + max_frac = frac if frac > max_frac else max_frac + lut[idx] = reg + + # print(f"min_frac: {min_frac} max_frac: {max_frac}") + + self.lut = lut + self.min_frac = min_frac + self.max_frac = max_frac + + + # if not os.path.exists(app_pll_model.register_file) or not os.path.exists(header_file) or force_lut_gen: + # pll = app_pll_model.app_pll_frac_calc(app_pll_model.register_file) + + input_freq, F, R, f, p, OD, ACD = self.parse_register_file(app_pll_model.register_file) + self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + + def parse_register_file(self, register_file): + """ + This method reads the pre-saved register setup comments from get_pll_solution and parses them into parameters that + can be used for later simulation. + """ + + with open(register_file) as rf: + reg_file = rf.read().replace('\n', '') + input_freq = int(re.search(".+Input freq:\s+(\d+).+", reg_file).groups()[0]) + F = int(re.search(".+F:\s+(\d+).+", reg_file).groups()[0]) + R = int(re.search(".+R:\s+(\d+).+", reg_file).groups()[0]) + f = int(re.search(".+f:\s+(\d+).+", reg_file).groups()[0]) + p = int(re.search(".+p:\s+(\d+).+", reg_file).groups()[0]) + OD = int(re.search(".+OD:\s+(\d+).+", reg_file).groups()[0]) + ACD = int(re.search(".+ACD:\s+(\d+).+", reg_file).groups()[0]) + + return input_freq, F, R, f, p, OD, ACD + + def get_lut(self): + """ + Return the look up table + """ + return self.lut + + def get_lut_size(self): + """ + Return the size of look up table + """ + return np.size(self.lut) + + def print_stats(self): + """ + Returns a summary of the LUT range and steps. + """ + lut = self.get_lut() + steps = np.size(lut) + + register = int(lut[0]) + min_freq = self.app_pll.update_frac_reg(register) + + register = int(lut[steps // 2]) + mid_freq = self.app_pll.update_frac_reg(register) + + register = int(lut[-1]) + max_freq = self.app_pll.update_frac_reg(register) + + print(f"LUT min_freq: {min_freq}") + print(f"LUT mid_freq: {mid_freq}") + print(f"LUT max_freq: {max_freq}") + print(f"LUT steps: {steps}") + + return min_freq, mid_freq, max_freq, steps + + def _reg_to_frac(self, register): + f = (register & 0xff00) >> 8 + p = register & 0xff + + return f, p + + + def plot_freq_range(self): + """ + Generates a plot of the frequency range of the LUT and + visually shows the spacing of the discrete frequencies + that it can produce. + """ + + frequencies = [] + for step in range(self.get_lut_size()): + register = int(self.lut[step]) + self.app_pll.update_frac_reg(register) + frequencies.append(self.app_pll.get_output_frequency()) + + plt.clf() + plt.plot(frequencies, color='green', marker='.', label='frequency') + plt.title('PLL fractional range', fontsize=14) + plt.xlabel(f'LUT index', fontsize=14) + plt.ylabel('Frequency', fontsize=10) + plt.legend(loc="upper right") + plt.grid(True) + # plt.show() + plt.savefig("lut_dco_range.png", dpi=150) + + # def get_frequency_from_error(error, lut, app_pll_frac_calc): + # """ + # given an error, a lut, and a pll, calculate the frequency + # """ + # num_entries = np.size(lut) + + # set_point = int(error) # Note negative term for neg feedback + # if set_point < 0: + # set_point = 0 + # lock_status = -1 + # elif set_point >= num_entries: + # set_point = num_entries - 1 + # lock_status = 1 + # else: + # set_point = set_point + # lock_status = 0 + + # register = int(lut[set_point]) + # pll.update_pll_frac_reg(register) + + # return pll.get_output_frequency(), lock_status + +# class error_to_pll_output_frequency(app_pll_frac_calc, parse_lut_h_file): +# """ +# This super class combines app_pll_frac_calc and parse_lut_h_file and provides a way of inputting the error signal and +# providing an output frequency for a given set of PLL configuration parameters. It includes additional methods for +# turning the LUT register settings parsed by parse_lut_h_file into fractional values which can be fed into app_pll_frac_calc. + +# It also contains information reporting methods which provide the range and step sizes of the PLL configuration as well as +# plotting the transfer function from error to frequency so the linearity and regularity the transfer function can be observed. +# """ + +# def __init__(self, header_file, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False): +# self.app_pll_frac_calc = app_pll_frac_calc.__init__(self, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False) +# self.parse_lut_h_file = parse_lut_h_file.__init__(self, header_file, verbose=False) +# self.verbose = verbose + + + + +# def _get_output_frequency_from_error(self, error): +# lut = self.get_lut() + +# return get_frequency_from_error(error, lut, self) + +###################################### +# SIGMA DELTA MODULATOR IMPLEMENTATION +###################################### + +class sdm: + """ + Experimental - taken from lib_xua synchronous branch + Third order, 9 level output delta sigma. 20 bit unsigned input. + """ + def __init__(self): + # Delta sigma modulator state + self.ds_x1 = 0 + self.ds_x2 = 0 + self.ds_x3 = 0 + + + # generalized version without fixed point shifts. WIP!! + # takes a Q20 number from + def do_sigma_delta(self, ds_in): + ds_out = int(self.ds_x3 * 0.002197265625) + if ds_out > 8: + ds_out = 8 + if ds_out < 0: + ds_out = 0 + self.ds_x3 += int((self.ds_x2 * 0.03125) - (ds_out * 768)) + self.ds_x2 += int((self.ds_x1 * 0.03125) - (ds_out * 16384)) + self.ds_x1 += int(ds_in - (ds_out * 131072)) + + return ds_out + + +class sigma_delta_dco(sdm): + """ + TBD + """ + def __init__(self): + # input_freq = + # F = + # R = + # f = + # p = + # OD + # ACD = + pass + + + +if __name__ == '__main__': + """ + This module is not intended to be run directly. This is here for internal testing only. + """ + dco = lut_dco() + print(f"LUT size: {dco.get_lut_size()}") + print(f"LUT : {dco.get_lut()}") + dco.plot_freq_range() + dco.print_stats() diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py new file mode 100644 index 00000000..95942e3a --- /dev/null +++ b/python/sw_pll/pfd_model.py @@ -0,0 +1,251 @@ +# Copyright 2022-2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +class audio_modulator: + def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): + self.sample_rate = sample_rate + self.test_tone_hz = test_tone_hz + + # First generate arrays for FM modulation + self.each_sample_number = np.linspace(0, duration_s, int(sample_rate * duration_s)) + self.carrier = 2 * np.pi * self.each_sample_number * test_tone_hz + + # Blank array with 0Hz modulation + k = 2 * np.pi # modulation constant - amplitude of 1.0 = 1Hz deviation + self.modulator = k * self.each_sample_number + + def apply_frequency_deviation(self, start_s, end_s, delta_freq): + start_idx = int(start_s * self.sample_rate) + end_idx = int(end_s * self.sample_rate) + self.modulator[start_idx:end_idx] = self.modulator[start_idx:end_idx] + delta_freq + + + def get_modulated_waveform(self): + # Now create the frequency modulated waveform + waveform = np.cos(self.carrier + self.modulator) + + return waveform + + def save_modulated_wav(self, filename, waveform): + integer_output = np.int16(waveform * 32767) + soundfile.write(filename, integer_output, int(self.sample_rate)) + + def plot_modulated_fft(self, filename, waveform): + xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.each_sample_number.size//2) + N = xf.size + window = np.kaiser(N*2, 14) + waveform = waveform * window + yf = np.fft.fft(waveform) + fig, ax = plt.subplots() + + # Plot a zoom in on the test + tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N) + num_side_bins = 50 + yf = 20 * np.log10(np.abs(yf) / N) + # ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.') + + # Plot the whole frequncy range from DC to nyquist + ax.plot(xf[:N], yf[:N], marker='.') + ax.set_xscale("log") + plt.savefig(filename, dpi=150) + + +def run_sim(target_output_frequency, + nominal_ref_frequency, + lut_lookup_function, + lut_size, + Kp, + Ki, + simulation_iterations, + ppm_shifts, + jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for + test_tone_hz=1000, + verbose=False): + """ + This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response + of the sw_pll to changes in input reference frequency. + A plot of the simulation is generated to allow visual inspection and tuning. + """ + + ref_frequency = nominal_ref_frequency + sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) + + output_count_end_float = 0.0 + real_time = 0.0 + actual_output_frequency = target_output_frequency + + last_count = 0 + + freq_log = [] + target_log = [] + + sigma_delta_loop_ratio = 10000 # for every contol update, we do sigma delta this many times + sigma_delta_loop_ratio = 1 # for every contol update, we do sigma delta this many times + + audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) + + sdm = sigma_delta() + + for count in range(simulation_iterations): + output_count_start_float = output_count_end_float + output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate + + # Add some jitter to the output_count to test jitter compensation + output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) + output_count_end_float += output_count_float_inc + output_sample_jitter + # Compensate for the jitter + period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc + + # print(f"output_count_float_inc: {output_count_float_inc}, period_fraction: {period_fraction}, ratio: {output_count_float_inc / period_fraction}") + + actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) + # lock_status = 0 + + # Sigma delta section + micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio + for sd_count in range(sigma_delta_loop_ratio): + # Helpers for the tone modulation + actual_output_frequency, lock_status = sw_pll.do_deviation(0) + + time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency + freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz + start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) + end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) + audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + + freq_log.append(actual_output_frequency) + target_log.append(ref_frequency * multiplier) + + if verbose: + print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") + + + + + real_time += ref_to_loop_call_rate / ref_frequency + + + # A number of events where the input reference is stepped + ppm_adjust = lambda f, ppm: f * (1 + (ppm / 1000000)) + for ppm_shift in ppm_shifts: + (change_at_count, ppm) = ppm_shift + if count == change_at_count: + ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) + + + plt.clf() + plt.plot(freq_log, color='red', marker='.', label='actual frequency') + plt.plot(target_log, color='blue', marker='.', label='target frequency') + plt.title('PLL tracking', fontsize=14) + plt.xlabel(f'loop_cycle {ref_to_loop_call_rate}', fontsize=14) + plt.ylabel('Frequency', fontsize=10) + plt.legend(loc="upper right") + plt.grid(True) + # plt.show() + plt.savefig("pll_step_response.png", dpi=150) + + # Generate fft of modulated test tone + audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) + audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) + + +""" +ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks +xtal_frequency - The xcore clock frequency +nominal_ref_frequency - The nominal input reference frequency +target_output_frequency - The nominal target output frequency +max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance +min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance +ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance +fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance +fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance +""" + +ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz +xtal_frequency = 24000000 +profile_choice = 0 + +# Example profiles to produce typical frequencies seen in audio systems +profiles = [ + # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, + # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, + # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, + # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, + # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, + # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size + {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, + ] + + + +if __name__ == '__main__': + """ + This script checks to see if PLL settings have already been generated, if not, generates them. + It then uses these settings to generate a LUT and control loop instance. + A set of step functions in input reference frequencies are then generated and the + response of the sw_pll to these changes is logged and then plotted. + """ + + profile_used = profiles[profile_choice] + + # Make a list of the correct args for get_pll_solution + get_pll_solution_args = {"input_frequency":xtal_frequency} + get_pll_solution_args.update(profile_used) + del get_pll_solution_args["nominal_ref_frequency"] + get_pll_solution_args = list(get_pll_solution_args.values()) + + # Extract the required vals from the profile + target_output_frequency = profile_used["target_output_frequency"] + nominal_ref_frequency = profile_used["nominal_ref_frequency"] + multiplier = target_output_frequency / nominal_ref_frequency + # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 + + + # Use pre-caclulated saved values if they exist, otherwise generate new ones + if not os.path.exists(header_file) or not os.path.exists(register_file): + output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(*get_pll_solution_args) + print(f"output_frequency: {output_frequency}, vco_freq: {vco_freq}, F: {F}, R: {R}, f: {f}, p: {p}, OD: {OD}, ACD: {ACD}, ppm: {ppm}") + else: + F, R, f, p, OD, ACD = parse_register_file(register_file) + print(f"Using pre-calculated settings read from {header_file} and {register_file}:") + + print(f"PLL register settings F: {F}, R: {R}, OD: {OD}, ACD: {ACD}, f: {f}, p: {p}") + + # Instantiate controller + error_from_h = error_to_pll_output_frequency(header_file, xtal_frequency, F, R, OD, ACD, f, p, verbose=False) + error_from_h.plot_freq_range() + + min_freq, mid_freq, max_freq, steps = error_from_h.get_stats() + step_size = ((max_freq - min_freq) / steps) + + print(f"min_freq: {min_freq:.0f}Hz") + print(f"mid_freq: {mid_freq:.0f}Hz") + print(f"max_freq: {max_freq:.0f}Hz") + print(f"average step size: {step_size:.6}Hz, PPM: {1e6 * step_size/mid_freq:.6}") + print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") + print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") + print(f"LUT entries: {steps} ({steps*2} bytes)") + + + # PI loop control constants + Kp = 0.0 + Ki = 1.0 + simulation_iterations = 150 + ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) + ppm_shifts = () # Straight run with no PPM deviation + + run_sim(target_output_frequency, + nominal_ref_frequency, + error_from_h.get_output_frequency_from_error, + error_from_h.get_lut_size(), + Kp, + Ki, + simulation_iterations, + ppm_shifts, + verbose=False) From 4b26ef9a17b1cfef12b4b7830b0cd8500176bc57 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 8 Nov 2023 16:29:46 +0000 Subject: [PATCH 003/118] Further WIP restructure of simulator --- python/sw_pll/analysis_tools.py | 69 +++++ python/sw_pll/pfd_model.py | 276 +++-------------- python/sw_pll/sw_pll_sim.py | 532 ++------------------------------ 3 files changed, 142 insertions(+), 735 deletions(-) create mode 100644 python/sw_pll/analysis_tools.py diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py new file mode 100644 index 00000000..b0de2c20 --- /dev/null +++ b/python/sw_pll/analysis_tools.py @@ -0,0 +1,69 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import matplotlib.pyplot as plt +import numpy as np +import soundfile + +class audio_modulator: + def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): + self.sample_rate = sample_rate + self.test_tone_hz = test_tone_hz + + # First generate arrays for FM modulation + self.each_sample_number = np.linspace(0, duration_s, int(sample_rate * duration_s)) + self.carrier = 2 * np.pi * self.each_sample_number * test_tone_hz + + # Blank array with 0Hz modulation + k = 2 * np.pi # modulation constant - amplitude of 1.0 = 1Hz deviation + self.modulator = k * self.each_sample_number + + def apply_frequency_deviation(self, start_s, end_s, delta_freq): + start_idx = int(start_s * self.sample_rate) + end_idx = int(end_s * self.sample_rate) + self.modulator[start_idx:end_idx] = self.modulator[start_idx:end_idx] + delta_freq + + + def get_modulated_waveform(self): + # Now create the frequency modulated waveform + waveform = np.cos(self.carrier + self.modulator) + + return waveform + + def save_modulated_wav(self, filename, waveform): + integer_output = np.int16(waveform * 32767) + soundfile.write(filename, integer_output, int(self.sample_rate)) + + def plot_modulated_fft(self, filename, waveform): + xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.each_sample_number.size//2) + N = xf.size + window = np.kaiser(N*2, 14) + waveform = waveform * window + yf = np.fft.fft(waveform) + fig, ax = plt.subplots() + + # Plot a zoom in on the test + tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N) + num_side_bins = 50 + yf = 20 * np.log10(np.abs(yf) / N) + # ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.') + + # Plot the whole frequncy range from DC to nyquist + ax.plot(xf[:N], yf[:N], marker='.') + ax.set_xscale("log") + plt.savefig(filename, dpi=150) + +if __name__ == '__main__': + """ + This module is not intended to be run directly. This is here for internal testing only. + """ + test_len = 10 + audio = audio_modulator(test_len) + for time_s in range(test_len): + modulation_hz = 10000 * (time_s - (test_len) / 2) + audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz) + + modulated_tone = audio.get_modulated_waveform() + audio.save_modulated_wav("modulated.wav", modulated_tone) + audio.plot_modulated_fft("modulated_fft.png", modulated_tone) + diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index 95942e3a..d674f70a 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -1,251 +1,53 @@ -# Copyright 2022-2023 XMOS LIMITED. +# Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -class audio_modulator: - def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): - self.sample_rate = sample_rate - self.test_tone_hz = test_tone_hz - - # First generate arrays for FM modulation - self.each_sample_number = np.linspace(0, duration_s, int(sample_rate * duration_s)) - self.carrier = 2 * np.pi * self.each_sample_number * test_tone_hz - - # Blank array with 0Hz modulation - k = 2 * np.pi # modulation constant - amplitude of 1.0 = 1Hz deviation - self.modulator = k * self.each_sample_number - - def apply_frequency_deviation(self, start_s, end_s, delta_freq): - start_idx = int(start_s * self.sample_rate) - end_idx = int(end_s * self.sample_rate) - self.modulator[start_idx:end_idx] = self.modulator[start_idx:end_idx] + delta_freq - - - def get_modulated_waveform(self): - # Now create the frequency modulated waveform - waveform = np.cos(self.carrier + self.modulator) - - return waveform - - def save_modulated_wav(self, filename, waveform): - integer_output = np.int16(waveform * 32767) - soundfile.write(filename, integer_output, int(self.sample_rate)) - - def plot_modulated_fft(self, filename, waveform): - xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.each_sample_number.size//2) - N = xf.size - window = np.kaiser(N*2, 14) - waveform = waveform * window - yf = np.fft.fft(waveform) - fig, ax = plt.subplots() - - # Plot a zoom in on the test - tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N) - num_side_bins = 50 - yf = 20 * np.log10(np.abs(yf) / N) - # ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.') - - # Plot the whole frequncy range from DC to nyquist - ax.plot(xf[:N], yf[:N], marker='.') - ax.set_xscale("log") - plt.savefig(filename, dpi=150) - - -def run_sim(target_output_frequency, - nominal_ref_frequency, - lut_lookup_function, - lut_size, - Kp, - Ki, - simulation_iterations, - ppm_shifts, - jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for - test_tone_hz=1000, - verbose=False): - """ - This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response - of the sw_pll to changes in input reference frequency. - A plot of the simulation is generated to allow visual inspection and tuning. - """ +import controller_model - ref_frequency = nominal_ref_frequency - sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) - - output_count_end_float = 0.0 - real_time = 0.0 - actual_output_frequency = target_output_frequency - - last_count = 0 - - freq_log = [] - target_log = [] - - sigma_delta_loop_ratio = 10000 # for every contol update, we do sigma delta this many times - sigma_delta_loop_ratio = 1 # for every contol update, we do sigma delta this many times - - audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) - - sdm = sigma_delta() - - for count in range(simulation_iterations): - output_count_start_float = output_count_end_float - output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate - - # Add some jitter to the output_count to test jitter compensation - output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) - output_count_end_float += output_count_float_inc + output_sample_jitter - # Compensate for the jitter - period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc - - # print(f"output_count_float_inc: {output_count_float_inc}, period_fraction: {period_fraction}, ratio: {output_count_float_inc / period_fraction}") - - actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) - # lock_status = 0 - - # Sigma delta section - micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio - for sd_count in range(sigma_delta_loop_ratio): - # Helpers for the tone modulation - actual_output_frequency, lock_status = sw_pll.do_deviation(0) - - time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency - freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz - start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) - end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) - audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) - # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) - - freq_log.append(actual_output_frequency) - target_log.append(ref_frequency * multiplier) - - if verbose: - print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") - - - - - real_time += ref_to_loop_call_rate / ref_frequency - - - # A number of events where the input reference is stepped - ppm_adjust = lambda f, ppm: f * (1 + (ppm / 1000000)) - for ppm_shift in ppm_shifts: - (change_at_count, ppm) = ppm_shift - if count == change_at_count: - ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) - - - plt.clf() - plt.plot(freq_log, color='red', marker='.', label='actual frequency') - plt.plot(target_log, color='blue', marker='.', label='target frequency') - plt.title('PLL tracking', fontsize=14) - plt.xlabel(f'loop_cycle {ref_to_loop_call_rate}', fontsize=14) - plt.ylabel('Frequency', fontsize=10) - plt.legend(loc="upper right") - plt.grid(True) - # plt.show() - plt.savefig("pll_step_response.png", dpi=150) - - # Generate fft of modulated test tone - audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) - audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) - - -""" -ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks -xtal_frequency - The xcore clock frequency -nominal_ref_frequency - The nominal input reference frequency -target_output_frequency - The nominal target output frequency -max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance -min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance -ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance -fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance -fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance -""" - -ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz -xtal_frequency = 24000000 -profile_choice = 0 - -# Example profiles to produce typical frequencies seen in audio systems -profiles = [ - # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, - # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, - # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, - # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size - {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, - ] +class port_timer_pfd(): + def __init__(self, nominal_output_hz, nominal_nominal_control_rate_hz): + self.output_count_last_int = 0 # Integer value of last output_clock_count + self.expected_output_count_inc = nominal_output_hz / nominal_control_rate_hz + print(f"expected_output_count_inc: {self.expected_output_count_inc}") -if __name__ == '__main__': - """ - This script checks to see if PLL settings have already been generated, if not, generates them. - It then uses these settings to generate a LUT and control loop instance. - A set of step functions in input reference frequencies are then generated and the - response of the sw_pll to these changes is logged and then plotted. - """ + def get_error(self, output_clock_count_float, period_fraction=1.0): + + """ + Calculate frequency error from the port output_count taken at the ref clock time. + Note it uses a floating point input clock count to make simulation easier. This + handles fractional counts and carries them properly. - profile_used = profiles[profile_choice] + If the time of sampling the output_count is not precisely 1.0 x the ref clock time, + you may pass a fraction to allow for a proportional value using period_fraction. This is optional. + """ - # Make a list of the correct args for get_pll_solution - get_pll_solution_args = {"input_frequency":xtal_frequency} - get_pll_solution_args.update(profile_used) - del get_pll_solution_args["nominal_ref_frequency"] - get_pll_solution_args = list(get_pll_solution_args.values()) + output_count_int = int(output_clock_count_float) # round down to nearest int to match hardware - # Extract the required vals from the profile - target_output_frequency = profile_used["target_output_frequency"] - nominal_ref_frequency = profile_used["nominal_ref_frequency"] - multiplier = target_output_frequency / nominal_ref_frequency - # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 + expected_output_count = self.output_count_last_int + int(self.expected_output_count_inc * period_fraction) # Compensate for jitter if period fraction is specified + self.output_count_last_int = output_count_int + error = output_count_int - expected_output_count + + return error - # Use pre-caclulated saved values if they exist, otherwise generate new ones - if not os.path.exists(header_file) or not os.path.exists(register_file): - output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(*get_pll_solution_args) - print(f"output_frequency: {output_frequency}, vco_freq: {vco_freq}, F: {F}, R: {R}, f: {f}, p: {p}, OD: {OD}, ACD: {ACD}, ppm: {ppm}") - else: - F, R, f, p, OD, ACD = parse_register_file(register_file) - print(f"Using pre-calculated settings read from {header_file} and {register_file}:") - print(f"PLL register settings F: {F}, R: {R}, OD: {OD}, ACD: {ACD}, f: {f}, p: {p}") - # Instantiate controller - error_from_h = error_to_pll_output_frequency(header_file, xtal_frequency, F, R, OD, ACD, f, p, verbose=False) - error_from_h.plot_freq_range() +if __name__ == '__main__': + """ + This module is not intended to be run directly. This is here for internal testing only. + """ - min_freq, mid_freq, max_freq, steps = error_from_h.get_stats() - step_size = ((max_freq - min_freq) / steps) - - print(f"min_freq: {min_freq:.0f}Hz") - print(f"mid_freq: {mid_freq:.0f}Hz") - print(f"max_freq: {max_freq:.0f}Hz") - print(f"average step size: {step_size:.6}Hz, PPM: {1e6 * step_size/mid_freq:.6}") - print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") - print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") - print(f"LUT entries: {steps} ({steps*2} bytes)") - - - # PI loop control constants - Kp = 0.0 - Ki = 1.0 - simulation_iterations = 150 - ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) - ppm_shifts = () # Straight run with no PPM deviation - - run_sim(target_output_frequency, - nominal_ref_frequency, - error_from_h.get_output_frequency_from_error, - error_from_h.get_lut_size(), - Kp, - Ki, - simulation_iterations, - ppm_shifts, - verbose=False) + nominal_output_hz = 12288000 + nominal_control_rate_hz = 93.75 + expected_output_clock_inc = nominal_output_hz / nominal_control_rate_hz + + pfd = port_timer_pfd(nominal_output_hz, nominal_control_rate_hz) + + output_clock_count_float = 0.0 + for output_hz in range(nominal_output_hz - 1000, nominal_output_hz + 1000, 10): + output_clock_count_float += output_hz / nominal_output_hz * expected_output_clock_inc + error = pfd.get_error(output_clock_count_float) + print(f"actual output Hz: {output_hz} output_clock_count: {output_clock_count_float} error: {error}") + + diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index ba75391b..0a10e590 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -1,495 +1,19 @@ -# Copyright 2022-2023 XMOS LIMITED. +# Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -import numpy as np -import matplotlib.pyplot as plt -import subprocess -import re -import os -from pathlib import Path -import soundfile - -header_file = "fractions.h" # fixed name by pll_calc.py -register_file = "register_setup.h" # can be changed as needed - - -class app_pll_frac_calc: - """ - This class uses the formula in the XU316 datasheet to calculate the output frequency of the - application PLL (sometimes called secondary PLL) from the register settings provided. - It uses the checks specified in the datasheet to ensure the settings are valid, and will assert if not. - To keep the inherent jitter of the PLL output down to a minimum, it is recommended that R be kept small, - ideally = 0 (which equiates to 1) but reduces lock range. - """ - def __init__(self, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, r_init, verbose=False): - self.input_frequency = input_frequency - self.F = F_init - self.R = R_init - self.OD = OD_init - self.ACD = ACD_init - self.f = f_init # fractional multiplier (+1.0) - self.p = r_init # fractional fivider (+1.0) - self.output_frequency = None - self.lock_status_state = 0 - self.verbose = verbose - - self.calc_frequency() - - def calc_frequency(self): - if self.verbose: - print(f"F: {self.F} R: {self.R} OD: {self.OD} ACD: {self.ACD} f: {self.f} p: {self.p}") - print(f"input_frequency: {self.input_frequency}") - assert self.F >= 1 and self.F <= 8191, f"Invalid F setting {self.F}" - assert type(self.F) is int, f"Error: F must be an INT" - assert self.R >= 0 and self.R <= 63, f"Invalid R setting {self.R}" - assert type(self.R) is int, f"Error: R must be an INT" - assert self.OD >= 0 and self.OD <= 7, f"Invalid OD setting {self.OD}" - assert type(self.OD) is int, f"Error: OD must be an INT" - - intermediate_freq = self.input_frequency * (self.F + 1.0) / 2.0 / (self.R + 1.0) - assert intermediate_freq >= 360000000.0 and intermediate_freq <= 1800000000.0, f"Invalid VCO freq: {intermediate_freq}" - # print(f"intermediate_freq: {intermediate_freq}") - - assert type(self.p) is int, f"Error: r must be an INT" - assert type(self.f) is int, f"Error: f must be an INT" - - assert self.p > self.f, "Error f is not < p: {self.f} {self.p}" - - # From XU316-1024-QF60A-xcore.ai-Datasheet_22.pdf - self.output_frequency = self.input_frequency * (self.F + 1.0 + ((self.f + 1) / (self.p + 1)) ) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1)) - - return self.output_frequency - - def get_output_frequency(self): - return self.output_frequency - - def update_pll_all(self, F, R, OD, ACD, f, p): - self.F = F - self.R = R - self.OD = OD - self.ACD = ACD - self.f = f - self.p = p - self.calc_frequency() - - def update_pll_frac(self, f, p): - self.f = f - self.p = p - self.calc_frequency() - - def update_pll_frac_reg(self, reg): - """determine f and p from the register number and recalculate frequency""" - f = int((reg >> 8) & ((2**8)-1)) - p = int(reg & ((2**8)-1)) - self.update_pll_frac(f, p) - -class parse_lut_h_file(): - """ - This class parses a pre-generated fractions.h file and builds a lookup table so that the values can be - used by the sw_pll simulation. It may be used directly but is generally used a sub class of error_to_pll_output_frequency. - """ - def __init__(self, header_file, verbose=False): - with open(header_file) as hdr: - header = hdr.readlines() - min_frac = 1.0 - max_frac = 0.0 - for line in header: - regex_ne = fr"frac_values_?\d*\[(\d+)].*" - match = re.search(regex_ne, line) - if match: - num_entries = int(match.groups()[0]) - # print(f"num_entries: {num_entries}") - lut = np.zeros(num_entries, dtype=np.uint16) - - regex_fr = r"0x([0-9A-F]+).+Index:\s+(\d+).+=\s(0.\d+)" - match = re.search(regex_fr, line) - if match: - reg, idx, frac = match.groups() - reg = int(reg, 16) - idx = int(idx) - frac = float(frac) - min_frac = frac if frac < min_frac else min_frac - max_frac = frac if frac > max_frac else max_frac - lut[idx] = reg - - # print(f"min_frac: {min_frac} max_frac: {max_frac}") - - self.lut_reg = lut - self.min_frac = min_frac - self.max_frac = max_frac - - def get_lut(self): - return self.lut_reg - - def get_lut_size(self): - return np.size(self.lut_reg) - -def get_frequency_from_error(error, lut, pll:app_pll_frac_calc): - """given an error, a lut, and a pll, calculate the frequency""" - num_entries = np.size(lut) - - set_point = int(error) # Note negative term for neg feedback - if set_point < 0: - set_point = 0 - lock_status = -1 - elif set_point >= num_entries: - set_point = num_entries - 1 - lock_status = 1 - else: - set_point = set_point - lock_status = 0 - - register = int(lut[set_point]) - pll.update_pll_frac_reg(register) - - return pll.get_output_frequency(), lock_status - -class error_to_pll_output_frequency(app_pll_frac_calc, parse_lut_h_file): - """ - This super class combines app_pll_frac_calc and parse_lut_h_file and provides a way of inputting the eror signal and - providing an output frequency for a given set of PLL configuration parameters. It includes additonal methods for - turning the LUT register settings parsed by parse_lut_h_file into fractional values which can be fed into app_pll_frac_calc. - - It also contains information reporting methods which provide the range and step sizes of the PLL configuration as well as - plotting the transfer function from error to frequncy so the linearity and regularity the transfer function can be observed. - """ - - def __init__(self, header_file, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False): - self.app_pll_frac_calc = app_pll_frac_calc.__init__(self, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False) - self.parse_lut_h_file = parse_lut_h_file.__init__(self, header_file, verbose=False) - self.verbose = verbose - - def reg_to_frac(self, register): - f = (register & 0xff00) >> 8 - p = register & 0xff - - return f, p - - def get_output_frequency_from_error(self, error): - lut = self.get_lut() - - return get_frequency_from_error(error, lut, self) - - def get_stats(self): - lut = self.get_lut() - steps = np.size(lut) - - register = int(lut[0]) - f, p = self.reg_to_frac(register) - self.update_pll_frac(f, p) - min_freq = self.get_output_frequency() - - register = int(lut[steps // 2]) - f, p = self.reg_to_frac(register) - self.update_pll_frac(f, p) - mid_freq = self.get_output_frequency() - - register = int(lut[-1]) - f, p = self.reg_to_frac(register) - self.update_pll_frac(f, p) - max_freq = self.get_output_frequency() - - return min_freq, mid_freq, max_freq, steps - - def plot_freq_range(self): - lut = self.get_lut() - steps = np.size(lut) - - frequencies = [] - for step in range(steps): - register = int(lut[step]) - f, p = self.reg_to_frac(register) - self.update_pll_frac(f, p) - frequencies.append(self.get_output_frequency()) - - plt.clf() - plt.plot(frequencies, color='green', marker='.', label='frequency') - plt.title('PLL fractional range', fontsize=14) - plt.xlabel(f'LUT index', fontsize=14) - plt.ylabel('Frequency', fontsize=10) - plt.legend(loc="upper right") - plt.grid(True) - # plt.show() - plt.savefig("sw_pll_range.png", dpi=150) - -def parse_register_file(register_file): - """ - This helper function reads the pre-saved register setup comments from get_pll_solution and parses them into parameters that - can be used for the simulation. - """ - - with open(register_file) as rf: - reg_file = rf.read().replace('\n', '') - F = int(re.search(".+F:\s+(\d+).+", reg_file).groups()[0]) - R = int(re.search(".+R:\s+(\d+).+", reg_file).groups()[0]) - f = int(re.search(".+f:\s+(\d+).+", reg_file).groups()[0]) - p = int(re.search(".+p:\s+(\d+).+", reg_file).groups()[0]) - OD = int(re.search(".+OD:\s+(\d+).+", reg_file).groups()[0]) - ACD = int(re.search(".+ACD:\s+(\d+).+", reg_file).groups()[0]) - - return F, R, f, p, OD, ACD - - - - # see /doc/sw_pll.rst for guidance on these settings -def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95): - """ - This is a wrapper function for pll_calc.py and allows it to be called programatically. - It contains sensible defaults for the arguments and abstracts some of the complexity away from - the underlying script. Configuring the PLL is not an exact science and there are many tradeoffs involved. - See sw_pll.rst for some of the tradeoffs involved and some example paramater sets. - - Once run, this function saves two output files: - - fractions.h which contains the fractional term lookup table, which is guarranteed monotonic (important for PI stability) - - register_setup.h which contains the PLL settings in comments as well as register settings for init in the application - - This function and the underlying call to pll_calc may take several seconds to complete since it searches a range - of possible solutions numerically. - """ - input_frequency_MHz = input_frequency / 1000000.0 - target_output_frequency_MHz = target_output_frequency / 1000000.0 - - calc_script = Path(__file__).parent/"pll_calc.py" - - # input freq, app pll, max denom, output freq, min phase comp freq, max ppm error, raw, fractional range, make header - cmd = f"{calc_script} -i {input_frequency_MHz} -a -m {max_denom} -t {target_output_frequency_MHz} -p 6.0 -e {int(ppm_max)} -r --fracmin {fracmin} --fracmax {fracmax} --header" - print(f"Running: {cmd}") - output = subprocess.check_output(cmd.split(), text=True) - - # Get each solution - solutions = [] - Fs = [] - regex = r"Found solution.+\nAPP.+\nAPP.+\nAPP.+" - matches = re.findall(regex, output) - - for solution in matches: - F = int(float(re.search(".+FD\s+(\d+.\d+).+", solution).groups()[0])) - solutions.append(solution) - Fs.append(F) - - possible_Fs = sorted(set(Fs)) - print(f"Available F values: {possible_Fs}") - - # Find first solution with F greater than F - idx = next(x for x, val in enumerate(Fs) if val > min_F) - solution = matches[idx] - - # Get actual PLL register bitfield settings and info - regex = r".+OUT (\d+\.\d+)MHz, VCO (\d+\.\d+)MHz, RD\s+(\d+), FD\s+(\d+.\d*)\s+\(m =\s+(\d+), n =\s+(\d+)\), OD\s+(\d+), FOD\s+(\d+), ERR (-*\d+.\d+)ppm.*" - match = re.search(regex, solution) - - if match: - vals = match.groups() - - output_frequency = (1000000.0 * float(vals[0])) - vco_freq = 1000000.0 * float(vals[1]) - - # Now convert to actual settings in register bitfields - F = int(float(vals[3]) - 1) # PLL integer multiplier - R = int(vals[2]) - 1 # PLL integer divisor - f = int(vals[4]) - 1 # PLL fractional multiplier - p = int(vals[5]) - 1 # PLL fractional divisor - OD = int(vals[6]) - 1 # PLL output divider - ACD = int(vals[7]) - 1 # PLL application clock divider - ppm = float(vals[8]) # PLL PPM error for requrested set frequency - - assert match, f"Could not parse output of: {cmd} output: {solution}" - - # Now get reg values and save to file - with open(register_file, "w") as reg_vals: - reg_vals.write(f"/* Autogenerated by {Path(__file__).name} using command:\n") - reg_vals.write(f" {cmd}\n") - reg_vals.write(f" Picked output solution #{idx}\n") - # reg_vals.write(f"\n{solution}\n\n") # This is verbose and contains the same info as below - reg_vals.write(f" F: {F}\n") - reg_vals.write(f" R: {R}\n") - reg_vals.write(f" f: {f}\n") - reg_vals.write(f" p: {p}\n") - reg_vals.write(f" OD: {OD}\n") - reg_vals.write(f" ACD: {ACD}\n") - reg_vals.write(f" Output freq: {output_frequency}\n") - reg_vals.write(f" VCO freq: {vco_freq} */\n") - reg_vals.write("\n") - - - for reg in ["APP PLL CTL REG", "APP PLL DIV REG", "APP PLL FRAC REG"]: - regex = rf"({reg})\s+(0[xX][A-Fa-f0-9]+)" - match = re.search(regex, solution) - if match: - val = match.groups()[1] - reg_name = reg.replace(" ", "_") - line = f"#define {reg_name} \t{val}\n" - reg_vals.write(line) - - - return output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm - -class pll_solution: - """ - Access to all the info from get_pll_solution, cleaning up temp files. - intended for programatic access from the tests - """ - def __init__(self, *args, **kwargs): - try: - self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs) - self.lut = parse_lut_h_file("fractions.h") - finally: - Path("fractions.h").unlink(missing_ok=True) - Path("register_setup.h").unlink(missing_ok=True) - -class sw_pll_ctrl: - """ - This class instantiates a control loop instance. It takes a lookup table function which can be generated - from the error_from_h class which allows it use the actual pre-calculated transfer function. - Once instantiated, the do_control method runs the control loop. - - This class forms the core of the simulator and allows the constants (K..) to be tuned to acheive the - desired response. The function run_sim allows for a plot of a step resopnse input which allows this - to be done visually. - """ - lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} - - def __init__(self, target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, init_output_count=0, init_ref_clk_count=0, base_lut_index=None, verbose=False): - self.lut_lookup_function = lut_lookup_function - self.multiplier = multiplier - self.ref_to_loop_call_rate = ref_to_loop_call_rate - - self.ref_clk_count = init_output_count # Integer as we run this loop based on the ref clock input count - self.output_count_old = init_output_count # Integer - self.expected_output_count_inc_float = multiplier * ref_to_loop_call_rate - self.expected_output_count_float = 0.0 - - if base_lut_index is None: - base_lut_index = lut_size // 2 - self.base_lut_index = base_lut_index - - self.Kp = Kp - self.Ki = Ki - - self.diff = 0.0 #Most recent diff between expected and actual - self.error_accum = 0.0 #Integral of error - self.error = 0.0 #total error - - self.i_windup_limit = lut_size / Ki if Ki != 0.0 else 0.0 - - self.last_output_frequency = target_output_frequency - - self.verbose = verbose - - if verbose: - print(f"Init sw_pll_ctrl, target_output_frequency: {target_output_frequency} ref_to_loop_call_rate: {ref_to_loop_call_rate}, Kp: {Kp} Ki: {Ki}") - - def get_expected_output_count_inc(self): - return self.expected_output_count_inc_float - - def get_error(self): - return self.error - - - def do_control_from_error(self, error): - """ Calculate the actual output frequency from raw input error term. - """ - self.diff = error # Used by tests - - # clamp integral terms to stop them irrecoverably drifting off. - self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) - - error_p = self.Kp * error; - error_i = self.Ki * self.error_accum - - self.error = error_p + error_i - - if self.verbose: - print(f"diff: {error} error_p: {error_p}({self.Kp}) error_i: {error_i}({self.Ki}) total error: {self.error}") - print(f"expected output_count: {self.expected_output_count_inc_float} actual output_count: {output_count_inc} error: {self.error}") - - actual_output_frequency, lock_status = self.lut_lookup_function(self.base_lut_index - self.error) - - return actual_output_frequency, lock_status - - def do_control(self, output_count_float, period_fraction=1.0): - - """ Calculate the actual output frequency from the input output_count taken at the ref clock time. - If the time of sampling the output_count is not precisely 1.0 x the ref clock time, - you may pass a fraction to allow for a proportional value using period_fraction. This is optional. - """ - if 0 == output_count_float: - return self.lut_lookup_function(self.base_lut_index) - - output_count_int = int(output_count_float) - output_count_inc = output_count_int - self.output_count_old - output_count_inc = output_count_inc / period_fraction - - self.expected_output_count_float = self.output_count_old + self.expected_output_count_inc_float - self.output_count_old = output_count_int - - self.ref_clk_count += self.ref_to_loop_call_rate - - error = output_count_inc - int(self.expected_output_count_inc_float) - actual_output_frequency, lock_status = self.do_control_from_error(error) - - return actual_output_frequency, lock_status - -class audio_modulator: - def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): - self.sample_rate = sample_rate - self.test_tone_hz = test_tone_hz - - # First generate arrays for FM modulation - self.each_sample_number = np.linspace(0, duration_s, int(sample_rate * duration_s)) - self.carrier = 2 * np.pi * self.each_sample_number * test_tone_hz - - # Blank array with 0Hz modulation - k = 2 * np.pi # modulation constant - amplitude of 1.0 = 1Hz deviation - self.modulator = k * self.each_sample_number - - def apply_frequency_deviation(self, start_s, end_s, delta_freq): - start_idx = int(start_s * self.sample_rate) - end_idx = int(end_s * self.sample_rate) - self.modulator[start_idx:end_idx] = self.modulator[start_idx:end_idx] + delta_freq - - - def get_modulated_waveform(self): - # Now create the frequency modulated waveform - waveform = np.cos(self.carrier + self.modulator) - - return waveform - - def save_modulated_wav(self, filename, waveform): - integer_output = np.int16(waveform * 32767) - soundfile.write(filename, integer_output, int(self.sample_rate)) - - def plot_modulated_fft(self, filename, waveform): - xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.each_sample_number.size//2) - N = xf.size - window = np.kaiser(N*2, 14) - waveform = waveform * window - yf = np.fft.fft(waveform) - fig, ax = plt.subplots() - - # Plot a zoom in on the test - tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N) - num_side_bins = 50 - yf = 20 * np.log10(np.abs(yf) / N) - # ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.') - - # Plot the whole frequncy range from DC to nyquist - ax.plot(xf[:N], yf[:N], marker='.') - ax.set_xscale("log") - plt.savefig(filename, dpi=150) - - -def run_sim(target_output_frequency, - nominal_ref_frequency, - lut_lookup_function, - lut_size, - Kp, - Ki, - simulation_iterations, - ppm_shifts, - jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for - test_tone_hz=1000, - verbose=False): +import controller_model + +def run_sim_sw_pll_lut( target_output_frequency, + nominal_ref_frequency, + lut_lookup_function, + lut_size, + Kp, + Ki, + simulation_iterations, + ppm_shifts, + jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for + test_tone_hz=1000, + verbose=False): """ This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response of the sw_pll to changes in input reference frequency. @@ -507,9 +31,14 @@ def run_sim(target_output_frequency, freq_log = [] target_log = [] + + sigma_delta_loop_ratio = 10000 # for every contol update, we do sigma delta this many times + sigma_delta_loop_ratio = 1 # for every contol update, we do sigma delta this many times audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) + sdm = sigma_delta() + for count in range(simulation_iterations): output_count_start_float = output_count_end_float output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate @@ -525,20 +54,27 @@ def run_sim(target_output_frequency, actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) # lock_status = 0 - # Helpers for the tone modulation - time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency - freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz - audio.apply_frequency_deviation(time_in_s(count), time_in_s(count + 1), freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) - + # Sigma delta section + micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio + for sd_count in range(sigma_delta_loop_ratio): + # Helpers for the tone modulation + actual_output_frequency, lock_status = sw_pll.do_deviation(0) - # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency + freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz + start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) + end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) + audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + + freq_log.append(actual_output_frequency) + target_log.append(ref_frequency * multiplier) if verbose: print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") - freq_log.append(actual_output_frequency) - target_log.append(ref_frequency * multiplier) + real_time += ref_to_loop_call_rate / ref_frequency From 1538de93cdb09905fee170060336ca8599132152 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 8 Nov 2023 17:12:16 +0000 Subject: [PATCH 004/118] Upgrade audio modulator to continuous phase --- python/sw_pll/analysis_tools.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index b0de2c20..e7a71b15 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -10,23 +10,21 @@ def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): self.sample_rate = sample_rate self.test_tone_hz = test_tone_hz - # First generate arrays for FM modulation - self.each_sample_number = np.linspace(0, duration_s, int(sample_rate * duration_s)) - self.carrier = 2 * np.pi * self.each_sample_number * test_tone_hz - - # Blank array with 0Hz modulation - k = 2 * np.pi # modulation constant - amplitude of 1.0 = 1Hz deviation - self.modulator = k * self.each_sample_number + self.modulator = np.full(duration_s * sample_rate, test_tone_hz, dtype=np.float64) def apply_frequency_deviation(self, start_s, end_s, delta_freq): start_idx = int(start_s * self.sample_rate) end_idx = int(end_s * self.sample_rate) - self.modulator[start_idx:end_idx] = self.modulator[start_idx:end_idx] + delta_freq + self.modulator[start_idx:end_idx] += delta_freq def get_modulated_waveform(self): # Now create the frequency modulated waveform - waveform = np.cos(self.carrier + self.modulator) + # this is designed to accumulate the phase so doesn't see discontinuities + # https://dsp.stackexchange.com/questions/80768/fsk-modulation-with-python + delta_phi = self.modulator * np.pi / (self.sample_rate / 2.0) + phi = np.cumsum(delta_phi) + waveform = np.sin(phi) return waveform @@ -35,7 +33,7 @@ def save_modulated_wav(self, filename, waveform): soundfile.write(filename, integer_output, int(self.sample_rate)) def plot_modulated_fft(self, filename, waveform): - xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.each_sample_number.size//2) + xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.modulator.size//2) N = xf.size window = np.kaiser(N*2, 14) waveform = waveform * window @@ -60,9 +58,11 @@ def plot_modulated_fft(self, filename, waveform): test_len = 10 audio = audio_modulator(test_len) for time_s in range(test_len): - modulation_hz = 10000 * (time_s - (test_len) / 2) + modulation_hz = 10 * (time_s - (test_len) / 2) audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz) + # audio.apply_frequency_deviation(4, 6, -500) + modulated_tone = audio.get_modulated_waveform() audio.save_modulated_wav("modulated.wav", modulated_tone) audio.plot_modulated_fft("modulated_fft.png", modulated_tone) From 9b9cafbae248676ca21a2c0c7e0d4eccf1f4db0e Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 08:52:38 +0000 Subject: [PATCH 005/118] Initial loop closed for LUT sw_pll model --- python/sw_pll/app_pll_model.py | 4 +- python/sw_pll/controller_model.py | 7 +- python/sw_pll/dco_model.py | 62 ++--- python/sw_pll/pfd_model.py | 10 +- python/sw_pll/sw_pll_sim.py | 412 +++++++++++++++++------------- 5 files changed, 261 insertions(+), 234 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index b6c70a03..6b443ec6 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -190,9 +190,9 @@ def __init__(self, *args, **kwargs): This module is not intended to be run directly. This is here for internal testing only. """ input_frequency = 24000000 - output_frequency = 24576000 + output_frequency = 12288000 print(f"get_pll_solution input_frequency: {input_frequency} output_frequency: {output_frequency}...") - output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(24000000, 24576000) + output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(input_frequency, output_frequency) print(f"got solution: \noutput_frequency: {output_frequency}\nvco_freq: {vco_freq}\nF: {F}\nR: {R}\nf: {f}\np: {p}\nOD: {OD}\nACD: {ACD}\nppm: {ppm}") app_pll = app_pll_frac_calc(input_frequency, F, R, f, p, OD, ACD) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 87aee072..a411b9d5 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -14,7 +14,6 @@ class sw_pll_lut_pi_ctrl(dco_model.lut_dco): desired response. The function run_sim allows for a plot of a step resopnse input which allows this to be done visually. """ - lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): @@ -63,12 +62,12 @@ def do_control_from_error(self, error): error_i = self.Ki * self.error_accum error_ii = self.Kii * self.error_accum_accum - self.error = error_p + error_i + error_ii + self.total_error = error_p + error_i + error_ii if self.verbose: - print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.error}") + print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.total_error}") - dco_ctrl = self.base_lut_index - self.error + dco_ctrl = self.base_lut_index - self.total_error return dco_ctrl diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 196dff2b..2cf307ec 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -30,6 +30,8 @@ class lut_dco: This class parses a pre-generated fractions.h file and builds a lookup table so that the values can be used by the sw_pll simulation. It may be used directly but is generally used a sub class of error_to_pll_output_frequency. """ + lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} + def __init__(self, header_file = "fractions.h", force_lut_gen=False, verbose=False): # fixed header_file name by pll_calc.py """ Constructor for the LUT DCO. Reads the pre-calculated header file and produces the LUT which contains @@ -62,6 +64,7 @@ def __init__(self, header_file = "fractions.h", force_lut_gen=False, verbose=Fal # print(f"min_frac: {min_frac} max_frac: {max_frac}") + self.lock_status = -1 self.lut = lut self.min_frac = min_frac self.max_frac = max_frac @@ -156,50 +159,27 @@ def plot_freq_range(self): # plt.show() plt.savefig("lut_dco_range.png", dpi=150) - # def get_frequency_from_error(error, lut, app_pll_frac_calc): - # """ - # given an error, a lut, and a pll, calculate the frequency - # """ - # num_entries = np.size(lut) - - # set_point = int(error) # Note negative term for neg feedback - # if set_point < 0: - # set_point = 0 - # lock_status = -1 - # elif set_point >= num_entries: - # set_point = num_entries - 1 - # lock_status = 1 - # else: - # set_point = set_point - # lock_status = 0 - - # register = int(lut[set_point]) - # pll.update_pll_frac_reg(register) - - # return pll.get_output_frequency(), lock_status - -# class error_to_pll_output_frequency(app_pll_frac_calc, parse_lut_h_file): -# """ -# This super class combines app_pll_frac_calc and parse_lut_h_file and provides a way of inputting the error signal and -# providing an output frequency for a given set of PLL configuration parameters. It includes additional methods for -# turning the LUT register settings parsed by parse_lut_h_file into fractional values which can be fed into app_pll_frac_calc. - -# It also contains information reporting methods which provide the range and step sizes of the PLL configuration as well as -# plotting the transfer function from error to frequency so the linearity and regularity the transfer function can be observed. -# """ - -# def __init__(self, header_file, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False): -# self.app_pll_frac_calc = app_pll_frac_calc.__init__(self, input_frequency, F_init, R_init, OD_init, ACD_init, f_init, p_init, verbose=False) -# self.parse_lut_h_file = parse_lut_h_file.__init__(self, header_file, verbose=False) -# self.verbose = verbose - + def get_frequency_from_error(self, error): + """ + given an error, a LUTT, and an APP_PLL, calculate the frequency + """ + num_entries = self.get_lut_size() + set_point = int(error) + if set_point < 0: + set_point = 0 + self.lock_status = -1 + elif set_point >= num_entries: + set_point = num_entries - 1 + self.lock_status = 1 + else: + set_point = set_point + self.lock_status = 0 + register = int(self.lut[set_point]) + return self.app_pll.update_frac_reg(register), self.lock_status -# def _get_output_frequency_from_error(self, error): -# lut = self.get_lut() -# return get_frequency_from_error(error, lut, self) ###################################### # SIGMA DELTA MODULATOR IMPLEMENTATION @@ -254,6 +234,6 @@ def __init__(self): """ dco = lut_dco() print(f"LUT size: {dco.get_lut_size()}") - print(f"LUT : {dco.get_lut()}") + # print(f"LUT : {dco.get_lut()}") dco.plot_freq_range() dco.print_stats() diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index d674f70a..58ce170c 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -4,13 +4,11 @@ import controller_model class port_timer_pfd(): - def __init__(self, nominal_output_hz, nominal_nominal_control_rate_hz): + def __init__(self, nominal_output_hz, nominal_control_rate_hz): self.output_count_last_int = 0 # Integer value of last output_clock_count self.expected_output_count_inc = nominal_output_hz / nominal_control_rate_hz - print(f"expected_output_count_inc: {self.expected_output_count_inc}") - def get_error(self, output_clock_count_float, period_fraction=1.0): """ @@ -23,12 +21,12 @@ def get_error(self, output_clock_count_float, period_fraction=1.0): """ output_count_int = int(output_clock_count_float) # round down to nearest int to match hardware - expected_output_count = self.output_count_last_int + int(self.expected_output_count_inc * period_fraction) # Compensate for jitter if period fraction is specified - self.output_count_last_int = output_count_int - + print(output_count_int, expected_output_count) error = output_count_int - expected_output_count + self.output_count_last_int = output_count_int + return error diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 0a10e590..48da5651 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -1,204 +1,254 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -import controller_model - -def run_sim_sw_pll_lut( target_output_frequency, - nominal_ref_frequency, - lut_lookup_function, - lut_size, - Kp, - Ki, - simulation_iterations, - ppm_shifts, - jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for - test_tone_hz=1000, - verbose=False): - """ - This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response - of the sw_pll to changes in input reference frequency. - A plot of the simulation is generated to allow visual inspection and tuning. - """ +from pfd_model import port_timer_pfd +from dco_model import lut_dco +from controller_model import sw_pll_lut_pi_ctrl +from analysis_tools import audio_modulator - ref_frequency = nominal_ref_frequency - sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) - - output_count_end_float = 0.0 - real_time = 0.0 - actual_output_frequency = target_output_frequency - last_count = 0 +class sim_sw_pll_lut: + def __init__( self, + target_output_frequency, + nominal_nominal_control_rate_frequency, + Kp, + Ki, + Kii=None): - freq_log = [] - target_log = [] + self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) + self.controller = sw_pll_lut_pi_ctrl(Kp, Ki) + self.dco = lut_dco() - sigma_delta_loop_ratio = 10000 # for every contol update, we do sigma delta this many times - sigma_delta_loop_ratio = 1 # for every contol update, we do sigma delta this many times - - audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) + self.target_output_frequency = target_output_frequency + self.time = 0.0 + self.control_time_inc = 1 / nominal_nominal_control_rate_frequency - sdm = sigma_delta() + def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False): + """ + This should be called once every control period nominally + """ - for count in range(simulation_iterations): - output_count_start_float = output_count_end_float - output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate - - # Add some jitter to the output_count to test jitter compensation - output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) - output_count_end_float += output_count_float_inc + output_sample_jitter - # Compensate for the jitter - period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc + error = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) + dco_ctl = self.controller.do_control_from_error(error) + output_frequency, lock_status = self.dco.get_frequency_from_error(dco_ctl) - # print(f"output_count_float_inc: {output_count_float_inc}, period_fraction: {period_fraction}, ratio: {output_count_float_inc / period_fraction}") + if verbose: + print(f"Raw error: {error}") + print(f"dco_ctl: {dco_ctl}") + print(f"Output_frequency: {output_frequency}") + print(f"Lock status: {self.dco.lock_status_lookup[lock_status]}") + + return output_frequency, lock_status + + +# def run_sim_sw_pll_lut( target_output_frequency, +# nominal_ref_frequency, +# lut_lookup_function, +# lut_size, +# Kp, +# Ki, +# simulation_iterations, +# ppm_shifts, +# jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for +# test_tone_hz=1000, +# verbose=False): +# """ +# This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response +# of the sw_pll to changes in input reference frequency. +# A plot of the simulation is generated to allow visual inspection and tuning. +# """ + +# ref_frequency = nominal_ref_frequency +# sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) + +# output_count_end_float = 0.0 +# real_time = 0.0 +# actual_output_frequency = target_output_frequency - actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) - # lock_status = 0 +# last_count = 0 - # Sigma delta section - micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio - for sd_count in range(sigma_delta_loop_ratio): - # Helpers for the tone modulation - actual_output_frequency, lock_status = sw_pll.do_deviation(0) +# freq_log = [] +# target_log = [] - time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency - freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz - start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) - end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) - audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) - # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) - freq_log.append(actual_output_frequency) - target_log.append(ref_frequency * multiplier) + +# audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) - if verbose: - print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") +# for count in range(simulation_iterations): +# output_count_start_float = output_count_end_float +# output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate + +# # Add some jitter to the output_count to test jitter compensation +# output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) +# output_count_end_float += output_count_float_inc + output_sample_jitter +# # Compensate for the jitter +# period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc + +# # print(f"output_count_float_inc: {output_count_float_inc}, period_fraction: {period_fraction}, ratio: {output_count_float_inc / period_fraction}") + +# actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) +# # lock_status = 0 + +# # Sigma delta section +# micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio +# for sd_count in range(sigma_delta_loop_ratio): +# # Helpers for the tone modulation +# actual_output_frequency, lock_status = sw_pll.do_deviation(0) + +# time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency +# freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz +# start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) +# end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) +# audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) +# # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + +# freq_log.append(actual_output_frequency) +# target_log.append(ref_frequency * multiplier) + +# if verbose: +# print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") - real_time += ref_to_loop_call_rate / ref_frequency - - - # A number of events where the input reference is stepped - ppm_adjust = lambda f, ppm: f * (1 + (ppm / 1000000)) - for ppm_shift in ppm_shifts: - (change_at_count, ppm) = ppm_shift - if count == change_at_count: - ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) - - - plt.clf() - plt.plot(freq_log, color='red', marker='.', label='actual frequency') - plt.plot(target_log, color='blue', marker='.', label='target frequency') - plt.title('PLL tracking', fontsize=14) - plt.xlabel(f'loop_cycle {ref_to_loop_call_rate}', fontsize=14) - plt.ylabel('Frequency', fontsize=10) - plt.legend(loc="upper right") - plt.grid(True) - # plt.show() - plt.savefig("pll_step_response.png", dpi=150) - - # Generate fft of modulated test tone - audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) - audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) - - -""" -ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks -xtal_frequency - The xcore clock frequency -nominal_ref_frequency - The nominal input reference frequency -target_output_frequency - The nominal target output frequency -max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance -min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance -ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance -fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance -fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance -""" - -ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz -xtal_frequency = 24000000 -profile_choice = 0 - -# Example profiles to produce typical frequencies seen in audio systems -profiles = [ - # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, - # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, - # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, - # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size - {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, - ] - - +# real_time += ref_to_loop_call_rate / ref_frequency + + +# # A number of events where the input reference is stepped +# ppm_adjust = lambda f, ppm: f * (1 + (ppm / 1000000)) +# for ppm_shift in ppm_shifts: +# (change_at_count, ppm) = ppm_shift +# if count == change_at_count: +# ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) + + +# plt.clf() +# plt.plot(freq_log, color='red', marker='.', label='actual frequency') +# plt.plot(target_log, color='blue', marker='.', label='target frequency') +# plt.title('PLL tracking', fontsize=14) +# plt.xlabel(f'loop_cycle {ref_to_loop_call_rate}', fontsize=14) +# plt.ylabel('Frequency', fontsize=10) +# plt.legend(loc="upper right") +# plt.grid(True) +# # plt.show() +# plt.savefig("pll_step_response.png", dpi=150) + +# # Generate fft of modulated test tone +# audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) +# audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) + + +# """ +# ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks +# xtal_frequency - The xcore clock frequency +# nominal_ref_frequency - The nominal input reference frequency +# target_output_frequency - The nominal target output frequency +# max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance +# min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance +# ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance +# fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance +# fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance +# """ + +# ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz +# xtal_frequency = 24000000 +# profile_choice = 0 + +# # Example profiles to produce typical frequencies seen in audio systems +# profiles = [ +# # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, +# # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, +# # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, +# # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, +# # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, +# # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size +# {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, +# ] + + + +# if __name__ == '__main__': +# """ +# This script checks to see if PLL settings have already been generated, if not, generates them. +# It then uses these settings to generate a LUT and control loop instance. +# A set of step functions in input reference frequencies are then generated and the +# response of the sw_pll to these changes is logged and then plotted. +# """ + +# profile_used = profiles[profile_choice] + +# # Make a list of the correct args for get_pll_solution +# get_pll_solution_args = {"input_frequency":xtal_frequency} +# get_pll_solution_args.update(profile_used) +# del get_pll_solution_args["nominal_ref_frequency"] +# get_pll_solution_args = list(get_pll_solution_args.values()) + +# # Extract the required vals from the profile +# target_output_frequency = profile_used["target_output_frequency"] +# nominal_ref_frequency = profile_used["nominal_ref_frequency"] +# multiplier = target_output_frequency / nominal_ref_frequency +# # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 + + +# # Use pre-caclulated saved values if they exist, otherwise generate new ones +# if not os.path.exists(header_file) or not os.path.exists(register_file): +# output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(*get_pll_solution_args) +# print(f"output_frequency: {output_frequency}, vco_freq: {vco_freq}, F: {F}, R: {R}, f: {f}, p: {p}, OD: {OD}, ACD: {ACD}, ppm: {ppm}") +# else: +# F, R, f, p, OD, ACD = parse_register_file(register_file) +# print(f"Using pre-calculated settings read from {header_file} and {register_file}:") + +# print(f"PLL register settings F: {F}, R: {R}, OD: {OD}, ACD: {ACD}, f: {f}, p: {p}") + +# # Instantiate controller +# error_from_h = error_to_pll_output_frequency(header_file, xtal_frequency, F, R, OD, ACD, f, p, verbose=False) +# error_from_h.plot_freq_range() + +# min_freq, mid_freq, max_freq, steps = error_from_h.get_stats() +# step_size = ((max_freq - min_freq) / steps) + +# print(f"min_freq: {min_freq:.0f}Hz") +# print(f"mid_freq: {mid_freq:.0f}Hz") +# print(f"max_freq: {max_freq:.0f}Hz") +# print(f"average step size: {step_size:.6}Hz, PPM: {1e6 * step_size/mid_freq:.6}") +# print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") +# print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") +# print(f"LUT entries: {steps} ({steps*2} bytes)") + + +# # PI loop control constants +# Kp = 0.0 +# Ki = 1.0 +# simulation_iterations = 150 +# ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) +# ppm_shifts = () # Straight run with no PPM deviation + +# run_sim(target_output_frequency, +# nominal_ref_frequency, +# error_from_h.get_output_frequency_from_error, +# error_from_h.get_lut_size(), +# Kp, +# Ki, +# simulation_iterations, +# ppm_shifts, +# verbose=False) if __name__ == '__main__': """ - This script checks to see if PLL settings have already been generated, if not, generates them. - It then uses these settings to generate a LUT and control loop instance. - A set of step functions in input reference frequencies are then generated and the - response of the sw_pll to these changes is logged and then plotted. + This module is not intended to be run directly. This is here for internal testing only. """ - - profile_used = profiles[profile_choice] - - # Make a list of the correct args for get_pll_solution - get_pll_solution_args = {"input_frequency":xtal_frequency} - get_pll_solution_args.update(profile_used) - del get_pll_solution_args["nominal_ref_frequency"] - get_pll_solution_args = list(get_pll_solution_args.values()) - - # Extract the required vals from the profile - target_output_frequency = profile_used["target_output_frequency"] - nominal_ref_frequency = profile_used["nominal_ref_frequency"] - multiplier = target_output_frequency / nominal_ref_frequency - # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 - - - # Use pre-caclulated saved values if they exist, otherwise generate new ones - if not os.path.exists(header_file) or not os.path.exists(register_file): - output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(*get_pll_solution_args) - print(f"output_frequency: {output_frequency}, vco_freq: {vco_freq}, F: {F}, R: {R}, f: {f}, p: {p}, OD: {OD}, ACD: {ACD}, ppm: {ppm}") - else: - F, R, f, p, OD, ACD = parse_register_file(register_file) - print(f"Using pre-calculated settings read from {header_file} and {register_file}:") - - print(f"PLL register settings F: {F}, R: {R}, OD: {OD}, ACD: {ACD}, f: {f}, p: {p}") - - # Instantiate controller - error_from_h = error_to_pll_output_frequency(header_file, xtal_frequency, F, R, OD, ACD, f, p, verbose=False) - error_from_h.plot_freq_range() - - min_freq, mid_freq, max_freq, steps = error_from_h.get_stats() - step_size = ((max_freq - min_freq) / steps) - - print(f"min_freq: {min_freq:.0f}Hz") - print(f"mid_freq: {mid_freq:.0f}Hz") - print(f"max_freq: {max_freq:.0f}Hz") - print(f"average step size: {step_size:.6}Hz, PPM: {1e6 * step_size/mid_freq:.6}") - print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") - print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") - print(f"LUT entries: {steps} ({steps*2} bytes)") - - - # PI loop control constants - Kp = 0.0 - Ki = 1.0 - simulation_iterations = 150 - ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) - ppm_shifts = () # Straight run with no PPM deviation - - run_sim(target_output_frequency, - nominal_ref_frequency, - error_from_h.get_output_frequency_from_error, - error_from_h.get_lut_size(), - Kp, - Ki, - simulation_iterations, - ppm_shifts, - verbose=False) + nominal_output_hz = 12288000 + nominal_control_rate_hz = 93.75 + output_frequency = nominal_output_hz + + sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, 0.0, 0.1, Kii=0.0) + output_clock_count = 0.0 + for i in range(50): + output_clock_count += output_frequency / nominal_control_rate_hz + 10 + output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, verbose=True) + From 30892e325880d2bde8de320db092ac97013bce17 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 14:01:23 +0000 Subject: [PATCH 006/118] Enable jitter compensation --- python/sw_pll/dco_model.py | 64 +++++++------ python/sw_pll/sw_pll_sim.py | 182 +++++++++++++++--------------------- 2 files changed, 114 insertions(+), 132 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 2cf307ec..4e4eab8a 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -32,13 +32,23 @@ class lut_dco: """ lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} - def __init__(self, header_file = "fractions.h", force_lut_gen=False, verbose=False): # fixed header_file name by pll_calc.py + def __init__(self, header_file = "fractions.h", verbose=False): # fixed header_file name by pll_calc.py """ - Constructor for the LUT DCO. Reads the pre-calculated header file and produces the LUT which contains - the pll fractional register settings (16b) for each of the entries + Constructor for the LUT DCO. Reads the pre-calculated header filed and produces the LUT which contains + the pll fractional register settings (16b) for each of the entries. Also a """ + + self.lut, self.min_frac, self.max_frac = self._read_lut_header(header_file) + input_freq, F, R, f, p, OD, ACD = self._parse_register_file(app_pll_model.register_file) + self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + + self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2]) + self.lock_status = -1 + + def _read_lut_header(self, header_file): if not os.path.exists(header_file): assert False, f"Please initialize a lut_dco to produce a parsable header file {header_file}" + with open(header_file) as hdr: header = hdr.readlines() min_frac = 1.0 @@ -62,25 +72,16 @@ def __init__(self, header_file = "fractions.h", force_lut_gen=False, verbose=Fal max_frac = frac if frac > max_frac else max_frac lut[idx] = reg - # print(f"min_frac: {min_frac} max_frac: {max_frac}") - - self.lock_status = -1 - self.lut = lut - self.min_frac = min_frac - self.max_frac = max_frac - + # print(f"min_frac: {min_frac} max_frac: {max_frac}") + return lut, min_frac, max_frac - # if not os.path.exists(app_pll_model.register_file) or not os.path.exists(header_file) or force_lut_gen: - # pll = app_pll_model.app_pll_frac_calc(app_pll_model.register_file) - - input_freq, F, R, f, p, OD, ACD = self.parse_register_file(app_pll_model.register_file) - self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) - - def parse_register_file(self, register_file): + def _parse_register_file(self, register_file): """ This method reads the pre-saved register setup comments from get_pll_solution and parses them into parameters that can be used for later simulation. """ + if not os.path.exists(register_file): + assert False, f"Please initialize a lut_dco to produce a parsable register setup file {register_file}" with open(register_file) as rf: reg_file = rf.read().replace('\n', '') @@ -106,7 +107,7 @@ def get_lut_size(self): """ return np.size(self.lut) - def print_stats(self): + def print_stats(self, target_output_frequency): """ Returns a summary of the LUT range and steps. """ @@ -122,10 +123,15 @@ def print_stats(self): register = int(lut[-1]) max_freq = self.app_pll.update_frac_reg(register) - print(f"LUT min_freq: {min_freq}") - print(f"LUT mid_freq: {mid_freq}") - print(f"LUT max_freq: {max_freq}") - print(f"LUT steps: {steps}") + ave_step_size = (max_freq - min_freq) / steps + + print(f"LUT min_freq: {min_freq:.0f}Hz") + print(f"LUT mid_freq: {mid_freq:.0f}Hz") + print(f"LUT max_freq: {max_freq:.0f}Hz") + print(f"LUT entries: {steps} ({steps*2} bytes)") + print(f"LUT average step size: {ave_step_size:.6}Hz, PPM: {1e6 * ave_step_size/mid_freq:.6}") + print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") + print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") return min_freq, mid_freq, max_freq, steps @@ -135,7 +141,6 @@ def _reg_to_frac(self, register): return f, p - def plot_freq_range(self): """ Generates a plot of the frequency range of the LUT and @@ -161,8 +166,12 @@ def plot_freq_range(self): def get_frequency_from_error(self, error): """ - given an error, a LUTT, and an APP_PLL, calculate the frequency + given an error, a LUT, and an APP_PLL, calculate the frequency """ + + if error is None: + return self.last_output_frequency, self.lock_status + num_entries = self.get_lut_size() set_point = int(error) @@ -177,7 +186,10 @@ def get_frequency_from_error(self, error): self.lock_status = 0 register = int(self.lut[set_point]) - return self.app_pll.update_frac_reg(register), self.lock_status + + output_frequency = self.app_pll.update_frac_reg(register) + self.last_output_frequency = output_frequency + return output_frequency, self.lock_status @@ -236,4 +248,4 @@ def __init__(self): print(f"LUT size: {dco.get_lut_size()}") # print(f"LUT : {dco.get_lut()}") dco.plot_freq_range() - dco.print_stats() + dco.print_stats(12288000) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 48da5651..3c588ea9 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -5,7 +5,8 @@ from dco_model import lut_dco from controller_model import sw_pll_lut_pi_ctrl from analysis_tools import audio_modulator - +import matplotlib.pyplot as plt +import numpy as np class sim_sw_pll_lut: def __init__( self, @@ -28,8 +29,8 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False This should be called once every control period nominally """ - error = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) - dco_ctl = self.controller.do_control_from_error(error) + error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) + dco_ctl = self.controller.do_control_from_error(error, first_loop=first_loop) output_frequency, lock_status = self.dco.get_frequency_from_error(dco_ctl) if verbose: @@ -41,35 +42,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False return output_frequency, lock_status -# def run_sim_sw_pll_lut( target_output_frequency, -# nominal_ref_frequency, -# lut_lookup_function, -# lut_size, -# Kp, -# Ki, -# simulation_iterations, -# ppm_shifts, -# jitter_amplitude=100, # Used to simulate port timer sampling time jitter, which can be compensated for -# test_tone_hz=1000, -# verbose=False): -# """ -# This function uses the sw_pll_ctrl and passed lut_lookup_function to run a simulation of the response -# of the sw_pll to changes in input reference frequency. -# A plot of the simulation is generated to allow visual inspection and tuning. -# """ - -# ref_frequency = nominal_ref_frequency -# sw_pll = sw_pll_ctrl(target_output_frequency, lut_lookup_function, lut_size, multiplier, ref_to_loop_call_rate, Kp, Ki, verbose=False) - -# output_count_end_float = 0.0 -# real_time = 0.0 -# actual_output_frequency = target_output_frequency - -# last_count = 0 - -# freq_log = [] -# target_log = [] - # audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) @@ -122,17 +94,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False # ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) -# plt.clf() -# plt.plot(freq_log, color='red', marker='.', label='actual frequency') -# plt.plot(target_log, color='blue', marker='.', label='target frequency') -# plt.title('PLL tracking', fontsize=14) -# plt.xlabel(f'loop_cycle {ref_to_loop_call_rate}', fontsize=14) -# plt.ylabel('Frequency', fontsize=10) -# plt.legend(loc="upper right") -# plt.grid(True) -# # plt.show() -# plt.savefig("pll_step_response.png", dpi=150) - # # Generate fft of modulated test tone # audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) # audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) @@ -150,26 +111,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False # fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance # """ -# ref_to_loop_call_rate = 512 # this results in the control loop being called at a rate of nominal_ref_frequency / ref_to_loop_call_rate. Typically ~100Hz -# xtal_frequency = 24000000 -# profile_choice = 0 - -# # Example profiles to produce typical frequencies seen in audio systems -# profiles = [ -# # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, -# # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, -# # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, -# # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, -# # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, -# # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size -# {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, -# ] - # if __name__ == '__main__': @@ -195,31 +136,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False # # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 -# # Use pre-caclulated saved values if they exist, otherwise generate new ones -# if not os.path.exists(header_file) or not os.path.exists(register_file): -# output_frequency, vco_freq, F, R, f, p, OD, ACD, ppm = get_pll_solution(*get_pll_solution_args) -# print(f"output_frequency: {output_frequency}, vco_freq: {vco_freq}, F: {F}, R: {R}, f: {f}, p: {p}, OD: {OD}, ACD: {ACD}, ppm: {ppm}") -# else: -# F, R, f, p, OD, ACD = parse_register_file(register_file) -# print(f"Using pre-calculated settings read from {header_file} and {register_file}:") - -# print(f"PLL register settings F: {F}, R: {R}, OD: {OD}, ACD: {ACD}, f: {f}, p: {p}") - -# # Instantiate controller -# error_from_h = error_to_pll_output_frequency(header_file, xtal_frequency, F, R, OD, ACD, f, p, verbose=False) -# error_from_h.plot_freq_range() - -# min_freq, mid_freq, max_freq, steps = error_from_h.get_stats() -# step_size = ((max_freq - min_freq) / steps) - -# print(f"min_freq: {min_freq:.0f}Hz") -# print(f"mid_freq: {mid_freq:.0f}Hz") -# print(f"max_freq: {max_freq:.0f}Hz") -# print(f"average step size: {step_size:.6}Hz, PPM: {1e6 * step_size/mid_freq:.6}") -# print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") -# print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") -# print(f"LUT entries: {steps} ({steps*2} bytes)") - # # PI loop control constants # Kp = 0.0 @@ -228,27 +144,81 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False # ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) # ppm_shifts = () # Straight run with no PPM deviation -# run_sim(target_output_frequency, -# nominal_ref_frequency, -# error_from_h.get_output_frequency_from_error, -# error_from_h.get_lut_size(), -# Kp, -# Ki, -# simulation_iterations, -# ppm_shifts, -# verbose=False) + +def plot_simulation(freq_log, target_freq_log, real_time_log): + plt.clf() + plt.plot(real_time_log, freq_log, color='red', marker='.', label='actual frequency') + plt.plot(real_time_log, target_freq_log, color='blue', marker='.', label='target frequency') + plt.title('PLL tracking', fontsize=14) + plt.xlabel(f'Time in seconds', fontsize=10) + plt.ylabel('Frequency', fontsize=10) + plt.legend(loc="upper right") + plt.grid(True) + # plt.show() + plt.savefig("pll_step_response.png", dpi=150) + + +def run_lut_sw_pll_sim(): + nominal_output_hz = 12288000 + nominal_control_rate_hz = 93.75 + output_frequency = nominal_output_hz + simulation_iterations = 100 + Kp = 0.0 + Ki = 0.1 + Kii = 0.0 + + + sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) + output_clock_count = 0 + + freq_log = [] + target_freq_log = [] + real_time_log = [] + real_time = 0.0 + + ppm_shift = -5 + period_fraction = 1.0 + + for loop in range(simulation_iterations): + output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=True) + measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6) + # Add some jitter to the output_count to test jitter compensation + jitter_amplitude = 100 # measured in output clock counts + clock_count_sampling_jitter = jitter_amplitude * (np.random.sample() - 0.5) + period_fraction = (measured_clock_count_inc + clock_count_sampling_jitter) * measured_clock_count_inc + + output_clock_count += measured_clock_count_inc * period_fraction + + real_time_log.append(real_time) + target_output_frequency = nominal_output_hz * (1 + ppm_shift / 1e6) + target_freq_log.append(target_output_frequency) + freq_log.append(output_frequency) + + real_time += 1 / nominal_control_rate_hz + + plot_simulation(freq_log, target_freq_log, real_time_log) if __name__ == '__main__': """ This module is not intended to be run directly. This is here for internal testing only. """ - nominal_output_hz = 12288000 - nominal_control_rate_hz = 93.75 - output_frequency = nominal_output_hz - - sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, 0.0, 0.1, Kii=0.0) - output_clock_count = 0.0 - for i in range(50): - output_clock_count += output_frequency / nominal_control_rate_hz + 10 - output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, verbose=True) + run_lut_sw_pll_sim() + + +# # Example profiles to produce typical frequencies seen in audio systems +# profiles = [ +# # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, +# # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, +# # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, +# # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, +# # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size +# {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, +# # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size +# {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, +# ] + From 381e7f953cc80c97e6861472530a620289a18534 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 14:01:49 +0000 Subject: [PATCH 007/118] Add first_loop to match C --- python/sw_pll/controller_model.py | 11 +++++++++-- python/sw_pll/pfd_model.py | 31 +++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index a411b9d5..9a6c425c 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -48,7 +48,11 @@ def get_dco_ctrl(self): return self.error - def do_control_from_error(self, error): + def _reset_controller(self): + self.error_accum = 0.0 + self.error_accum_accum = 0.0 + + def do_control_from_error(self, error, first_loop=False): """ Calculate the LUT setting from the input error """ @@ -58,6 +62,9 @@ def do_control_from_error(self, error): self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) self.error_accum_accum = np.clip(self.error_accum_accum + self.error_accum, -self.ii_windup_limit, self.ii_windup_limit) + if first_loop: + self._reset_controller() + error_p = self.Kp * error; error_i = self.Ki * self.error_accum error_ii = self.Kii * self.error_accum_accum @@ -69,7 +76,7 @@ def do_control_from_error(self, error): dco_ctrl = self.base_lut_index - self.total_error - return dco_ctrl + return None if first_loop else dco_ctrl diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index 58ce170c..daef007c 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -4,10 +4,11 @@ import controller_model class port_timer_pfd(): - def __init__(self, nominal_output_hz, nominal_control_rate_hz): - self.output_count_last_int = 0 # Integer value of last output_clock_count - - self.expected_output_count_inc = nominal_output_hz / nominal_control_rate_hz + def __init__(self, nominal_output_hz, nominal_control_rate_hz, ppm_range=1000): + self.output_count_last = 0.0 # Integer value of last output_clock_count + self.first_loop = True + self.ppm_range = ppm_range + self.expected_output_clock_count_inc = nominal_output_hz / nominal_control_rate_hz def get_error(self, output_clock_count_float, period_fraction=1.0): @@ -21,13 +22,23 @@ def get_error(self, output_clock_count_float, period_fraction=1.0): """ output_count_int = int(output_clock_count_float) # round down to nearest int to match hardware - expected_output_count = self.output_count_last_int + int(self.expected_output_count_inc * period_fraction) # Compensate for jitter if period fraction is specified - print(output_count_int, expected_output_count) - error = output_count_int - expected_output_count - - self.output_count_last_int = output_count_int + output_count_inc = output_count_int - self.output_count_last + output_count_inc = output_count_inc / period_fraction + + expected_output_clock_count = self.output_count_last + self.expected_output_clock_count_inc + + error = output_count_inc - int(self.expected_output_clock_count_inc) + print(output_count_inc, self.expected_output_clock_count_inc) + + if abs(error) > (self.ppm_range / 1e6) * self.expected_output_clock_count_inc: + print("FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) + self.first_loop = True + else: + self.first_loop = False + + self.output_count_last = output_count_int - return error + return error, self.first_loop From b79c92dec3211f1064b82bfda85c20807643ad5d Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 15:25:01 +0000 Subject: [PATCH 008/118] Sim now generates audio modulated tone --- python/sw_pll/analysis_tools.py | 2 +- python/sw_pll/pfd_model.py | 1 + python/sw_pll/sw_pll_sim.py | 73 ++++++++++++--------------------- 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index e7a71b15..1eeb42b8 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -10,7 +10,7 @@ def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): self.sample_rate = sample_rate self.test_tone_hz = test_tone_hz - self.modulator = np.full(duration_s * sample_rate, test_tone_hz, dtype=np.float64) + self.modulator = np.full(int(duration_s * sample_rate), test_tone_hz, dtype=np.float64) def apply_frequency_deviation(self, start_s, end_s, delta_freq): start_idx = int(start_s * self.sample_rate) diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index daef007c..533a7013 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -30,6 +30,7 @@ def get_error(self, output_clock_count_float, period_fraction=1.0): error = output_count_inc - int(self.expected_output_clock_count_inc) print(output_count_inc, self.expected_output_clock_count_inc) + # Apply out of range detection so that the controller ignores startup or missed control loops (as per C) if abs(error) > (self.ppm_range / 1e6) * self.expected_output_clock_count_inc: print("FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) self.first_loop = True diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 3c588ea9..5f37a7f0 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -99,50 +99,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False # audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) -# """ -# ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks -# xtal_frequency - The xcore clock frequency -# nominal_ref_frequency - The nominal input reference frequency -# target_output_frequency - The nominal target output frequency -# max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance -# min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance -# ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance -# fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance -# fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance -# """ - - - -# if __name__ == '__main__': -# """ -# This script checks to see if PLL settings have already been generated, if not, generates them. -# It then uses these settings to generate a LUT and control loop instance. -# A set of step functions in input reference frequencies are then generated and the -# response of the sw_pll to these changes is logged and then plotted. -# """ - -# profile_used = profiles[profile_choice] - -# # Make a list of the correct args for get_pll_solution -# get_pll_solution_args = {"input_frequency":xtal_frequency} -# get_pll_solution_args.update(profile_used) -# del get_pll_solution_args["nominal_ref_frequency"] -# get_pll_solution_args = list(get_pll_solution_args.values()) - -# # Extract the required vals from the profile -# target_output_frequency = profile_used["target_output_frequency"] -# nominal_ref_frequency = profile_used["nominal_ref_frequency"] -# multiplier = target_output_frequency / nominal_ref_frequency -# # input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95 - - - -# # PI loop control constants -# Kp = 0.0 -# Ki = 1.0 -# simulation_iterations = 150 -# ppm_shifts = ((250, 300), (500, 150), (800, -200), (1300, 0)) -# ppm_shifts = () # Straight run with no PPM deviation def plot_simulation(freq_log, target_freq_log, real_time_log): @@ -167,20 +123,24 @@ def run_lut_sw_pll_sim(): Ki = 0.1 Kii = 0.0 - sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) output_clock_count = 0 + + audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=1000) + freq_log = [] target_freq_log = [] real_time_log = [] real_time = 0.0 + period_fraction = 1.0 ppm_shift = -5 - period_fraction = 1.0 for loop in range(simulation_iterations): output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=True) + + # Now work out how many output clock counts this translates to measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6) # Add some jitter to the output_count to test jitter compensation jitter_amplitude = 100 # measured in output clock counts @@ -194,9 +154,17 @@ def run_lut_sw_pll_sim(): target_freq_log.append(target_output_frequency) freq_log.append(output_frequency) - real_time += 1 / nominal_control_rate_hz + time_inc = 1 / nominal_control_rate_hz + audio.apply_frequency_deviation(real_time, real_time + time_inc, output_frequency - target_output_frequency) + + real_time += time_inc + + plot_simulation(freq_log, target_freq_log, real_time_log) + waveform = audio.get_modulated_waveform() + audio.save_modulated_wav("modulated_tone_1000Hz.wav", waveform) + audio.plot_modulated_fft("modulated_fft.png", waveform) if __name__ == '__main__': """ @@ -222,3 +190,14 @@ def run_lut_sw_pll_sim(): # {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, # ] +# """ +# ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks +# xtal_frequency - The xcore clock frequency +# nominal_ref_frequency - The nominal input reference frequency +# target_output_frequency - The nominal target output frequency +# max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance +# min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance +# ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance +# fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance +# fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance +# """ From ee704f0b099007c39fad04eb597593375176d6ec Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 16:20:26 +0000 Subject: [PATCH 009/118] Fix audio tone modulation --- python/sw_pll/analysis_tools.py | 20 +++++++++----------- python/sw_pll/sw_pll_sim.py | 16 +++++++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index 1eeb42b8..ef539758 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -18,25 +18,23 @@ def apply_frequency_deviation(self, start_s, end_s, delta_freq): self.modulator[start_idx:end_idx] += delta_freq - def get_modulated_waveform(self): + def modulate_waveform(self): # Now create the frequency modulated waveform # this is designed to accumulate the phase so doesn't see discontinuities # https://dsp.stackexchange.com/questions/80768/fsk-modulation-with-python delta_phi = self.modulator * np.pi / (self.sample_rate / 2.0) phi = np.cumsum(delta_phi) - waveform = np.sin(phi) + self.waveform = np.sin(phi) - return waveform - - def save_modulated_wav(self, filename, waveform): - integer_output = np.int16(waveform * 32767) + def save_modulated_wav(self, filename): + integer_output = np.int16(self.waveform * 32767) soundfile.write(filename, integer_output, int(self.sample_rate)) - def plot_modulated_fft(self, filename, waveform): + def plot_modulated_fft(self, filename): xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.modulator.size//2) N = xf.size window = np.kaiser(N*2, 14) - waveform = waveform * window + waveform = self.waveform * window yf = np.fft.fft(waveform) fig, ax = plt.subplots() @@ -63,7 +61,7 @@ def plot_modulated_fft(self, filename, waveform): # audio.apply_frequency_deviation(4, 6, -500) - modulated_tone = audio.get_modulated_waveform() - audio.save_modulated_wav("modulated.wav", modulated_tone) - audio.plot_modulated_fft("modulated_fft.png", modulated_tone) + audio.modulate_waveform() + audio.save_modulated_wav("modulated.wav") + audio.plot_modulated_fft("modulated_fft.png") diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 5f37a7f0..217a5592 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -111,7 +111,7 @@ def plot_simulation(freq_log, target_freq_log, real_time_log): plt.legend(loc="upper right") plt.grid(True) # plt.show() - plt.savefig("pll_step_response.png", dpi=150) + plt.savefig("sw_pll_tracking.png", dpi=150) def run_lut_sw_pll_sim(): @@ -126,7 +126,8 @@ def run_lut_sw_pll_sim(): sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) output_clock_count = 0 - audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=1000) + test_tone_hz = 1000 + audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=test_tone_hz) freq_log = [] @@ -155,16 +156,17 @@ def run_lut_sw_pll_sim(): freq_log.append(output_frequency) time_inc = 1 / nominal_control_rate_hz - audio.apply_frequency_deviation(real_time, real_time + time_inc, output_frequency - target_output_frequency) + scaled_frequency_shift = test_tone_hz * (output_frequency - target_output_frequency) / target_output_frequency + audio.apply_frequency_deviation(real_time, real_time + time_inc, scaled_frequency_shift) real_time += time_inc - plot_simulation(freq_log, target_freq_log, real_time_log) - waveform = audio.get_modulated_waveform() - audio.save_modulated_wav("modulated_tone_1000Hz.wav", waveform) - audio.plot_modulated_fft("modulated_fft.png", waveform) + + audio.modulate_waveform() + audio.save_modulated_wav("modulated_tone_1000Hz.wav") + audio.plot_modulated_fft("modulated_fft.png") if __name__ == '__main__': """ From 13eb089eb48dd52990a8606797d4bdd7f9eceae2 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Nov 2023 17:49:00 +0000 Subject: [PATCH 010/118] Initial rough (incomplete) SDM sim --- python/sw_pll/dco_model.py | 50 +++++++++---- python/sw_pll/sw_pll_sim.py | 137 +++++++++++++++++++++++------------- 2 files changed, 124 insertions(+), 63 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 4e4eab8a..d3d248c8 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -210,8 +210,16 @@ def __init__(self): # generalized version without fixed point shifts. WIP!! - # takes a Q20 number from + # takes a Q20 number from 60000 to 980000 or 0.0572 to 0.934 def do_sigma_delta(self, ds_in): + if ds_in > 980000: + ds_in = 980000 + print("Pos clip") + + if ds_in < 60000: + ds_in = 60000 + print("Neg clip") + ds_out = int(self.ds_x3 * 0.002197265625) if ds_out > 8: ds_out = 8 @@ -229,23 +237,37 @@ class sigma_delta_dco(sdm): TBD """ def __init__(self): - # input_freq = - # F = - # R = - # f = - # p = - # OD - # ACD = - pass + # PLL solution from Joe's code 24.576MHz + input_freq =24000000 + F = int(102.4 - 1) + R = 1 - 1 + f = 2 - 1 + p = 5 - 1 + OD = 5 - 1 + ACD = 5 - 1 + + self.lock_status = -1 + + self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + sdm.__init__(self) + + def do_modulate(self, input): + ds_out = sdm.do_sigma_delta(self, input) + return self.app_pll.update_frac(ds_out, 7), self.lock_status if __name__ == '__main__': """ This module is not intended to be run directly. This is here for internal testing only. """ - dco = lut_dco() - print(f"LUT size: {dco.get_lut_size()}") - # print(f"LUT : {dco.get_lut()}") - dco.plot_freq_range() - dco.print_stats(12288000) + # dco = lut_dco() + # print(f"LUT size: {dco.get_lut_size()}") + # # print(f"LUT : {dco.get_lut()}") + # dco.plot_freq_range() + # dco.print_stats(12288000) + + sd_dco = sigma_delta_dco() + for i in range(30): + output_frequency = sd_dco.do_modulate(400000) + print(i, output_frequency) \ No newline at end of file diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 217a5592..054e70b6 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -2,7 +2,7 @@ # This Software is subject to the terms of the XMOS Public Licence: Version 1. from pfd_model import port_timer_pfd -from dco_model import lut_dco +from dco_model import lut_dco, sigma_delta_dco from controller_model import sw_pll_lut_pi_ctrl from analysis_tools import audio_modulator import matplotlib.pyplot as plt @@ -42,61 +42,50 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False return output_frequency, lock_status +class sim_sw_pll_sd: + def __init__( self, + target_output_frequency, + nominal_nominal_control_rate_frequency, + Kp, + Ki, + Kii=None): - -# audio = audio_modulator(simulation_iterations * ref_to_loop_call_rate / ref_frequency, sample_rate = ref_frequency, test_tone_hz = test_tone_hz) - -# for count in range(simulation_iterations): -# output_count_start_float = output_count_end_float -# output_count_float_inc = actual_output_frequency / ref_frequency * ref_to_loop_call_rate - -# # Add some jitter to the output_count to test jitter compensation -# output_sample_jitter = jitter_amplitude * (np.random.sample() - 0.5) -# output_count_end_float += output_count_float_inc + output_sample_jitter -# # Compensate for the jitter -# period_fraction = (output_count_float_inc + output_sample_jitter) / output_count_float_inc - -# # print(f"output_count_float_inc: {output_count_float_inc}, period_fraction: {period_fraction}, ratio: {output_count_float_inc / period_fraction}") - -# actual_output_frequency, lock_status = sw_pll.do_control(output_count_end_float, period_fraction = period_fraction) -# # lock_status = 0 + self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) + # self.controller = sw_pll_lut_pi_ctrl(Kp, Ki) + self.dco = sigma_delta_dco() -# # Sigma delta section -# micro_time_inc = output_count_float_inc / sigma_delta_loop_ratio -# for sd_count in range(sigma_delta_loop_ratio): -# # Helpers for the tone modulation -# actual_output_frequency, lock_status = sw_pll.do_deviation(0) + self.target_output_frequency = target_output_frequency + self.time = 0.0 + self.control_time_inc = 1 / nominal_nominal_control_rate_frequency -# time_in_s = lambda count: count * ref_to_loop_call_rate / ref_frequency -# freq_shift = lambda actual_output_frequency, target_output_frequency, test_tone_hz: (actual_output_frequency / target_output_frequency - 1) * test_tone_hz -# start_time = time_in_s(count + sd_count / sigma_delta_loop_ratio) -# end_time = time_in_s(count + (sd_count + 1) / sigma_delta_loop_ratio) -# audio.apply_frequency_deviation(start_time, end_time, freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) -# # print(freq_shift(actual_output_frequency, target_output_frequency, test_tone_hz)) + self.control_setting = 400000 -# freq_log.append(actual_output_frequency) -# target_log.append(ref_frequency * multiplier) + def do_control_loop(self): + pass -# if verbose: -# print(f"Loop: count: {count}, time: {real_time}, actual_output_frequency: {actual_output_frequency}, lock_status: {sw_pll_ctrl.lock_status_lookup[lock_status]}") - + def do_sigma_delta_loop(self): + frequency, lock_status = self.dco.do_modulate(self.control_setting) + return frequency, lock_status -# real_time += ref_to_loop_call_rate / ref_frequency + # def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False): + # """ + # This should be called once every control period nominally + # """ -# # A number of events where the input reference is stepped -# ppm_adjust = lambda f, ppm: f * (1 + (ppm / 1000000)) -# for ppm_shift in ppm_shifts: -# (change_at_count, ppm) = ppm_shift -# if count == change_at_count: -# ref_frequency = ppm_adjust(nominal_ref_frequency, ppm) + # error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) + # dco_ctl = self.controller.do_control_from_error(error, first_loop=first_loop) + # output_frequency, lock_status = self.dco.get_frequency_from_error(dco_ctl) + # if verbose: + # print(f"Raw error: {error}") + # print(f"dco_ctl: {dco_ctl}") + # print(f"Output_frequency: {output_frequency}") + # print(f"Lock status: {self.dco.lock_status_lookup[lock_status]}") -# # Generate fft of modulated test tone -# audio.plot_modulated_fft(f"modulated_tone_fft_{test_tone_hz}Hz.png", audio.get_modulated_waveform()) -# audio.save_modulated_wav(f"modulated_tone_{test_tone_hz}Hz.wav", audio.get_modulated_waveform()) + # return output_frequency, lock_status @@ -127,7 +116,7 @@ def run_lut_sw_pll_sim(): output_clock_count = 0 test_tone_hz = 1000 - audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=test_tone_hz) + audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=3072000, test_tone_hz=test_tone_hz) freq_log = [] @@ -165,14 +154,64 @@ def run_lut_sw_pll_sim(): plot_simulation(freq_log, target_freq_log, real_time_log) audio.modulate_waveform() + audio.save_modulated_wav("modulated_tone_1000Hz_lut.wav") + audio.plot_modulated_fft("modulated_fft_lut.png") + +def run_sd_sw_pll_sim(): + nominal_output_hz = 24576000 + nominal_control_rate_hz = 93.75 + nominal_sd_rate_hz = 1000000 + output_frequency = nominal_output_hz + + simulation_iterations = 100000 + Kp = 0.0 + Ki = 0.1 + Kii = 0.0 + + sw_pll = sim_sw_pll_sd(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) + output_clock_count = 0 + + test_tone_hz = 1000 + print("audio 1") + audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=3072000, test_tone_hz=test_tone_hz) + print("audio 2") + + + freq_log = [] + target_freq_log = [] + real_time_log = [] + real_time = 0.0 + period_fraction = 1.0 + + # ppm_shift = -5 + + for loop in range(simulation_iterations): + + output_frequency, lock_status = sw_pll.do_sigma_delta_loop() + + + freq_log.append(output_frequency) + target_freq_log.append(nominal_output_hz) + real_time_log.append(real_time) + + time_inc = 1 / nominal_sd_rate_hz + real_time += time_inc + + + plot_simulation(freq_log, target_freq_log, real_time_log) + + print("audio 3") + audio.modulate_waveform() + print("audio 4") audio.save_modulated_wav("modulated_tone_1000Hz.wav") - audio.plot_modulated_fft("modulated_fft.png") + print("audio 5") + audio.plot_modulated_fft("modulated_fft_sd.png") + print("audio 6") + if __name__ == '__main__': - """ - This module is not intended to be run directly. This is here for internal testing only. - """ run_lut_sw_pll_sim() + run_sd_sw_pll_sim() From a124e0fc912afc51374697feec750f99253809ad Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Nov 2023 12:11:59 +0000 Subject: [PATCH 011/118] Very basic full sdm sim --- python/sw_pll/analysis_tools.py | 33 ++++-- python/sw_pll/controller_model.py | 116 +++++++++++++------- python/sw_pll/dco_model.py | 18 ++-- python/sw_pll/pfd_model.py | 2 +- python/sw_pll/sw_pll_sim.py | 171 ++++++++++++++++-------------- 5 files changed, 207 insertions(+), 133 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index ef539758..a2905a07 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -47,21 +47,34 @@ def plot_modulated_fft(self, filename): # Plot the whole frequncy range from DC to nyquist ax.plot(xf[:N], yf[:N], marker='.') ax.set_xscale("log") + plt.xlim((10**1, 10**5)) + plt.ylim((-200, 0)) plt.savefig(filename, dpi=150) + def load_wav(self, filename): + """ + Used for testing only - load a wav into self.waveform + """ + self.waveform, self.sample_rate = soundfile.read(filename) + + if __name__ == '__main__': """ This module is not intended to be run directly. This is here for internal testing only. """ - test_len = 10 - audio = audio_modulator(test_len) - for time_s in range(test_len): - modulation_hz = 10 * (time_s - (test_len) / 2) - audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz) - - # audio.apply_frequency_deviation(4, 6, -500) + if False: + test_len = 10 + audio = audio_modulator(test_len) + for time_s in range(test_len): + modulation_hz = 10 * (time_s - (test_len) / 2) + audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz) - audio.modulate_waveform() - audio.save_modulated_wav("modulated.wav") - audio.plot_modulated_fft("modulated_fft.png") + audio.modulate_waveform() + audio.save_modulated_wav("modulated.wav") + audio.plot_modulated_fft("modulated_fft.png") + + else: + audio = audio_modulator(1) + audio.load_wav("modulated_tone_1000Hz_sd_ds.wav") + audio.plot_modulated_fft("modulated_tone_1000Hz_sd_ds.png") diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 9a6c425c..e9e25d8b 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -4,7 +4,59 @@ import dco_model import numpy as np -class sw_pll_lut_pi_ctrl(dco_model.lut_dco): + +class pi_ctrl(): + """ + Parent PI(I) controller class + """ + def __init__(self, Kp, Ki, Kii=None, i_windup_limit=None, ii_windup_limit=None, verbose=False): + self.Kp = Kp + self.Ki = Ki + self.Kii = 0.0 if Kii is None else Kii + self.i_windup_limit = i_windup_limit + self.ii_windup_limit = ii_windup_limit + + self.error_accum = 0.0 # Integral of error + self.error_accum_accum = 0.0 # Double integral of error (optional) + self.total_error = 0.0 # Calculated total error + + self.verbose = verbose + + if verbose: + print(f"Init sw_pll_lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") + + def _reset_controller(self): + self.error_accum = 0.0 + self.error_accum_accum = 0.0 + + def do_control_from_error(self, error): + """ + Calculate the LUT setting from the input error + """ + + # clamp integral terms to stop them irrecoverably drifting off. + if self.i_windup_limit is None: + self.error_accum = self.error_accum + error + else: + + self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) + if self.ii_windup_limit is None: + self.error_accum_accum = self.error_accum_accum + self.error_accum + else: + self.error_accum_accum = np.clip(self.error_accum_accum + self.error_accum, -self.ii_windup_limit, self.ii_windup_limit) + + error_p = self.Kp * error; + error_i = self.Ki * self.error_accum + error_ii = self.Kii * self.error_accum_accum + + self.total_error = error_p + error_i + error_ii + + if self.verbose: + print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.total_error}") + + return self.total_error + +class lut_pi_ctrl(pi_ctrl, dco_model.lut_dco): """ This class instantiates a control loop instance. It takes a lookup table function which can be generated from the error_from_h class which allows it use the actual pre-calculated transfer function. @@ -14,43 +66,29 @@ class sw_pll_lut_pi_ctrl(dco_model.lut_dco): desired response. The function run_sim allows for a plot of a step resopnse input which allows this to be done visually. """ - def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): self.dco = dco_model.lut_dco() self.lut_lookup_function = self.dco.get_lut() lut_size = self.dco.get_lut_size() + self.diff = 0.0 # Most recent diff between expected and actual. Used by tests + # By default set the nominal LUT index to half way if base_lut_index is None: base_lut_index = lut_size // 2 self.base_lut_index = base_lut_index - self.Kp = Kp - self.Ki = Ki - self.Kii = 0.0 if Kii is None else Kii - - self.diff = 0.0 # Most recent diff between expected and actual - self.error_accum = 0.0 # Integral of error - self.error_accum_accum = 0.0 # Double integral of error - self.total_error = 0.0 # Calculated total error - # Set windup limit to the lut_size, which by default is double of the deflection from nominal - self.i_windup_limit = lut_size / self.Ki if self.Ki != 0.0 else 0.0 - self.ii_windup_limit = lut_size / self.Kii if self.Kii != 0.0 else 0.0 + i_windup_limit = lut_size / Ki if Ki != 0.0 else 0.0 + ii_windup_limit = 0.0 if Kii is None else lut_size / Kii if Kii != 0.0 else 0.0 + + pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, i_windup_limit=i_windup_limit, ii_windup_limit=ii_windup_limit, verbose=verbose) self.verbose = verbose if verbose: - print(f"Init sw_pll_lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") - - def get_dco_ctrl(self): - return self.error - - - def _reset_controller(self): - self.error_accum = 0.0 - self.error_accum_accum = 0.0 + print(f"Init lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") def do_control_from_error(self, error, first_loop=False): """ @@ -58,27 +96,33 @@ def do_control_from_error(self, error, first_loop=False): """ self.diff = error # Used by tests - # clamp integral terms to stop them irrecoverably drifting off. - self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) - self.error_accum_accum = np.clip(self.error_accum_accum + self.error_accum, -self.ii_windup_limit, self.ii_windup_limit) - + if first_loop: - self._reset_controller() + pi_ctrl._reset_controller() - error_p = self.Kp * error; - error_i = self.Ki * self.error_accum - error_ii = self.Kii * self.error_accum_accum + dco_ctrl = self.base_lut_index - pi_ctrl.do_control_from_error(self, error) - self.total_error = error_p + error_i + error_ii + return None if first_loop else dco_ctrl - if self.verbose: - print(f"error: {error} error_p: {error_p} error_i: {error_i} error_ii: {error_ii} total error: {self.total_error}") +class sdm_pi_ctrl(pi_ctrl, dco_model.sigma_delta_dco): + def __init__(self, Kp, Ki, Kii=None, verbose=False): + pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=False) - dco_ctrl = self.base_lut_index - self.total_error + print("init pi_ctrl", Kp, Ki, Kii, dir(pi_ctrl)) - return None if first_loop else dco_ctrl + self.iir_y = 0 + self.initial_setting = 478151 + + def do_control_from_error(self, error): + x = pi_ctrl.do_control_from_error(self, -error) + print(f"error: {error}, x: {x}") + # Filter some noise into DCO to reduce jitter + # First order IIR, make A=0.125 + # y = y + A(x-y) + self.iir_y = self.iir_y + (x - self.iir_y) * 0.125 + return self.initial_setting + self.iir_y if __name__ == '__main__': """ @@ -87,7 +131,7 @@ def do_control_from_error(self, error, first_loop=False): Kp = 1.0 Ki = 0.1 - sw_pll = sw_pll_lut_pi_ctrl(Kp, Ki, verbose=True) + sw_pll = lut_pi_ctrl(Kp, Ki, verbose=True) for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index d3d248c8..a9320bc9 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -208,17 +208,19 @@ def __init__(self): self.ds_x2 = 0 self.ds_x3 = 0 + self.ds_in_max = 980000 + self.ds_in_min = 60000 # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 or 0.0572 to 0.934 def do_sigma_delta(self, ds_in): - if ds_in > 980000: - ds_in = 980000 - print("Pos clip") + if ds_in > self.ds_in_max: + print(f"SDM Pos clip: {ds_in}, {self.ds_in_max}") + ds_in = self. ds_in_max - if ds_in < 60000: - ds_in = 60000 - print("Neg clip") + if ds_in < self.ds_in_min: + print(f"SDM Neg clip: {ds_in}, {self.ds_in_min}") + ds_in = self.ds_in_min ds_out = int(self.ds_x3 * 0.002197265625) if ds_out > 8: @@ -267,7 +269,7 @@ def do_modulate(self, input): # dco.plot_freq_range() # dco.print_stats(12288000) - sd_dco = sigma_delta_dco() + sdm_dco = sigma_delta_dco() for i in range(30): - output_frequency = sd_dco.do_modulate(400000) + output_frequency = sdm_dco.do_modulate(400000) print(i, output_frequency) \ No newline at end of file diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index 533a7013..e5da8157 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -32,7 +32,7 @@ def get_error(self, output_clock_count_float, period_fraction=1.0): # Apply out of range detection so that the controller ignores startup or missed control loops (as per C) if abs(error) > (self.ppm_range / 1e6) * self.expected_output_clock_count_inc: - print("FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) + print("PFD FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) self.first_loop = True else: self.first_loop = False diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 054e70b6..eeb37681 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -3,11 +3,29 @@ from pfd_model import port_timer_pfd from dco_model import lut_dco, sigma_delta_dco -from controller_model import sw_pll_lut_pi_ctrl +from controller_model import lut_pi_ctrl, sdm_pi_ctrl from analysis_tools import audio_modulator import matplotlib.pyplot as plt import numpy as np + +def plot_simulation(freq_log, target_freq_log, real_time_log): + plt.clf() + plt.plot(real_time_log, freq_log, color='red', marker='.', label='actual frequency') + plt.plot(real_time_log, target_freq_log, color='blue', marker='.', label='target frequency') + plt.title('PLL tracking', fontsize=14) + plt.xlabel(f'Time in seconds', fontsize=10) + plt.ylabel('Frequency', fontsize=10) + plt.legend(loc="upper right") + plt.grid(True) + # plt.show() + plt.savefig("sw_pll_tracking.png", dpi=150) + + +############################## +# LOOK UP TABLE IMPLEMENTATION +############################## + class sim_sw_pll_lut: def __init__( self, target_output_frequency, @@ -42,66 +60,6 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False return output_frequency, lock_status -class sim_sw_pll_sd: - def __init__( self, - target_output_frequency, - nominal_nominal_control_rate_frequency, - Kp, - Ki, - Kii=None): - - self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) - # self.controller = sw_pll_lut_pi_ctrl(Kp, Ki) - self.dco = sigma_delta_dco() - - self.target_output_frequency = target_output_frequency - self.time = 0.0 - self.control_time_inc = 1 / nominal_nominal_control_rate_frequency - - self.control_setting = 400000 - - def do_control_loop(self): - pass - - def do_sigma_delta_loop(self): - - frequency, lock_status = self.dco.do_modulate(self.control_setting) - - return frequency, lock_status - - - # def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False): - # """ - # This should be called once every control period nominally - # """ - - # error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) - # dco_ctl = self.controller.do_control_from_error(error, first_loop=first_loop) - # output_frequency, lock_status = self.dco.get_frequency_from_error(dco_ctl) - - # if verbose: - # print(f"Raw error: {error}") - # print(f"dco_ctl: {dco_ctl}") - # print(f"Output_frequency: {output_frequency}") - # print(f"Lock status: {self.dco.lock_status_lookup[lock_status]}") - - # return output_frequency, lock_status - - - - -def plot_simulation(freq_log, target_freq_log, real_time_log): - plt.clf() - plt.plot(real_time_log, freq_log, color='red', marker='.', label='actual frequency') - plt.plot(real_time_log, target_freq_log, color='blue', marker='.', label='target frequency') - plt.title('PLL tracking', fontsize=14) - plt.xlabel(f'Time in seconds', fontsize=10) - plt.ylabel('Frequency', fontsize=10) - plt.legend(loc="upper right") - plt.grid(True) - # plt.show() - plt.savefig("sw_pll_tracking.png", dpi=150) - def run_lut_sw_pll_sim(): nominal_output_hz = 12288000 @@ -116,7 +74,7 @@ def run_lut_sw_pll_sim(): output_clock_count = 0 test_tone_hz = 1000 - audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=3072000, test_tone_hz=test_tone_hz) + audio = audio_modulator(simulation_iterations * 1 / nominal_control_rate_hz, sample_rate=48000, test_tone_hz=test_tone_hz) freq_log = [] @@ -124,7 +82,6 @@ def run_lut_sw_pll_sim(): real_time_log = [] real_time = 0.0 period_fraction = 1.0 - ppm_shift = -5 for loop in range(simulation_iterations): @@ -157,23 +114,63 @@ def run_lut_sw_pll_sim(): audio.save_modulated_wav("modulated_tone_1000Hz_lut.wav") audio.plot_modulated_fft("modulated_fft_lut.png") + + +###################################### +# SIGMA DELTA MODULATOR IMPLEMENTATION +###################################### + +class sim_sw_pll_sd: + def __init__( self, + target_output_frequency, + nominal_nominal_control_rate_frequency, + Kp, + Ki, + Kii=None): + + self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) + self.controller = sdm_pi_ctrl(Kp, Ki, Kii) + self.dco = sigma_delta_dco() + + self.target_output_frequency = target_output_frequency + self.time = 0.0 + self.control_time_inc = 1 / nominal_nominal_control_rate_frequency + + self.control_setting = (self.dco.ds_in_max + self.dco.ds_in_min) / 2 # Mid way + + + def do_control_loop(self, output_clock_count): + + error, first_loop = self.pfd.get_error(output_clock_count) + ctrl_output = self.controller.do_control_from_error(error) + print(f"Err: {error} ctrl: {ctrl_output}") + self.control_setting = ctrl_output + + return self.control_setting + + def do_sigma_delta(self): + frequncy, lock_status = self.dco.do_modulate(self.control_setting) + + return frequncy, lock_status + + def run_sd_sw_pll_sim(): nominal_output_hz = 24576000 - nominal_control_rate_hz = 93.75 + nominal_control_rate_hz = 100 nominal_sd_rate_hz = 1000000 output_frequency = nominal_output_hz - simulation_iterations = 100000 + simulation_iterations = 1000000 Kp = 0.0 - Ki = 0.1 - Kii = 0.0 + Ki = 32.0 + Kii = 0.25 sw_pll = sim_sw_pll_sd(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) output_clock_count = 0 test_tone_hz = 1000 print("audio 1") - audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=3072000, test_tone_hz=test_tone_hz) + audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=6144000, test_tone_hz=test_tone_hz) print("audio 2") @@ -181,36 +178,54 @@ def run_sd_sw_pll_sim(): target_freq_log = [] real_time_log = [] real_time = 0.0 - period_fraction = 1.0 - # ppm_shift = -5 + control_time_inc = 1 / nominal_control_rate_hz + control_time_trigger = control_time_inc for loop in range(simulation_iterations): - output_frequency, lock_status = sw_pll.do_sigma_delta_loop() - + output_frequency, lock_status = sw_pll.do_sigma_delta() + # Log results freq_log.append(output_frequency) - target_freq_log.append(nominal_output_hz) + target_output_frequency = nominal_output_hz + target_freq_log.append(target_output_frequency) real_time_log.append(real_time) + # print(output_frequency, nominal_output_hz, real_time) - time_inc = 1 / nominal_sd_rate_hz - real_time += time_inc + + # Modulate tone + sdm_time_inc = 1 / nominal_sd_rate_hz + scaled_frequency_shift = test_tone_hz * (output_frequency - target_output_frequency) / target_output_frequency + audio.apply_frequency_deviation(real_time, real_time + sdm_time_inc, scaled_frequency_shift) + + # Accumulate the real number of output clocks + output_clock_count += output_frequency / nominal_sd_rate_hz + + # Check for control loop run ready + if real_time > control_time_trigger: + control_time_trigger += control_time_inc + + # Now work out how many output clock counts this translates to + sw_pll.do_control_loop(output_clock_count) + # print("SDM CONTROL", output_clock_count, real_time) + + real_time += sdm_time_inc plot_simulation(freq_log, target_freq_log, real_time_log) - + print("audio 3") audio.modulate_waveform() print("audio 4") - audio.save_modulated_wav("modulated_tone_1000Hz.wav") + audio.save_modulated_wav("modulated_tone_1000Hz_sd.wav") print("audio 5") audio.plot_modulated_fft("modulated_fft_sd.png") print("audio 6") if __name__ == '__main__': - run_lut_sw_pll_sim() + # run_lut_sw_pll_sim() run_sd_sw_pll_sim() From a7eaca162a5d071a90f1c27feac74cd4849f1624 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Nov 2023 14:42:26 +0000 Subject: [PATCH 012/118] Tidy up --- python/sw_pll/analysis_tools.py | 9 ++++-- python/sw_pll/controller_model.py | 14 ++++---- python/sw_pll/dco_model.py | 22 +++++++++---- python/sw_pll/pfd_model.py | 3 +- python/sw_pll/sw_pll_sim.py | 54 +++++++++++++++---------------- 5 files changed, 57 insertions(+), 45 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index a2905a07..a07b77a7 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -30,11 +30,14 @@ def save_modulated_wav(self, filename): integer_output = np.int16(self.waveform * 32767) soundfile.write(filename, integer_output, int(self.sample_rate)) - def plot_modulated_fft(self, filename): - xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), self.modulator.size//2) + def plot_modulated_fft(self, filename, skip_s=None): + start_x = 0 if skip_s is None else int(skip_s * self.sample_rate) // 2 * 2 + waveform = self.waveform[start_x:] + + xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), waveform.size // 2) N = xf.size window = np.kaiser(N*2, 14) - waveform = self.waveform * window + waveform = waveform * window yf = np.fft.fft(waveform) fig, ax = plt.subplots() diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index e9e25d8b..b154fbcf 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -38,8 +38,8 @@ def do_control_from_error(self, error): if self.i_windup_limit is None: self.error_accum = self.error_accum + error else: - self.error_accum = np.clip(self.error_accum + error, -self.i_windup_limit, self.i_windup_limit) + if self.ii_windup_limit is None: self.error_accum_accum = self.error_accum_accum + self.error_accum else: @@ -98,7 +98,7 @@ def do_control_from_error(self, error, first_loop=False): if first_loop: - pi_ctrl._reset_controller() + pi_ctrl._reset_controller(self) dco_ctrl = self.base_lut_index - pi_ctrl.do_control_from_error(self, error) @@ -106,21 +106,23 @@ def do_control_from_error(self, error, first_loop=False): class sdm_pi_ctrl(pi_ctrl, dco_model.sigma_delta_dco): def __init__(self, Kp, Ki, Kii=None, verbose=False): - pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=False) - print("init pi_ctrl", Kp, Ki, Kii, dir(pi_ctrl)) + pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=verbose) + # Low pass filter state + self.alpha = 0.125 self.iir_y = 0 + + # Nominal setting for SDM self.initial_setting = 478151 def do_control_from_error(self, error): x = pi_ctrl.do_control_from_error(self, -error) - print(f"error: {error}, x: {x}") # Filter some noise into DCO to reduce jitter # First order IIR, make A=0.125 # y = y + A(x-y) - self.iir_y = self.iir_y + (x - self.iir_y) * 0.125 + self.iir_y = self.iir_y + (x - self.iir_y) * self.alpha return self.initial_setting + self.iir_y diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index a9320bc9..7513c2c2 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -21,6 +21,8 @@ """ +lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} + ############################## # LOOK UP TABLE IMPLEMENTATION ############################## @@ -30,7 +32,6 @@ class lut_dco: This class parses a pre-generated fractions.h file and builds a lookup table so that the values can be used by the sw_pll simulation. It may be used directly but is generally used a sub class of error_to_pll_output_frequency. """ - lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} def __init__(self, header_file = "fractions.h", verbose=False): # fixed header_file name by pll_calc.py """ @@ -211,27 +212,36 @@ def __init__(self): self.ds_in_max = 980000 self.ds_in_min = 60000 + self.lock_status = -1 + # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 or 0.0572 to 0.934 def do_sigma_delta(self, ds_in): if ds_in > self.ds_in_max: print(f"SDM Pos clip: {ds_in}, {self.ds_in_max}") ds_in = self. ds_in_max + self.lock_status = 1 - if ds_in < self.ds_in_min: + elif ds_in < self.ds_in_min: print(f"SDM Neg clip: {ds_in}, {self.ds_in_min}") ds_in = self.ds_in_min + self.lock_status = -1 + + else: + self.lock_status = 0 ds_out = int(self.ds_x3 * 0.002197265625) + if ds_out > 8: ds_out = 8 if ds_out < 0: ds_out = 0 + self.ds_x3 += int((self.ds_x2 * 0.03125) - (ds_out * 768)) self.ds_x2 += int((self.ds_x1 * 0.03125) - (ds_out * 16384)) self.ds_x1 += int(ds_in - (ds_out * 131072)) - return ds_out + return ds_out, self.lock_status class sigma_delta_dco(sdm): @@ -248,15 +258,13 @@ def __init__(self): OD = 5 - 1 ACD = 5 - 1 - self.lock_status = -1 - self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) sdm.__init__(self) def do_modulate(self, input): - ds_out = sdm.do_sigma_delta(self, input) + ds_out, lock_status = sdm.do_sigma_delta(self, input) - return self.app_pll.update_frac(ds_out, 7), self.lock_status + return self.app_pll.update_frac(ds_out, 7), lock_status if __name__ == '__main__': diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index e5da8157..714193d3 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -28,11 +28,10 @@ def get_error(self, output_clock_count_float, period_fraction=1.0): expected_output_clock_count = self.output_count_last + self.expected_output_clock_count_inc error = output_count_inc - int(self.expected_output_clock_count_inc) - print(output_count_inc, self.expected_output_clock_count_inc) # Apply out of range detection so that the controller ignores startup or missed control loops (as per C) if abs(error) > (self.ppm_range / 1e6) * self.expected_output_clock_count_inc: - print("PFD FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) + # print("PFD FIRST LOOP", abs(error), (self.ppm_range / 10e6) * self.expected_output_clock_count_inc) self.first_loop = True else: self.first_loop = False diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index eeb37681..af756a0d 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -2,14 +2,14 @@ # This Software is subject to the terms of the XMOS Public Licence: Version 1. from pfd_model import port_timer_pfd -from dco_model import lut_dco, sigma_delta_dco +from dco_model import lut_dco, sigma_delta_dco, lock_status_lookup from controller_model import lut_pi_ctrl, sdm_pi_ctrl from analysis_tools import audio_modulator import matplotlib.pyplot as plt import numpy as np -def plot_simulation(freq_log, target_freq_log, real_time_log): +def plot_simulation(freq_log, target_freq_log, real_time_log, name="sw_pll_tracking.png"): plt.clf() plt.plot(real_time_log, freq_log, color='red', marker='.', label='actual frequency') plt.plot(real_time_log, target_freq_log, color='blue', marker='.', label='target frequency') @@ -19,7 +19,7 @@ def plot_simulation(freq_log, target_freq_log, real_time_log): plt.legend(loc="upper right") plt.grid(True) # plt.show() - plt.savefig("sw_pll_tracking.png", dpi=150) + plt.savefig(name, dpi=150) ############################## @@ -35,7 +35,7 @@ def __init__( self, Kii=None): self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) - self.controller = sw_pll_lut_pi_ctrl(Kp, Ki) + self.controller = lut_pi_ctrl(Kp, Ki) self.dco = lut_dco() self.target_output_frequency = target_output_frequency @@ -55,7 +55,7 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False print(f"Raw error: {error}") print(f"dco_ctl: {dco_ctl}") print(f"Output_frequency: {output_frequency}") - print(f"Lock status: {self.dco.lock_status_lookup[lock_status]}") + print(f"Lock status: {lock_status_lookup[lock_status]}") return output_frequency, lock_status @@ -82,13 +82,15 @@ def run_lut_sw_pll_sim(): real_time_log = [] real_time = 0.0 period_fraction = 1.0 + ppm_shift = -5 for loop in range(simulation_iterations): - output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=True) + output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=False) # Now work out how many output clock counts this translates to measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6) + # Add some jitter to the output_count to test jitter compensation jitter_amplitude = 100 # measured in output clock counts clock_count_sampling_jitter = jitter_amplitude * (np.random.sample() - 0.5) @@ -108,11 +110,11 @@ def run_lut_sw_pll_sim(): real_time += time_inc - plot_simulation(freq_log, target_freq_log, real_time_log) + plot_simulation(freq_log, target_freq_log, real_time_log, "tracking_lut.png") audio.modulate_waveform() audio.save_modulated_wav("modulated_tone_1000Hz_lut.wav") - audio.plot_modulated_fft("modulated_fft_lut.png") + audio.plot_modulated_fft("modulated_fft_lut.png", skip_s=real_time / 2) # skip so we ignore the inital lock period @@ -139,13 +141,17 @@ def __init__( self, self.control_setting = (self.dco.ds_in_max + self.dco.ds_in_min) / 2 # Mid way - def do_control_loop(self, output_clock_count): + def do_control_loop(self, output_clock_count, verbose=False): error, first_loop = self.pfd.get_error(output_clock_count) ctrl_output = self.controller.do_control_from_error(error) - print(f"Err: {error} ctrl: {ctrl_output}") self.control_setting = ctrl_output + if verbose: + print(f"Raw error: {error}") + print(f"ctrl_output: {ctrl_output}") + print(f"Lock status: {lock_status_lookup[lock_status]}") + return self.control_setting def do_sigma_delta(self): @@ -157,10 +163,10 @@ def do_sigma_delta(self): def run_sd_sw_pll_sim(): nominal_output_hz = 24576000 nominal_control_rate_hz = 100 - nominal_sd_rate_hz = 1000000 + nominal_sd_rate_hz = 1e6 output_frequency = nominal_output_hz - simulation_iterations = 1000000 + simulation_iterations = 2000000 Kp = 0.0 Ki = 32.0 Kii = 0.25 @@ -169,9 +175,7 @@ def run_sd_sw_pll_sim(): output_clock_count = 0 test_tone_hz = 1000 - print("audio 1") audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=6144000, test_tone_hz=test_tone_hz) - print("audio 2") freq_log = [] @@ -179,6 +183,9 @@ def run_sd_sw_pll_sim(): real_time_log = [] real_time = 0.0 + ppm_shift = +0 + + # For working out when to do control calls control_time_inc = 1 / nominal_control_rate_hz control_time_trigger = control_time_inc @@ -188,11 +195,9 @@ def run_sd_sw_pll_sim(): # Log results freq_log.append(output_frequency) - target_output_frequency = nominal_output_hz + target_output_frequency = nominal_output_hz * (1 + ppm_shift / 1e6) target_freq_log.append(target_output_frequency) real_time_log.append(real_time) - # print(output_frequency, nominal_output_hz, real_time) - # Modulate tone sdm_time_inc = 1 / nominal_sd_rate_hz @@ -200,7 +205,7 @@ def run_sd_sw_pll_sim(): audio.apply_frequency_deviation(real_time, real_time + sdm_time_inc, scaled_frequency_shift) # Accumulate the real number of output clocks - output_clock_count += output_frequency / nominal_sd_rate_hz + output_clock_count += output_frequency / nominal_sd_rate_hz * (1 - ppm_shift / 1e6) # Check for control loop run ready if real_time > control_time_trigger: @@ -208,24 +213,19 @@ def run_sd_sw_pll_sim(): # Now work out how many output clock counts this translates to sw_pll.do_control_loop(output_clock_count) - # print("SDM CONTROL", output_clock_count, real_time) real_time += sdm_time_inc - plot_simulation(freq_log, target_freq_log, real_time_log) + plot_simulation(freq_log, target_freq_log, real_time_log, "tracking_sdm.png") - print("audio 3") audio.modulate_waveform() - print("audio 4") - audio.save_modulated_wav("modulated_tone_1000Hz_sd.wav") - print("audio 5") - audio.plot_modulated_fft("modulated_fft_sd.png") - print("audio 6") + audio.save_modulated_wav("modulated_tone_1000Hz_sdm.wav") + audio.plot_modulated_fft("modulated_fft_sdm.png", skip_s=real_time/2) # skip so we ignore the inital lock period if __name__ == '__main__': - # run_lut_sw_pll_sim() + run_lut_sw_pll_sim() run_sd_sw_pll_sim() From 697fa86a5822d9864c73ed0bb59ee74325ab3808 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Nov 2023 17:04:56 +0000 Subject: [PATCH 013/118] Ensure sw_pll python imports from same dir + update test_lib_sw_pll.py --- python/sw_pll/app_pll_model.py | 4 +++- python/sw_pll/controller_model.py | 8 +++---- python/sw_pll/dco_model.py | 8 +++---- python/sw_pll/pfd_model.py | 2 -- python/sw_pll/sw_pll_sim.py | 8 +++---- tests/test_lib_sw_pll.py | 38 ++++++++++++++----------------- 6 files changed, 32 insertions(+), 36 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 6b443ec6..9faa5a39 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -179,7 +179,9 @@ class pll_solution: def __init__(self, *args, **kwargs): try: self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs) - self.lut = parse_lut_h_file("fractions.h") + from .dco_model import lut_dco + dco = lut_dco("fractions.h") + self.lut, min_frac, max_frac = dco._read_lut_header("fractions.h") finally: Path("fractions.h").unlink(missing_ok=True) Path("register_setup.h").unlink(missing_ok=True) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index b154fbcf..09cb8225 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -1,7 +1,7 @@ -# Copyright 2022-2023 XMOS LIMITED. +# Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -import dco_model +from .dco_model import lut_dco, sigma_delta_dco import numpy as np @@ -56,7 +56,7 @@ def do_control_from_error(self, error): return self.total_error -class lut_pi_ctrl(pi_ctrl, dco_model.lut_dco): +class lut_pi_ctrl(pi_ctrl, lut_dco): """ This class instantiates a control loop instance. It takes a lookup table function which can be generated from the error_from_h class which allows it use the actual pre-calculated transfer function. @@ -104,7 +104,7 @@ def do_control_from_error(self, error, first_loop=False): return None if first_loop else dco_ctrl -class sdm_pi_ctrl(pi_ctrl, dco_model.sigma_delta_dco): +class sdm_pi_ctrl(pi_ctrl, sigma_delta_dco): def __init__(self, Kp, Ki, Kii=None, verbose=False): pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=verbose) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 7513c2c2..1ee84916 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -1,7 +1,7 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -import app_pll_model +from .app_pll_model import register_file, app_pll_frac_calc import matplotlib.pyplot as plt import numpy as np import os @@ -40,8 +40,8 @@ def __init__(self, header_file = "fractions.h", verbose=False): # fixed header """ self.lut, self.min_frac, self.max_frac = self._read_lut_header(header_file) - input_freq, F, R, f, p, OD, ACD = self._parse_register_file(app_pll_model.register_file) - self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + input_freq, F, R, f, p, OD, ACD = self._parse_register_file(register_file) + self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2]) self.lock_status = -1 @@ -258,7 +258,7 @@ def __init__(self): OD = 5 - 1 ACD = 5 - 1 - self.app_pll = app_pll_model.app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) sdm.__init__(self) def do_modulate(self, input): diff --git a/python/sw_pll/pfd_model.py b/python/sw_pll/pfd_model.py index 714193d3..6ddddee3 100644 --- a/python/sw_pll/pfd_model.py +++ b/python/sw_pll/pfd_model.py @@ -1,8 +1,6 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -import controller_model - class port_timer_pfd(): def __init__(self, nominal_output_hz, nominal_control_rate_hz, ppm_range=1000): self.output_count_last = 0.0 # Integer value of last output_clock_count diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index af756a0d..e82916fa 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -1,10 +1,10 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -from pfd_model import port_timer_pfd -from dco_model import lut_dco, sigma_delta_dco, lock_status_lookup -from controller_model import lut_pi_ctrl, sdm_pi_ctrl -from analysis_tools import audio_modulator +from .pfd_model import port_timer_pfd +from .dco_model import lut_dco, sigma_delta_dco, lock_status_lookup +from .controller_model import lut_pi_ctrl, sdm_pi_ctrl +from .analysis_tools import audio_modulator import matplotlib.pyplot as plt import numpy as np diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 2fe002d4..027f2694 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -14,13 +14,9 @@ import numpy as np import copy +from sw_pll.app_pll_model import pll_solution, app_pll_frac_calc + from typing import Any -from sw_pll.sw_pll_sim import ( - pll_solution, - app_pll_frac_calc, - sw_pll_ctrl, - get_frequency_from_error, -) from dataclasses import dataclass, asdict from subprocess import Popen, PIPE from itertools import product @@ -53,11 +49,11 @@ def __init__(self, args: DutArgs, pll): self.pll = pll self.args = DutArgs(**asdict(args)) # copies the values self.lut = self.args.lut - self.args.lut = len(self.lut.get_lut()) + self.args.lut = len(self.lut) self.ctrl = sw_pll_ctrl( args.target_output_frequency, self.lut_func, - len(self.lut.get_lut()), + len(self.lut), args.loop_rate_count, args.pll_ratio, args.kp, @@ -67,7 +63,7 @@ def __init__(self, args: DutArgs, pll): def lut_func(self, error): """Sim requires a function to provide access to the LUT. This is that""" - return get_frequency_from_error(error, self.lut.get_lut(), self.pll) + return get_frequency_from_error(error, self.lut, self.pll) def __enter__(self): """support context manager""" @@ -102,8 +98,8 @@ def __init__(self, args: DutArgs, pll, xe_file=DUT_XE): self.args = DutArgs(**asdict(args)) # copies the values self.args.kp = self.args.kp self.args.ki = self.args.ki - lut = self.args.lut.get_lut() - self.args.lut = len(args.lut.get_lut()) + lut = self.args.lut + self.args.lut = len(args.lut) # concatenate the parameters to the init function and the whole lut # as the command line parameters to the xe. list_args = [*(str(i) for i in asdict(self.args).values())] + [ @@ -139,7 +135,7 @@ def do_control(self, mclk_pt, ref_pt): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_pll_frac_reg(int(reg, 16)) + self.pll.update_frac_reg(int(reg, 16)) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) def do_control_from_error(self, error): @@ -151,7 +147,7 @@ def do_control_from_error(self, error): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_pll_frac_reg(int(reg, 16)) + self.pll.update_frac_reg(int(reg, 16)) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) @@ -217,7 +213,7 @@ def basic_test_vector(request, solution_12288, bin_dir): loop_rate_count = 1 # Generate init parameters - start_reg = sol.lut.get_lut()[0] + start_reg = sol.lut[0] args = DutArgs( target_output_frequency=target_mclk_f, kp=0.0, @@ -234,15 +230,15 @@ def basic_test_vector(request, solution_12288, bin_dir): # directly into the lut index. therefore the "ppm_range" or max # allowable diff must be at least as big as the LUT. *2 used here # to allow recovery from out of range values. - ppm_range=int(len(sol.lut.get_lut()) * 2), + ppm_range=int(len(sol.lut) * 2), lut=sol.lut, ) pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, sol.OD, sol.ACD, 1, 2) frequency_lut = [] - for reg in sol.lut.get_lut(): - pll.update_pll_frac_reg(reg) + for reg in sol.lut: + pll.update_frac_reg(reg) frequency_lut.append(pll.get_output_frequency()) frequency_range_frac = (frequency_lut[-1] - frequency_lut[0])/frequency_lut[0] @@ -251,7 +247,7 @@ def basic_test_vector(request, solution_12288, bin_dir): plt.savefig(bin_dir/f"lut-{name}.png") plt.close() - pll.update_pll_frac_reg(start_reg) + pll.update_frac_reg(start_reg) input_freqs = { "perfect": target_ref_f, @@ -430,8 +426,8 @@ def test_low_level_equivalence(solution_12288, bin_dir): target_ref_f = 48000 # Generate init parameters - start_reg = sol.lut.get_lut()[0] - lut_size = len(sol.lut.get_lut()) + start_reg = sol.lut[0] + lut_size = len(sol.lut) args = DutArgs( target_output_frequency=target_mclk_f, @@ -457,7 +453,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): frequency_lut = [] - pll.update_pll_frac_reg(start_reg) + pll.update_frac_reg(start_reg) input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) print(f"input_errors: {input_errors}") From 6d6fa50119240a180b4d5a1c8f72bbd89f83f4ac Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Nov 2023 17:41:17 +0000 Subject: [PATCH 014/118] Fixing tests --- python/sw_pll/controller_model.py | 2 +- tests/test_lib_sw_pll.py | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 09cb8225..18fd6113 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -68,7 +68,7 @@ class lut_pi_ctrl(pi_ctrl, lut_dco): """ def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): - self.dco = dco_model.lut_dco() + self.dco = lut_dco() self.lut_lookup_function = self.dco.get_lut() lut_size = self.dco.get_lut_size() self.diff = 0.0 # Most recent diff between expected and actual. Used by tests diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 027f2694..cf7de321 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -15,6 +15,7 @@ import copy from sw_pll.app_pll_model import pll_solution, app_pll_frac_calc +from sw_pll.sw_pll_sim import sim_sw_pll_lut from typing import Any from dataclasses import dataclass, asdict @@ -50,16 +51,12 @@ def __init__(self, args: DutArgs, pll): self.args = DutArgs(**asdict(args)) # copies the values self.lut = self.args.lut self.args.lut = len(self.lut) - self.ctrl = sw_pll_ctrl( + nominal_control_rate_hz = args.target_output_frequency / args.pll_ratio / args.loop_rate_count + self.ctrl = sim_sw_pll_lut( args.target_output_frequency, - self.lut_func, - len(self.lut), - args.loop_rate_count, - args.pll_ratio, + nominal_control_rate_hz, args.kp, - args.ki, - base_lut_index=args.nominal_lut_idx, - ) + args.ki, ) def lut_func(self, error): """Sim requires a function to provide access to the LUT. This is that""" @@ -76,17 +73,17 @@ def do_control(self, mclk_pt, _ref_pt): """ Execute control using simulator """ - f, l = self.ctrl.do_control(mclk_pt) + f, l = self.ctrl.do_control_loop(mclk_pt) - return l, f, self.ctrl.diff, self.ctrl.error_accum, 0, 0 + return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 def do_control_from_error(self, error): """ Execute control using simulator """ - f, l = self.ctrl.do_control_from_error(error) + f, l = self.ctrl.controller.do_control_from_error(error) - return l, f, self.ctrl.diff, self.ctrl.error_accum, 0, 0 + return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 class Dut: """ @@ -180,6 +177,7 @@ def bin_dir(): # pytest params for fixtures aren't as flexible as with tests as far as I can tell # so manually doing the combination here, 16k and 48k for both xsim and python versions. BASIC_TEST_PARAMS = list(product([16000, 48000], [Dut, SimDut])) +BASIC_TEST_PARAMS = list(product([48000], [SimDut])) #TEMP REMOVE!!!! @pytest.fixture( scope="module", params=BASIC_TEST_PARAMS, ids=[str(i) for i in BASIC_TEST_PARAMS] From 6a60a8d45e1cbc18ca1d30003c9583d55e06e29f Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 11:46:05 +0000 Subject: [PATCH 015/118] Lots of test fixes --- python/sw_pll/app_pll_model.py | 14 +++++--------- python/sw_pll/controller_model.py | 13 ++++++++++++- python/sw_pll/dco_model.py | 8 ++++---- python/sw_pll/sw_pll_sim.py | 20 +++++++++++--------- tests/test_lib_sw_pll.py | 19 +++++++------------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 9faa5a39..6038d2ff 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -174,17 +174,13 @@ def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min class pll_solution: """ Access to all the info from get_pll_solution, cleaning up temp files. - intended for programatic access from the tests + intended for programatic access from the tests. Creates a PLL setup and LUT and reads back the generated LUT """ def __init__(self, *args, **kwargs): - try: - self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs) - from .dco_model import lut_dco - dco = lut_dco("fractions.h") - self.lut, min_frac, max_frac = dco._read_lut_header("fractions.h") - finally: - Path("fractions.h").unlink(missing_ok=True) - Path("register_setup.h").unlink(missing_ok=True) + self.output_frequency, self.vco_freq, self.F, self.R, self.f, self.p, self.OD, self.ACD, self.ppm = get_pll_solution(*args, **kwargs) + from .dco_model import lut_dco + dco = lut_dco("fractions.h") + self.lut, min_frac, max_frac = dco._read_lut_header("fractions.h") if __name__ == '__main__': diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 18fd6113..932baf39 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -56,6 +56,12 @@ def do_control_from_error(self, error): return self.total_error + + +############################## +# LOOK UP TABLE IMPLEMENTATION +############################## + class lut_pi_ctrl(pi_ctrl, lut_dco): """ This class instantiates a control loop instance. It takes a lookup table function which can be generated @@ -90,7 +96,7 @@ def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): if verbose: print(f"Init lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") - def do_control_from_error(self, error, first_loop=False): + def get_dco_control_from_error(self, error, first_loop=False): """ Calculate the LUT setting from the input error """ @@ -104,6 +110,11 @@ def do_control_from_error(self, error, first_loop=False): return None if first_loop else dco_ctrl + +###################################### +# SIGMA DELTA MODULATOR IMPLEMENTATION +###################################### + class sdm_pi_ctrl(pi_ctrl, sigma_delta_dco): def __init__(self, Kp, Ki, Kii=None, verbose=False): diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 1ee84916..5064c9de 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -165,17 +165,17 @@ def plot_freq_range(self): # plt.show() plt.savefig("lut_dco_range.png", dpi=150) - def get_frequency_from_error(self, error): + def get_frequency_from_dco_control(self, dco_ctrl): """ - given an error, a LUT, and an APP_PLL, calculate the frequency + given a set_point, a LUT, and an APP_PLL, calculate the frequency """ - if error is None: + if dco_ctrl is None: return self.last_output_frequency, self.lock_status num_entries = self.get_lut_size() - set_point = int(error) + set_point = int(dco_ctrl) if set_point < 0: set_point = 0 self.lock_status = -1 diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index e82916fa..0b3f5113 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -1,10 +1,10 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -from .pfd_model import port_timer_pfd -from .dco_model import lut_dco, sigma_delta_dco, lock_status_lookup -from .controller_model import lut_pi_ctrl, sdm_pi_ctrl -from .analysis_tools import audio_modulator +from sw_pll.pfd_model import port_timer_pfd +from sw_pll.dco_model import lut_dco, sigma_delta_dco, lock_status_lookup +from sw_pll.controller_model import lut_pi_ctrl, sdm_pi_ctrl +from sw_pll.analysis_tools import audio_modulator import matplotlib.pyplot as plt import numpy as np @@ -48,8 +48,10 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False """ error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) - dco_ctl = self.controller.do_control_from_error(error, first_loop=first_loop) - output_frequency, lock_status = self.dco.get_frequency_from_error(dco_ctl) + dco_ctl = self.controller.get_dco_control_from_error(error, first_loop=first_loop) + output_frequency, lock_status = self.dco.get_frequency_from_dco_control(dco_ctl) + if first_loop: # We cannot claim to be locked if the PFD sees an error + lock_status = -1 if verbose: print(f"Raw error: {error}") @@ -83,10 +85,10 @@ def run_lut_sw_pll_sim(): real_time = 0.0 period_fraction = 1.0 - ppm_shift = -5 + ppm_shift = -200 for loop in range(simulation_iterations): - output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=False) + output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=True) # Now work out how many output clock counts this translates to measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6) @@ -226,7 +228,7 @@ def run_sd_sw_pll_sim(): if __name__ == '__main__': run_lut_sw_pll_sim() - run_sd_sw_pll_sim() + # run_sd_sw_pll_sim() diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index cf7de321..b4d43e11 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -81,7 +81,8 @@ def do_control_from_error(self, error): """ Execute control using simulator """ - f, l = self.ctrl.controller.do_control_from_error(error) + dco_ctl = self.ctrl.controller.get_dco_control_from_error(error) + f, l = self.ctrl.dco.get_frequency_from_dco_control(dco_ctl) return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 @@ -177,7 +178,6 @@ def bin_dir(): # pytest params for fixtures aren't as flexible as with tests as far as I can tell # so manually doing the combination here, 16k and 48k for both xsim and python versions. BASIC_TEST_PARAMS = list(product([16000, 48000], [Dut, SimDut])) -BASIC_TEST_PARAMS = list(product([48000], [SimDut])) #TEMP REMOVE!!!! @pytest.fixture( scope="module", params=BASIC_TEST_PARAMS, ids=[str(i) for i in BASIC_TEST_PARAMS] @@ -232,7 +232,7 @@ def basic_test_vector(request, solution_12288, bin_dir): lut=sol.lut, ) - pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, sol.OD, sol.ACD, 1, 2) + pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) frequency_lut = [] for reg in sol.lut: @@ -362,6 +362,7 @@ def test_lock_lost(basic_test_vector, test_f): this_df = df[df["ref_f"] == input_freqs[test_f]] not_locked_df = this_df[this_df["locked"] != 0] + assert not not_locked_df.empty, "Expected lock to be lost when out of range" first_not_locked = not_locked_df.index[0] after_not_locked = this_df[first_not_locked:]["locked"] != 0 @@ -413,7 +414,7 @@ def test_locked_values_within_desirable_ppm(basic_test_vector, test_f): def test_low_level_equivalence(solution_12288, bin_dir): """ Simple low level test of equivalence using do_control_from_error - Feed in random numbers into but C and Python DUTs and see if we get the same results + Feed in random numbers into C and Python DUTs and see if we get the same results """ _, xtal_freq, target_mclk_f, sol = solution_12288 @@ -438,18 +439,12 @@ def test_low_level_equivalence(solution_12288, bin_dir): ref_clk_expected_inc=0, app_pll_ctl_reg_val=0, app_pll_div_reg_val=start_reg, - nominal_lut_idx=0, # start low so there is some control to do - # with ki of 1 and the other values 0, the diff value translates - # directly into the lut index. therefore the "ppm_range" or max - # allowable diff must be at least as big as the LUT. *2 used here - # to allow recovery from out of range values. + nominal_lut_idx=lut_size//2, ppm_range=int(lut_size * 2), lut=sol.lut, ) - pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, sol.OD, sol.ACD, 1, 2) - - frequency_lut = [] + pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) pll.update_frac_reg(start_reg) From 5b9e2790f7b5c6772e5b73e3f2a0a5516b7cfe77 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:13:54 +0000 Subject: [PATCH 016/118] Split Jenkins into stages --- Jenkinsfile | 62 +++++++++++++++++++-------- tools/ci/{do-ci.sh => do-ci-build.sh} | 6 --- tools/ci/do-ci-tests.sh | 11 +++++ 3 files changed, 55 insertions(+), 24 deletions(-) rename tools/ci/{do-ci.sh => do-ci-build.sh} (73%) create mode 100644 tools/ci/do-ci-tests.sh diff --git a/Jenkinsfile b/Jenkinsfile index c644b7cc..b228bb16 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -30,29 +30,55 @@ pipeline { } stages { - stage('ci') { + stage('Build and tests') { agent { label 'linux&&64' } - steps { - sh 'mkdir lib_sw_pll' - // source checks require the directory - // name to be the same as the repo name - dir('lib_sw_pll') { - // checkout repo - checkout scm - installPipfile(false) - withVenv { - withTools(params.TOOLS_VERSION) { - sh './tools/ci/checkout-submodules.sh' - catchError { - sh './tools/ci/do-ci.sh' + stages{ + stage{'Checkout'} + steps { + sh 'mkdir lib_sw_pll' + // source checks require the directory + // name to be the same as the repo name + dir('lib_sw_pll') { + // checkout repo + checkout scm + installPipfile(false) + withVenv { + withTools(params.TOOLS_VERSION) { + sh './tools/ci/checkout-submodules.sh' + } } - zip archive: true, zipFile: "build.zip", dir: "build" - zip archive: true, zipFile: "tests.zip", dir: "tests/bin" - archiveArtifacts artifacts: "tests/bin/timing-report.txt", allowEmptyArchive: false + } + } + } + stage{'Build'} + steps { + dir('lib_sw_pll') { + withVenv { + withTools(params.TOOLS_VERSION) { + sh './tools/ci/do-ci-build.sh' + } + } + } + } + } + } + stage{'Test'} + steps { + dir('lib_sw_pll') { + withVenv { + withTools(params.TOOLS_VERSION) { + catchError { + sh './tools/ci/do-test.sh' + } + zip archive: true, zipFile: "build.zip", dir: "build" + zip archive: true, zipFile: "tests.zip", dir: "tests/bin" + archiveArtifacts artifacts: "tests/bin/timing-report.txt", allowEmptyArchive: false - junit 'tests/results.xml' + junit 'tests/results.xml' + } + } } } } diff --git a/tools/ci/do-ci.sh b/tools/ci/do-ci-build.sh similarity index 73% rename from tools/ci/do-ci.sh rename to tools/ci/do-ci-build.sh index 8c9e91e2..bf90494b 100755 --- a/tools/ci/do-ci.sh +++ b/tools/ci/do-ci-build.sh @@ -6,9 +6,3 @@ set -ex cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake cmake --build build --target all --target test_app --target test_app_low_level_api --target simple --target i2s_slave -j$(nproc) - -pushd tests -pytest --junitxml=results.xml -rA -v --durations=0 -o junit_logging=all -ls bin -popd - diff --git a/tools/ci/do-ci-tests.sh b/tools/ci/do-ci-tests.sh new file mode 100644 index 00000000..bd21f1e7 --- /dev/null +++ b/tools/ci/do-ci-tests.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash +# +# build and test stuff + +set -ex + +pushd tests +pytest --junitxml=results.xml -rA -v --durations=0 -o junit_logging=all +ls bin +popd + From d6690c839b5a5e23a9049e6e9c44961b89118d57 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:19:49 +0000 Subject: [PATCH 017/118] Jenkinsfile formatting --- Jenkinsfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b228bb16..14bd5d1e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,7 +35,7 @@ pipeline { label 'linux&&64' } stages{ - stage{'Checkout'} + stage('Checkout'){ steps { sh 'mkdir lib_sw_pll' // source checks require the directory @@ -52,19 +52,18 @@ pipeline { } } } - stage{'Build'} + stage('Build'){ steps { dir('lib_sw_pll') { withVenv { withTools(params.TOOLS_VERSION) { - sh './tools/ci/do-ci-build.sh' - } + sh './tools/ci/do-ci-build.sh' } } } } } - stage{'Test'} + stage('Test'){ steps { dir('lib_sw_pll') { withVenv { From a2c85e0ca4039cf5487da905538f9dbd3a23f841 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:23:45 +0000 Subject: [PATCH 018/118] Typo in jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 14bd5d1e..1dc23a1d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,7 +69,7 @@ pipeline { withVenv { withTools(params.TOOLS_VERSION) { catchError { - sh './tools/ci/do-test.sh' + sh './tools/ci/do-tests.sh' } zip archive: true, zipFile: "build.zip", dir: "build" zip archive: true, zipFile: "tests.zip", dir: "tests/bin" From 6f7ce42ddc9b6d36799652be1e88331d009b6628 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:29:08 +0000 Subject: [PATCH 019/118] Update submodules to latest tagged releases --- Jenkinsfile | 2 +- tools/ci/checkout-submodules.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1dc23a1d..1d1376cc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,7 +69,7 @@ pipeline { withVenv { withTools(params.TOOLS_VERSION) { catchError { - sh './tools/ci/do-tests.sh' + sh './tools/ci/do-ci-tests.sh' } zip archive: true, zipFile: "build.zip", dir: "build" zip archive: true, zipFile: "tests.zip", dir: "tests/bin" diff --git a/tools/ci/checkout-submodules.sh b/tools/ci/checkout-submodules.sh index bd16abb5..0e261312 100755 --- a/tools/ci/checkout-submodules.sh +++ b/tools/ci/checkout-submodules.sh @@ -22,11 +22,11 @@ rm -rf modules mkdir -p modules pushd modules -clone fwk_core git@github.com:xmos/fwk_core.git 9e4f6196386995e2d7786b376091404638055639 -clone fwk_io git@github.com:xmos/fwk_io.git 6b3275cbe4e39abce3b6e822655732767a62740d +clone fwk_core git@github.com:xmos/fwk_core.git v1.0.2 +clone fwk_io git@github.com:xmos/fwk_io.git v3.3.0 -clone infr_scripts_py git@github.com:xmos/infr_scripts_py.git 1d767cbe89a3223da7a4e27c283fb96ee2a279c9 -clone infr_apps git@github.com:xmos/infr_apps.git 8bc62324b19a1ab32b1e5a5e262f40f710f9f5c1 +clone infr_scripts_py git@github.com:xmos/infr_scripts_py.git v1.2.1 +clone infr_apps git@github.com:xmos/infr_apps.git v1.4.6 pip install -e infr_apps -e infr_scripts_py popd From 64d77e80031184e7eed2858d41632bdd726bfcff Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:33:52 +0000 Subject: [PATCH 020/118] Make past few commit changes actually work --- tools/ci/checkout-submodules.sh | 1 - tools/ci/do-ci-build.sh | 2 +- tools/ci/do-ci-tests.sh | 0 3 files changed, 1 insertion(+), 2 deletions(-) mode change 100644 => 100755 tools/ci/do-ci-tests.sh diff --git a/tools/ci/checkout-submodules.sh b/tools/ci/checkout-submodules.sh index 0e261312..19e78f6e 100755 --- a/tools/ci/checkout-submodules.sh +++ b/tools/ci/checkout-submodules.sh @@ -12,7 +12,6 @@ clone() { git init git remote add origin $2 git fetch --depth 1 origin $3 - git checkout FETCH_HEAD git submodule update --init --recursive --depth 1 --jobs $(nproc) popd } diff --git a/tools/ci/do-ci-build.sh b/tools/ci/do-ci-build.sh index bf90494b..cdbeefee 100755 --- a/tools/ci/do-ci-build.sh +++ b/tools/ci/do-ci-build.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash # -# build and test stuff +# build stuff set -ex diff --git a/tools/ci/do-ci-tests.sh b/tools/ci/do-ci-tests.sh old mode 100644 new mode 100755 From 028d19309df185bc742b1e2ee9aa3071adebfb44 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:37:29 +0000 Subject: [PATCH 021/118] Revert infrapps --- tools/ci/checkout-submodules.sh | 4 ++-- tools/ci/do-ci-tests.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/ci/checkout-submodules.sh b/tools/ci/checkout-submodules.sh index 19e78f6e..9b4346c7 100755 --- a/tools/ci/checkout-submodules.sh +++ b/tools/ci/checkout-submodules.sh @@ -24,8 +24,8 @@ pushd modules clone fwk_core git@github.com:xmos/fwk_core.git v1.0.2 clone fwk_io git@github.com:xmos/fwk_io.git v3.3.0 -clone infr_scripts_py git@github.com:xmos/infr_scripts_py.git v1.2.1 -clone infr_apps git@github.com:xmos/infr_apps.git v1.4.6 +clone infr_scripts_py git@github.com:xmos/infr_scripts_py.git 1d767cbe89a3223da7a4e27c283fb96ee2a279c9 +clone infr_apps git@github.com:xmos/infr_apps.git 8bc62324b19a1ab32b1e5a5e262f40f710f9f5c1 pip install -e infr_apps -e infr_scripts_py popd diff --git a/tools/ci/do-ci-tests.sh b/tools/ci/do-ci-tests.sh index bd21f1e7..773b094b 100755 --- a/tools/ci/do-ci-tests.sh +++ b/tools/ci/do-ci-tests.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash # -# build and test stuff +# test stuff set -ex From 4a0f2d2094fda59e985691e159967f98b2001c01 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 12:42:03 +0000 Subject: [PATCH 022/118] Fix checkout --- python/sw_pll/dco_model.py | 2 +- tools/ci/checkout-submodules.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 5064c9de..18eb2560 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -280,4 +280,4 @@ def do_modulate(self, input): sdm_dco = sigma_delta_dco() for i in range(30): output_frequency = sdm_dco.do_modulate(400000) - print(i, output_frequency) \ No newline at end of file + print(i, output_frequency) diff --git a/tools/ci/checkout-submodules.sh b/tools/ci/checkout-submodules.sh index 9b4346c7..e51e6427 100755 --- a/tools/ci/checkout-submodules.sh +++ b/tools/ci/checkout-submodules.sh @@ -12,6 +12,7 @@ clone() { git init git remote add origin $2 git fetch --depth 1 origin $3 + git checkout FETCH_HEAD git submodule update --init --recursive --depth 1 --jobs $(nproc) popd } From b9cba06715c296f0b0e8c439e5ac33e0e26d936e Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 16:33:30 +0000 Subject: [PATCH 023/118] Some general tidying --- python/sw_pll/analysis_tools.py | 11 +++- python/sw_pll/app_pll_model.py | 28 +++++++++++ python/sw_pll/controller_model.py | 13 +++-- python/sw_pll/dco_model.py | 83 ++++++++++++++++++++++++------- python/sw_pll/sw_pll_sim.py | 38 ++------------ 5 files changed, 116 insertions(+), 57 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index a07b77a7..8baddf0d 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np import soundfile +from scipy.io import wavfile # soundfile has some issues writing high Fs files class audio_modulator: def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): @@ -28,7 +29,10 @@ def modulate_waveform(self): def save_modulated_wav(self, filename): integer_output = np.int16(self.waveform * 32767) - soundfile.write(filename, integer_output, int(self.sample_rate)) + print(self.sample_rate) + print(integer_output.shape) + # soundfile.write(filename, integer_output, int(self.sample_rate)) + wavfile.write(filename, int(self.sample_rate), integer_output) def plot_modulated_fft(self, filename, skip_s=None): start_x = 0 if skip_s is None else int(skip_s * self.sample_rate) // 2 * 2 @@ -65,7 +69,7 @@ def load_wav(self, filename): """ This module is not intended to be run directly. This is here for internal testing only. """ - if False: + if 0: test_len = 10 audio = audio_modulator(test_len) for time_s in range(test_len): @@ -79,5 +83,8 @@ def load_wav(self, filename): else: audio = audio_modulator(1) audio.load_wav("modulated_tone_1000Hz_sd_ds.wav") + # audio = audio_modulator(1, sample_rate=3072000) + # audio.modulate_waveform() audio.plot_modulated_fft("modulated_tone_1000Hz_sd_ds.png") + # audio.save_modulated_wav("modulated.wav") diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 6038d2ff..c6db77a7 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -91,7 +91,35 @@ def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min This function and the underlying call to pll_calc may take several seconds to complete since it searches a range of possible solutions numerically. + + input_frequency - The xcore clock frequency, normally the XTAL frequency + nominal_ref_frequency - The nominal input reference frequency + target_output_frequency - The nominal target output frequency + max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance + min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance + ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance + fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance + fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance + + # Example profiles to produce typical frequencies seen in audio systems + profiles = [ + # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, + # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, + # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, + # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, + # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, + # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size + {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, + ] """ + + + input_frequency_MHz = input_frequency / 1000000.0 target_output_frequency_MHz = target_output_frequency / 1000000.0 diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 932baf39..22b1d104 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -1,7 +1,7 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -from .dco_model import lut_dco, sigma_delta_dco +from sw_pll.dco_model import lut_dco, sigma_delta_dco import numpy as np @@ -23,7 +23,7 @@ def __init__(self, Kp, Ki, Kii=None, i_windup_limit=None, ii_windup_limit=None, self.verbose = verbose if verbose: - print(f"Init sw_pll_lut_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") + print(f"Init sw_pll_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") def _reset_controller(self): self.error_accum = 0.0 @@ -116,7 +116,7 @@ def get_dco_control_from_error(self, error, first_loop=False): ###################################### class sdm_pi_ctrl(pi_ctrl, sigma_delta_dco): - def __init__(self, Kp, Ki, Kii=None, verbose=False): + def __init__(self, Kp, Ki, Kii=None, verbose=False): pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=verbose) @@ -148,3 +148,10 @@ def do_control_from_error(self, error): for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) + Kp = 0.0 + Ki = 0.1 + Kii = 0.1 + + sw_pll = sdm_pi_ctrl(Kp, Ki, Kii=Kii, verbose=True) + for error_input in range(-10, 20): + dco_ctrl = sw_pll.do_control_from_error(error_input) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 18eb2560..c6a0514e 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -1,7 +1,7 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -from .app_pll_model import register_file, app_pll_frac_calc +from sw_pll.app_pll_model import register_file, app_pll_frac_calc import matplotlib.pyplot as plt import numpy as np import os @@ -136,11 +136,6 @@ def print_stats(self, target_output_frequency): return min_freq, mid_freq, max_freq, steps - def _reg_to_frac(self, register): - f = (register & 0xff00) >> 8 - p = register & 0xff - - return f, p def plot_freq_range(self): """ @@ -248,15 +243,16 @@ class sigma_delta_dco(sdm): """ TBD """ - def __init__(self): - # PLL solution from Joe's code 24.576MHz - input_freq =24000000 - F = int(102.4 - 1) - R = 1 - 1 - f = 2 - 1 - p = 5 - 1 - OD = 5 - 1 - ACD = 5 - 1 + def __init__(self, profile): + # PLL solution profiles depending on target output clock + # These are designed to work with a SFM at 1MHz + # 10ps jitter 100Hz-40kHz. Low freq noise floor -100dBc + profiles = {"24.576": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, + "22.5792": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}, + } + self.p_value = 8 - 1 + + input_freq, F, R, f, p, OD, ACD = list(profiles[profile].values()) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) sdm.__init__(self) @@ -264,7 +260,56 @@ def __init__(self): def do_modulate(self, input): ds_out, lock_status = sdm.do_sigma_delta(self, input) - return self.app_pll.update_frac(ds_out, 7), lock_status + # TODO support turning fractional off. Needs work in app_pll_model + """ + if (ds_out == 0) + frac_val = 0x00000007; // 0/8 + else + frac_val = ((ds_out - 1) << 8) | 0x80000007; // 1/8 to 8/8 + """ + return self.app_pll.update_frac(ds_out, self.p_value), lock_status + + def print_stats(self, target_output_frequency): + """ + Returns a summary of the SDM range and steps. + """ + + steps = self.p_value - 1 + min_freq = self.app_pll.update_frac(0, self.p_value) + max_freq = self.app_pll.update_frac(steps, self.p_value) + + + ave_step_size = (max_freq - min_freq) / steps + + print(f"SDM min_freq: {min_freq:.0f}Hz") + print(f"SDM max_freq: {max_freq:.0f}Hz") + print(f"SDM steps: {steps}") + print(f"PPM range: {1e6 * (1 - target_output_frequency / min_freq):.6}") + print(f"PPM range: +{1e6 * (max_freq / target_output_frequency - 1):.6}") + + return min_freq, max_freq, steps + + + def plot_freq_range(self): + """ + Generates a plot of the frequency range of the LUT and + visually shows the spacing of the discrete frequencies + that it can produce. + """ + + frequencies = [] + for step in range(self.p_value): + frequencies.append(self.app_pll.update_frac(step, self.p_value)) + + plt.clf() + plt.plot(frequencies, color='green', marker='.', label='frequency') + plt.title('PLL fractional range', fontsize=14) + plt.xlabel(f'LUT index', fontsize=14) + plt.ylabel('Frequency', fontsize=10) + plt.legend(loc="upper right") + plt.grid(True) + # plt.show() + plt.savefig("sdm_dco_range.png", dpi=150) if __name__ == '__main__': @@ -277,7 +322,9 @@ def do_modulate(self, input): # dco.plot_freq_range() # dco.print_stats(12288000) - sdm_dco = sigma_delta_dco() + sdm_dco = sigma_delta_dco("24.576") + sdm_dco.print_stats(24576000) + sdm_dco.plot_freq_range() for i in range(30): - output_frequency = sdm_dco.do_modulate(400000) + output_frequency = sdm_dco.do_modulate(500000) print(i, output_frequency) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 0b3f5113..e3da0d94 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -134,7 +134,7 @@ def __init__( self, self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) self.controller = sdm_pi_ctrl(Kp, Ki, Kii) - self.dco = sigma_delta_dco() + self.dco = sigma_delta_dco("24.576") self.target_output_frequency = target_output_frequency self.time = 0.0 @@ -179,7 +179,6 @@ def run_sd_sw_pll_sim(): test_tone_hz = 1000 audio = audio_modulator(simulation_iterations * 1 / nominal_sd_rate_hz, sample_rate=6144000, test_tone_hz=test_tone_hz) - freq_log = [] target_freq_log = [] real_time_log = [] @@ -227,35 +226,6 @@ def run_sd_sw_pll_sim(): if __name__ == '__main__': - run_lut_sw_pll_sim() - # run_sd_sw_pll_sim() - - - -# # Example profiles to produce typical frequencies seen in audio systems -# profiles = [ -# # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, -# # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, -# # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, -# # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, -# # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size -# {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, -# # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size -# {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, -# ] - -# """ -# ref_to_loop_call_rate - Determines how often to call the control loop in terms of ref clocks -# xtal_frequency - The xcore clock frequency -# nominal_ref_frequency - The nominal input reference frequency -# target_output_frequency - The nominal target output frequency -# max_denom - (Optional) The maximum fractional denominator. See/doc/sw_pll.rst for guidance -# min_F - (Optional) The minimum integer numerator. See/doc/sw_pll.rst for guidance -# ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance -# fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance -# fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance -# """ + # run_lut_sw_pll_sim() + run_sd_sw_pll_sim() + \ No newline at end of file From aafe668dbfbacaf6bb5359b5a009a1f531114465 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 16:51:48 +0000 Subject: [PATCH 024/118] Ensure error zeroed on first_loop --- python/sw_pll/analysis_tools.py | 4 +--- python/sw_pll/controller_model.py | 3 ++- python/sw_pll/sw_pll_sim.py | 12 ++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index 8baddf0d..a0f4faea 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -29,9 +29,7 @@ def modulate_waveform(self): def save_modulated_wav(self, filename): integer_output = np.int16(self.waveform * 32767) - print(self.sample_rate) - print(integer_output.shape) - # soundfile.write(filename, integer_output, int(self.sample_rate)) + # soundfile.write(filename, integer_output, int(self.sample_rate)) # This struggles with >768ksps wavfile.write(filename, int(self.sample_rate), integer_output) def plot_modulated_fft(self, filename, skip_s=None): diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 22b1d104..596d9548 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -102,9 +102,9 @@ def get_dco_control_from_error(self, error, first_loop=False): """ self.diff = error # Used by tests - if first_loop: pi_ctrl._reset_controller(self) + error = 0.0 dco_ctrl = self.base_lut_index - pi_ctrl.do_control_from_error(self, error) @@ -137,6 +137,7 @@ def do_control_from_error(self, error): return self.initial_setting + self.iir_y + if __name__ == '__main__': """ This module is not intended to be run directly. This is here for internal testing only. diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index e3da0d94..1f5bc1d4 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -35,8 +35,8 @@ def __init__( self, Kii=None): self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) - self.controller = lut_pi_ctrl(Kp, Ki) - self.dco = lut_dco() + self.controller = lut_pi_ctrl(Kp, Ki, verbose=False) + self.dco = lut_dco(verbose=False) self.target_output_frequency = target_output_frequency self.time = 0.0 @@ -69,7 +69,7 @@ def run_lut_sw_pll_sim(): output_frequency = nominal_output_hz simulation_iterations = 100 Kp = 0.0 - Ki = 0.1 + Ki = 1.0 Kii = 0.0 sw_pll = sim_sw_pll_lut(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) @@ -85,10 +85,10 @@ def run_lut_sw_pll_sim(): real_time = 0.0 period_fraction = 1.0 - ppm_shift = -200 + ppm_shift = +500 for loop in range(simulation_iterations): - output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=True) + output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=False) # Now work out how many output clock counts this translates to measured_clock_count_inc = output_frequency / nominal_control_rate_hz * (1 - ppm_shift / 1e6) @@ -226,6 +226,6 @@ def run_sd_sw_pll_sim(): if __name__ == '__main__': - # run_lut_sw_pll_sim() + run_lut_sw_pll_sim() run_sd_sw_pll_sim() \ No newline at end of file From bce78dabb839411592c10b36137914dde1b71d2c Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 17:03:59 +0000 Subject: [PATCH 025/118] Add scipy to requirements --- python/sw_pll/app_pll_model.py | 2 +- python/sw_pll/sw_pll_sim.py | 4 ++-- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index c6db77a7..aa67923e 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -137,7 +137,7 @@ def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min matches = re.findall(regex, output) for solution in matches: - F = int(float(re.search(".+FD\s+(\d+.\d+).+", solution).groups()[0])) + F = int(float(re.search(r".+FD\s+(\d+.\d+).+", solution).groups()[0])) solutions.append(solution) Fs.append(F) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 1f5bc1d4..7850c3f2 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -85,7 +85,7 @@ def run_lut_sw_pll_sim(): real_time = 0.0 period_fraction = 1.0 - ppm_shift = +500 + ppm_shift = +50 for loop in range(simulation_iterations): output_frequency, lock_status = sw_pll.do_control_loop(output_clock_count, period_fraction=period_fraction, verbose=False) @@ -222,7 +222,7 @@ def run_sd_sw_pll_sim(): audio.modulate_waveform() audio.save_modulated_wav("modulated_tone_1000Hz_sdm.wav") - audio.plot_modulated_fft("modulated_fft_sdm.png", skip_s=real_time/2) # skip so we ignore the inital lock period + audio.plot_modulated_fft("modulated_fft_sdm.png", skip_s=real_time / 2) # skip so we ignore the inital lock period if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index 097d778e..c8599821 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ pytest pandas soundfile - +scipy From 1127573335fb921d1c4cd010f3d4fc63738d87fb Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 13 Nov 2023 17:49:59 +0000 Subject: [PATCH 026/118] Support non-fractional setting of app pll and in sdm dco --- python/sw_pll/app_pll_model.py | 26 +++++++++++++++++++------- python/sw_pll/dco_model.py | 32 ++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index aa67923e..01cb7e55 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -22,9 +22,10 @@ def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD self.OD = OD_init self.ACD = ACD_init self.f = f_init # fractional multiplier (+1.0) - self.p = p_init # fractional fivider (+1.0) + self.p = p_init # fractional divider (+1.0) self.output_frequency = None self.lock_status_state = 0 + self.fractional_enable = True self.verbose = verbose self.calc_frequency() @@ -47,10 +48,14 @@ def calc_frequency(self): assert type(self.p) is int, f"Error: r must be an INT" assert type(self.f) is int, f"Error: f must be an INT" - assert self.p > self.f, "Error f is not < p: {self.f} {self.p}" - # From XU316-1024-QF60A-xcore.ai-Datasheet_22.pdf - self.output_frequency = self.input_frequency * (self.F + 1.0 + ((self.f + 1) / (self.p + 1)) ) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1)) + if self.fractional_enable: + # assert self.p > self.f, "Error f is not < p: {self.f} {self.p}" # This check has been removed as Joe found it to be OK in RTL/practice + pll_ratio = (self.F + 1.0 + ((self.f + 1) / (self.p + 1)) ) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1)) + else: + pll_ratio = (self.F + 1.0) / 2.0 / (self.R + 1.0) / (self.OD + 1.0) / (2.0 * (self.ACD + 1)) + + self.output_frequency = self.input_frequency * pll_ratio return self.output_frequency @@ -66,15 +71,21 @@ def update_all(self, F, R, OD, ACD, f, p): self.p = p return self.calc_frequency() - def update_frac(self, f, p): + def update_frac(self, f, p, fractional=True): self.f = f self.p = p + self.fractional_enable = fractional return self.calc_frequency() def update_frac_reg(self, reg): - """determine f and p from the register number and recalculate frequency""" + """ + Determine f and p from the register number and recalculate frequency + Assumes fractional is set to true + """ f = int((reg >> 8) & ((2**8)-1)) p = int(reg & ((2**8)-1)) + assert self.fractional_enable is True + return self.update_frac(f, p) # see /doc/sw_pll.rst for guidance on these settings @@ -225,5 +236,6 @@ def __init__(self, *args, **kwargs): print(f"Got output frequency: {app_pll.calc_frequency()}") p = 10 for f in range(p): - print(f"For f: {f} got frequency: {app_pll.update_frac(f, p)}") + for frac_enable in [True, False]: + print(f"For f: {f} frac_enable: {frac_enable} got frequency: {app_pll.update_frac(f, p, frac_enable)}") diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index c6a0514e..16b49ef9 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -257,26 +257,30 @@ def __init__(self, profile): self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) sdm.__init__(self) + + def _ds_out_to_freq(self, ds_out): + if ds_out == 0: + # Step 0 + return self.app_pll.update_frac(0, 0, False) + else: + # Steps 1 to 8 inclusive + return self.app_pll.update_frac(ds_out - 1, self.p_value) + def do_modulate(self, input): ds_out, lock_status = sdm.do_sigma_delta(self, input) - # TODO support turning fractional off. Needs work in app_pll_model - """ - if (ds_out == 0) - frac_val = 0x00000007; // 0/8 - else - frac_val = ((ds_out - 1) << 8) | 0x80000007; // 1/8 to 8/8 - """ - return self.app_pll.update_frac(ds_out, self.p_value), lock_status + frequency = self._ds_out_to_freq(ds_out) + + return frequency, lock_status def print_stats(self, target_output_frequency): """ Returns a summary of the SDM range and steps. """ - steps = self.p_value - 1 - min_freq = self.app_pll.update_frac(0, self.p_value) - max_freq = self.app_pll.update_frac(steps, self.p_value) + steps = self.p_value + 1 + 1 # because p_value is -1 and we have frac off state + min_freq = self._ds_out_to_freq(0) + max_freq = self._ds_out_to_freq(self.p_value + 1) ave_step_size = (max_freq - min_freq) / steps @@ -298,13 +302,13 @@ def plot_freq_range(self): """ frequencies = [] - for step in range(self.p_value): - frequencies.append(self.app_pll.update_frac(step, self.p_value)) + for step in range(self.p_value + 1 + 1): # +1 since p value is +1 in datasheet and another +1 so we hit the max value + frequencies.append(self._ds_out_to_freq(step)) plt.clf() plt.plot(frequencies, color='green', marker='.', label='frequency') plt.title('PLL fractional range', fontsize=14) - plt.xlabel(f'LUT index', fontsize=14) + plt.xlabel(f'SDM step', fontsize=14) plt.ylabel('Frequency', fontsize=10) plt.legend(loc="upper right") plt.grid(True) From b3c597677bb49a037d0f706a31de0228f7352b23 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 14 Nov 2023 09:07:43 +0000 Subject: [PATCH 027/118] Add comments to class methods --- python/sw_pll/app_pll_model.py | 6 ++++++ python/sw_pll/controller_model.py | 17 ++++++++++++++--- python/sw_pll/dco_model.py | 22 +++++++++++++++++----- python/sw_pll/sw_pll_sim.py | 7 +++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 01cb7e55..a14a4ada 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -63,6 +63,9 @@ def get_output_frequency(self): return self.output_frequency def update_all(self, F, R, OD, ACD, f, p): + """ + Reset all App PLL vars + """ self.F = F self.R = R self.OD = OD @@ -72,6 +75,9 @@ def update_all(self, F, R, OD, ACD, f, p): return self.calc_frequency() def update_frac(self, f, p, fractional=True): + """ + Update only the fractional parts of the App PLL + """ self.f = f self.p = p self.fractional_enable = fractional diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 596d9548..92ef5a26 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -26,6 +26,9 @@ def __init__(self, Kp, Ki, Kii=None, i_windup_limit=None, ii_windup_limit=None, print(f"Init sw_pll_pi_ctrl, Kp: {Kp} Ki: {Ki} Kii: {Kii}") def _reset_controller(self): + """ + Reset anu accumulated state + """ self.error_accum = 0.0 self.error_accum_accum = 0.0 @@ -72,8 +75,10 @@ class lut_pi_ctrl(pi_ctrl, lut_dco): desired response. The function run_sim allows for a plot of a step resopnse input which allows this to be done visually. """ - def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): - + def __init__(self, Kp, Ki, Kii=None, base_lut_index=None, verbose=False): + """ + Create instance absed on specific control constants + """ self.dco = lut_dco() self.lut_lookup_function = self.dco.get_lut() lut_size = self.dco.get_lut_size() @@ -117,7 +122,9 @@ def get_dco_control_from_error(self, error, first_loop=False): class sdm_pi_ctrl(pi_ctrl, sigma_delta_dco): def __init__(self, Kp, Ki, Kii=None, verbose=False): - + """ + Create instance absed on specific control constants + """ pi_ctrl.__init__(self, Kp, Ki, Kii=Kii, verbose=verbose) # Low pass filter state @@ -128,6 +135,10 @@ def __init__(self, Kp, Ki, Kii=None, verbose=False): self.initial_setting = 478151 def do_control_from_error(self, error): + """ + Run the control loop. Also contains an additional + low passs filtering stage. + """ x = pi_ctrl.do_control_from_error(self, -error) # Filter some noise into DCO to reduce jitter diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 16b49ef9..6a17a2ae 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -47,6 +47,9 @@ def __init__(self, header_file = "fractions.h", verbose=False): # fixed header self.lock_status = -1 def _read_lut_header(self, header_file): + """ + read and parse the pre-written LUT + """ if not os.path.exists(header_file): assert False, f"Please initialize a lut_dco to produce a parsable header file {header_file}" @@ -210,7 +213,7 @@ def __init__(self): self.lock_status = -1 # generalized version without fixed point shifts. WIP!! - # takes a Q20 number from 60000 to 980000 or 0.0572 to 0.934 + # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) def do_sigma_delta(self, ds_in): if ds_in > self.ds_in_max: print(f"SDM Pos clip: {ds_in}, {self.ds_in_max}") @@ -241,12 +244,15 @@ def do_sigma_delta(self, ds_in): class sigma_delta_dco(sdm): """ - TBD + DCO based on the sigma delta modulator + PLL solution profiles depending on target output clock + These are designed to work with a SDM running at 1MHz + 10ps jitter 100Hz-40kHz. Low freq noise floor -100dBc """ def __init__(self, profile): - # PLL solution profiles depending on target output clock - # These are designed to work with a SFM at 1MHz - # 10ps jitter 100Hz-40kHz. Low freq noise floor -100dBc + """ + Create a sigmal delta DCO targetting either 24.576 or 22.5792MHz + """ profiles = {"24.576": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, "22.5792": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}, } @@ -259,6 +265,9 @@ def __init__(self, profile): def _ds_out_to_freq(self, ds_out): + """ + Translate the SDM steps to register settings + """ if ds_out == 0: # Step 0 return self.app_pll.update_frac(0, 0, False) @@ -267,6 +276,9 @@ def _ds_out_to_freq(self, ds_out): return self.app_pll.update_frac(ds_out - 1, self.p_value) def do_modulate(self, input): + """ + Input a control value and output a SDM signal + """ ds_out, lock_status = sdm.do_sigma_delta(self, input) frequency = self._ds_out_to_freq(ds_out) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 7850c3f2..c31583d9 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -144,6 +144,9 @@ def __init__( self, def do_control_loop(self, output_clock_count, verbose=False): + """ + Run the control loop which runs at a tiny fraction of the SDM rate + """ error, first_loop = self.pfd.get_error(output_clock_count) ctrl_output = self.controller.do_control_from_error(error) @@ -157,6 +160,10 @@ def do_control_loop(self, output_clock_count, verbose=False): return self.control_setting def do_sigma_delta(self): + """ + Run the SDM which needs to be run constantly at the SDM rate. + See DCO (dco_model) for details + """ frequncy, lock_status = self.dco.do_modulate(self.control_setting) return frequncy, lock_status From 6069ce57df4dbfd4242f2428d606dcba8b0e6d8b Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 15 Nov 2023 13:45:45 +0000 Subject: [PATCH 028/118] Add 500k SDM update profiles and tidy --- python/sw_pll/dco_model.py | 56 ++++++++++++++++++++----------------- python/sw_pll/sw_pll_sim.py | 4 +-- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 6a17a2ae..a0991d2c 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -228,35 +228,41 @@ def do_sigma_delta(self, ds_in): else: self.lock_status = 0 - ds_out = int(self.ds_x3 * 0.002197265625) + sdm_out = int(self.ds_x3 * 0.002197265625) - if ds_out > 8: - ds_out = 8 - if ds_out < 0: - ds_out = 0 + if sdm_out > 8: + sdm_out = 8 + if sdm_out < 0: + sdm_out = 0 - self.ds_x3 += int((self.ds_x2 * 0.03125) - (ds_out * 768)) - self.ds_x2 += int((self.ds_x1 * 0.03125) - (ds_out * 16384)) - self.ds_x1 += int(ds_in - (ds_out * 131072)) + self.ds_x3 += int((self.ds_x2 * 0.03125) - (sdm_out * 768)) + self.ds_x2 += int((self.ds_x1 * 0.03125) - (sdm_out * 16384)) + self.ds_x1 += int(ds_in - (sdm_out * 131072)) - return ds_out, self.lock_status + return sdm_out, self.lock_status class sigma_delta_dco(sdm): """ DCO based on the sigma delta modulator PLL solution profiles depending on target output clock - These are designed to work with a SDM running at 1MHz - 10ps jitter 100Hz-40kHz. Low freq noise floor -100dBc + + These are designed to work with a SDM either running at + 500kHz: + - 50ps jitter 100Hz-40kHz with low freq noise floor -93dBc. + or 1MHz: + - 10ps jitter 100Hz-40kHz with very low freq noise floor -100dBc """ def __init__(self, profile): """ Create a sigmal delta DCO targetting either 24.576 or 22.5792MHz """ - profiles = {"24.576": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, - "22.5792": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}, - } - self.p_value = 8 - 1 + profiles = {"24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1}, + "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1}, + "24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, + "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}} + + self.p_value = 8 # 8 frac settings + 1 non frac setting input_freq, F, R, f, p, OD, ACD = list(profiles[profile].values()) @@ -264,24 +270,24 @@ def __init__(self, profile): sdm.__init__(self) - def _ds_out_to_freq(self, ds_out): + def _sdm_out_to_freq(self, sdm_out): """ Translate the SDM steps to register settings """ - if ds_out == 0: + if sdm_out == 0: # Step 0 return self.app_pll.update_frac(0, 0, False) else: # Steps 1 to 8 inclusive - return self.app_pll.update_frac(ds_out - 1, self.p_value) + return self.app_pll.update_frac(sdm_out - 1, self.p_value - 1) def do_modulate(self, input): """ Input a control value and output a SDM signal """ - ds_out, lock_status = sdm.do_sigma_delta(self, input) + sdm_out, lock_status = sdm.do_sigma_delta(self, input) - frequency = self._ds_out_to_freq(ds_out) + frequency = self._sdm_out_to_freq(sdm_out) return frequency, lock_status @@ -290,9 +296,9 @@ def print_stats(self, target_output_frequency): Returns a summary of the SDM range and steps. """ - steps = self.p_value + 1 + 1 # because p_value is -1 and we have frac off state - min_freq = self._ds_out_to_freq(0) - max_freq = self._ds_out_to_freq(self.p_value + 1) + steps = self.p_value + 1 # +1 we have frac off state + min_freq = self._sdm_out_to_freq(0) + max_freq = self._sdm_out_to_freq(self.p_value) ave_step_size = (max_freq - min_freq) / steps @@ -315,7 +321,7 @@ def plot_freq_range(self): frequencies = [] for step in range(self.p_value + 1 + 1): # +1 since p value is +1 in datasheet and another +1 so we hit the max value - frequencies.append(self._ds_out_to_freq(step)) + frequencies.append(self._sdm_out_to_freq(step)) plt.clf() plt.plot(frequencies, color='green', marker='.', label='frequency') @@ -338,7 +344,7 @@ def plot_freq_range(self): # dco.plot_freq_range() # dco.print_stats(12288000) - sdm_dco = sigma_delta_dco("24.576") + sdm_dco = sigma_delta_dco("24.576_1M") sdm_dco.print_stats(24576000) sdm_dco.plot_freq_range() for i in range(30): diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index c31583d9..868de600 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -175,7 +175,7 @@ def run_sd_sw_pll_sim(): nominal_sd_rate_hz = 1e6 output_frequency = nominal_output_hz - simulation_iterations = 2000000 + simulation_iterations = 1000000 Kp = 0.0 Ki = 32.0 Kii = 0.25 @@ -191,7 +191,7 @@ def run_sd_sw_pll_sim(): real_time_log = [] real_time = 0.0 - ppm_shift = +0 + ppm_shift = +50 # For working out when to do control calls control_time_inc = 1 / nominal_control_rate_hz From b3baa2816e81200a3eec05237a3487261526e92f Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 15 Nov 2023 15:16:28 +0000 Subject: [PATCH 029/118] Add generation of register setup for SDM DCO --- python/sw_pll/app_pll_model.py | 30 +++++++++++++++++++++++++++++- python/sw_pll/dco_model.py | 9 +++++++++ python/sw_pll/sw_pll_sim.py | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index a14a4ada..c0506d42 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -4,12 +4,16 @@ import subprocess import re from pathlib import Path +from sw_pll.pll_calc import print_regs +from contextlib import redirect_stdout +import io register_file = "register_setup.h" # can be changed as needed. This contains the register setup params and is accessible via C in the firmware + class app_pll_frac_calc: """ - This class uses the formula in the XU316 datasheet to calculate the output frequency of the + This class uses the formulae in the XU316 datasheet to calculate the output frequency of the application PLL (sometimes called secondary PLL) from the register settings provided. It uses the checks specified in the datasheet to ensure the settings are valid, and will assert if not. To keep the inherent jitter of the PLL output down to a minimum, it is recommended that R be kept small, @@ -94,6 +98,30 @@ def update_frac_reg(self, reg): return self.update_frac(f, p) + def gen_register_file_text(self): + """ + Helper used to generate text for the register setup h file + """ + text = f"/* Input freq: {self.input_frequency}\n" + text += f" F: {self.F}\n" + text += f" R: {self.R}\n" + text += f" f: {self.f}\n" + text += f" p: {self.p}\n" + text += f" OD: {self.OD}\n" + text += f" ACD: {self.ACD}\n" + text += "*/\n\n" + + # This is a way of calling a printing function and capturing the STDOUT + class args: + app = True + f = io.StringIO() + with redirect_stdout(f): + # in pll_calc, op_div = OD, fb_div = F, f, p, ref_div = R, fin_op_div = ACD + print_regs(args, self.OD + 1, [self.F + 1, self.f + 1, self.p + 1] , self.R + 1, self.ACD + 1) + text += f.getvalue() + + return text + # see /doc/sw_pll.rst for guidance on these settings def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min_F=200, ppm_max=2, fracmin=0.65, fracmax=0.95): """ diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index a0991d2c..a8363560 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -6,6 +6,7 @@ import numpy as np import os import re +from pathlib import Path """ This file contains implementations of digitally controlled oscillators. @@ -262,6 +263,7 @@ def __init__(self, profile): "24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}} + self.profile = profile self.p_value = 8 # 8 frac settings + 1 non frac setting input_freq, F, R, f, p, OD, ACD = list(profiles[profile].values()) @@ -333,6 +335,12 @@ def plot_freq_range(self): # plt.show() plt.savefig("sdm_dco_range.png", dpi=150) + def write_register_file(self): + with open(register_file, "w") as reg_vals: + reg_vals.write(f"/* Autogenerated SDM App PLL setup by {Path(__file__).name} using {self.profile} profile */\n") + reg_vals.write(self.app_pll.gen_register_file_text()) + reg_vals.write("\n\n") + if __name__ == '__main__': """ @@ -345,6 +353,7 @@ def plot_freq_range(self): # dco.print_stats(12288000) sdm_dco = sigma_delta_dco("24.576_1M") + sdm_dco.write_register_file() sdm_dco.print_stats(24576000) sdm_dco.plot_freq_range() for i in range(30): diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 868de600..57f0de8a 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -134,7 +134,7 @@ def __init__( self, self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) self.controller = sdm_pi_ctrl(Kp, Ki, Kii) - self.dco = sigma_delta_dco("24.576") + self.dco = sigma_delta_dco("24.576_1M") self.target_output_frequency = target_output_frequency self.time = 0.0 From 1f0289ff3921032182de85b66b662171006bc0ee Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 15 Nov 2023 15:27:55 +0000 Subject: [PATCH 030/118] Initial copy of simple example and separate out pfd --- examples/examples.cmake | 1 + examples/simple_sdm/simple_sdm.cmake | 48 +++++ examples/simple_sdm/src/config.xscope | 24 +++ examples/simple_sdm/src/main.xc | 22 +++ examples/simple_sdm/src/simple_sw_pll_sdm.c | 166 ++++++++++++++++++ lib_sw_pll/src/sw_pll_pfd.h | 40 +++++ lib_sw_pll/src/sw_pll_sdm.c | 185 ++++++++++++++++++++ 7 files changed, 486 insertions(+) create mode 100644 examples/simple_sdm/simple_sdm.cmake create mode 100644 examples/simple_sdm/src/config.xscope create mode 100644 examples/simple_sdm/src/main.xc create mode 100644 examples/simple_sdm/src/simple_sw_pll_sdm.c create mode 100644 lib_sw_pll/src/sw_pll_pfd.h create mode 100644 lib_sw_pll/src/sw_pll_sdm.c diff --git a/examples/examples.cmake b/examples/examples.cmake index 4c8785d3..7f868b09 100644 --- a/examples/examples.cmake +++ b/examples/examples.cmake @@ -1,2 +1,3 @@ include(${CMAKE_CURRENT_LIST_DIR}/simple/simple.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/simple_sdm/simple_sdm.cmake) include(${CMAKE_CURRENT_LIST_DIR}/i2s_slave/i2s_slave.cmake) diff --git a/examples/simple_sdm/simple_sdm.cmake b/examples/simple_sdm/simple_sdm.cmake new file mode 100644 index 00000000..e9bf329a --- /dev/null +++ b/examples/simple_sdm/simple_sdm.cmake @@ -0,0 +1,48 @@ +#********************** +# Gather Sources +#********************** +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/src/*.xc) +set(APP_INCLUDES + ${CMAKE_CURRENT_LIST_DIR}/src +) + +#********************** +# Flags +#********************** +set(APP_COMPILER_FLAGS + -Os + -g + -report + -fxscope + -mcmodel=large + -Wno-xcore-fptrgroup + ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope + -target=XCORE-AI-EXPLORER +) + +set(APP_COMPILE_DEFINITIONS + DEBUG_PRINT_ENABLE=1 + PLATFORM_SUPPORTS_TILE_0=1 + PLATFORM_SUPPORTS_TILE_1=1 + PLATFORM_SUPPORTS_TILE_2=0 + PLATFORM_SUPPORTS_TILE_3=0 + PLATFORM_USES_TILE_0=1 + PLATFORM_USES_TILE_1=1 +) + +set(APP_LINK_OPTIONS + -report + -target=XCORE-AI-EXPLORER + ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope +) + +#********************** +# Tile Targets +#********************** +add_executable(simple_sdm) +target_sources(simple_sdm PUBLIC ${APP_SOURCES}) +target_include_directories(simple_sdm PUBLIC ${APP_INCLUDES}) +target_compile_definitions(simple_sdm PRIVATE ${APP_COMPILE_DEFINITIONS}) +target_compile_options(simple_sdm PRIVATE ${APP_COMPILER_FLAGS}) +target_link_options(simple_sdm PRIVATE ${APP_LINK_OPTIONS}) +target_link_libraries(simple_sdm PUBLIC lib_sw_pll) diff --git a/examples/simple_sdm/src/config.xscope b/examples/simple_sdm/src/config.xscope new file mode 100644 index 00000000..8ddc37b6 --- /dev/null +++ b/examples/simple_sdm/src/config.xscope @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/simple_sdm/src/main.xc b/examples/simple_sdm/src/main.xc new file mode 100644 index 00000000..5e64b6dc --- /dev/null +++ b/examples/simple_sdm/src/main.xc @@ -0,0 +1,22 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +extern void sw_pll_sdm_test(void); +extern void clock_gen(void); + +int main(void) +{ + par + { + on tile[0]: par { + } + on tile[1]: par { + sw_pll_sdm_test(); + clock_gen(); + } + } + return 0; +} diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c new file mode 100644 index 00000000..b1d9a6be --- /dev/null +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -0,0 +1,166 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include + +#include "sw_pll.h" + +#define MCLK_FREQUENCY 12288000 +#define REF_FREQUENCY 48000 +#define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY) +#define CONTROL_LOOP_COUNT 512 +#define PPM_RANGE 150 + +#define APP_PLL_CTL_12288 0x0881FA03 +#define APP_PLL_DIV_12288 0x8000001E +#define APP_PLL_NOMINAL_INDEX_12288 35 + +//Found solution: IN 24.000MHz, OUT 12.288018MHz, VCO 3047.43MHz, RD 4, FD 507.905 (m = 19, n = 21), OD 2, FOD 31, ERR +1.50ppm +#include "fractions.h" + +void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) +{ + // Create clock from mclk port and use it to clock the p_ref_clk port. + clock_enable(clk_mclk); + port_enable(p_mclk); + clock_set_source_port(clk_mclk, p_mclk); + + // Clock p_ref_clk from MCLK + port_enable(p_ref_clk_in); + port_set_clock(p_ref_clk_in, clk_mclk); + + clock_start(clk_mclk); + + // Create clock from ref_clock_port and use it to clock the p_ref_clk_count port. + clock_enable(clk_word_clk); + clock_set_source_port(clk_word_clk, p_ref_clk_in); + port_enable(p_ref_clk_count); + port_set_clock(p_ref_clk_count, clk_word_clk); + + clock_start(clk_word_clk); +} + + +void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider) +{ + // Connect clock block with divide to mclk + clock_enable(clk_recovered_ref_clk); + clock_set_source_port(clk_recovered_ref_clk, p_mclk); + clock_set_divide(clk_recovered_ref_clk, divider / 2); + printf("Divider: %u\n", divider); + + // Output the divided mclk on a port + port_enable(p_recovered_ref_clk); + port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk); + port_set_out_clock(p_recovered_ref_clk); + clock_start(clk_recovered_ref_clk); +} + +void sw_pll_sdm_test(void){ + + // Declare mclk and refclk resources and connect up + port_t p_mclk = PORT_MCLK_IN; + xclock_t clk_mclk = XS1_CLKBLK_1; + port_t p_ref_clk = PORT_I2S_LRCLK; + xclock_t clk_word_clk = XS1_CLKBLK_2; + port_t p_ref_clk_count = XS1_PORT_32A; + setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_ref_clk, clk_word_clk, p_ref_clk_count); + + // Make a test output to observe the recovered mclk divided down to the refclk frequency + xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3; + port_t p_recovered_ref_clk = PORT_I2S_DAC_DATA; + setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO); + + sw_pll_state_t sw_pll; + sw_pll_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + CONTROL_LOOP_COUNT, + PLL_RATIO, + 0, + frac_values_80, + SW_PLL_NUM_LUT_ENTRIES(frac_values_80), + APP_PLL_CTL_12288, + APP_PLL_DIV_12288, + APP_PLL_NOMINAL_INDEX_12288, + PPM_RANGE); + + sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; + + uint32_t max_time = 0; + while(1) + { + port_in(p_ref_clk_count); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time. + uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. + + uint32_t t0 = get_reference_time(); + sw_pll_do_control(&sw_pll, mclk_pt, 0); + uint32_t t1 = get_reference_time(); + if(t1 - t0 > max_time){ + max_time = t1 - t0; + printf("Max ticks taken: %lu\n", max_time); + } + + if(sw_pll.lock_status != lock_status){ + lock_status = sw_pll.lock_status; + const char msg[3][16] = {"UNLOCKED LOW\0", "LOCKED\0", "UNLOCKED HIGH\0"}; + printf("%s\n", msg[lock_status+1]); + } + } +} + +#define DO_CLOCKS \ +printf("Ref Hz: %d\n", clock_rate >> 1); \ +\ +unsigned cycle_ticks_int = XS1_TIMER_HZ / clock_rate; \ +unsigned cycle_ticks_remaidner = XS1_TIMER_HZ % clock_rate; \ +unsigned carry = 0; \ +\ +period_trig += XS1_TIMER_HZ * 1; \ +unsigned time_now = hwtimer_get_time(period_tmr); \ +while(TIMER_TIMEAFTER(period_trig, time_now)) \ +{ \ + port_out(p_clock_gen, port_val); \ + hwtimer_wait_until(clock_tmr, time_trig); \ + time_trig += cycle_ticks_int; \ + carry += cycle_ticks_remaidner; \ + if(carry >= clock_rate){ \ + time_trig++; \ + carry -= clock_rate; \ + } \ + port_val ^= 1; \ + time_now = hwtimer_get_time(period_tmr); \ +} + +void clock_gen(void) +{ + unsigned clock_rate = REF_FREQUENCY * 2; // Note double because we generate edges at this rate + unsigned ppm_range = 150; // Step from - to + this + + unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0)); + unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0)); + unsigned step_size = (clock_rate_high - clock_rate_low) / 20; + + printf("Sweep range: %d %d %d, step size: %d\n", clock_rate_low / 2, clock_rate / 2, clock_rate_high / 2, step_size); + + hwtimer_t period_tmr = hwtimer_alloc(); + unsigned period_trig = hwtimer_get_time(period_tmr); + + hwtimer_t clock_tmr = hwtimer_alloc(); + unsigned time_trig = hwtimer_get_time(clock_tmr); + + port_t p_clock_gen = PORT_I2S_BCLK; + port_enable(p_clock_gen); + unsigned port_val = 1; + + for(unsigned clock_rate = clock_rate_low; clock_rate <= clock_rate_high; clock_rate += 2 * step_size){ + DO_CLOCKS + } + for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){ + DO_CLOCKS + } + exit(0); +} \ No newline at end of file diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h new file mode 100644 index 00000000..ed15d67c --- /dev/null +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -0,0 +1,40 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "sw_pll.h" + +__attribute__((always_inline)) +static inline void sw_pll_calc_error_from_port_timers(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +{ + uint16_t mclk_expected_pt = 0; + // See if we are using variable loop period sampling, if so, compensate for it by scaling the expected mclk count + if(sw_pll->ref_clk_expected_inc) + { + uint16_t ref_clk_expected_pt = sw_pll->ref_clk_pt_last + sw_pll->ref_clk_expected_inc; + // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 + int16_t ref_clk_diff = PORT_TIMEAFTER(ref_clk_pt, ref_clk_expected_pt) ? -(int16_t)(ref_clk_expected_pt - ref_clk_pt) : (int16_t)(ref_clk_pt - ref_clk_expected_pt); + sw_pll->ref_clk_pt_last = ref_clk_pt; + + // This allows for wrapping of the timer when CONTROL_LOOP_COUNT is high + // Note we use a pre-computed divide followed by a shift to replace a constant divide with a constant multiply + shift + uint32_t mclk_expected_pt_inc = ((uint64_t)sw_pll->mclk_expected_pt_inc + * ((uint64_t)sw_pll->ref_clk_expected_inc + ref_clk_diff) + * sw_pll->ref_clk_scaling_numerator) >> SW_PLL_PRE_DIV_BITS; + // Below is the line we would use if we do not pre-compute the divide. This can take a long time if we spill over 32b + // uint32_t mclk_expected_pt_inc = sw_pll->mclk_expected_pt_inc * (sw_pll->ref_clk_expected_inc + ref_clk_diff) / sw_pll->ref_clk_expected_inc; + mclk_expected_pt = sw_pll->mclk_pt_last + mclk_expected_pt_inc; + } + else // we are assuming mclk_pt is sampled precisely and needs no compoensation + { + mclk_expected_pt = sw_pll->mclk_pt_last + sw_pll->mclk_expected_pt_inc; + } + + // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 + sw_pll->mclk_diff = PORT_TIMEAFTER(mclk_pt, mclk_expected_pt) ? -(int16_t)(mclk_expected_pt - mclk_pt) : (int16_t)(mclk_pt - mclk_expected_pt); + + // Check to see if something has gone very wrong, for example ref clock stop/start. If so, reset state and keep trying + if(MAGNITUDE(sw_pll->mclk_diff) > sw_pll->mclk_max_diff) + { + sw_pll->first_loop = 1; + } +} \ No newline at end of file diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c new file mode 100644 index 00000000..9a2a4070 --- /dev/null +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -0,0 +1,185 @@ +// Copyright 2022-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "sw_pll.h" +#include "sw_pll_pfd.h" +#include + +#define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked +#define SW_PLL_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max + +// Implement a delay in 100MHz timer ticks without using a timer resource +static void blocking_delay(const uint32_t delay_ticks){ + uint32_t time_delay = get_reference_time() + delay_ticks; + while(TIMER_TIMEAFTER(time_delay, get_reference_time())); +} + + +// Set secondary (App) PLL control register safely to work around chip bug. +static void sw_pll_app_pll_init(const unsigned tileid, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint16_t frac_val_nominal) +{ + // Disable the PLL + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF)); + // Enable the PLL to invoke a reset on the appPLL. + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); + // Must write the CTL register twice so that the F and R divider values are captured using a running clock. + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); + // Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values. + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF)); + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); + + // Write the fractional-n register and set to nominal + // We set the top bit to enable the frac-n block. + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | frac_val_nominal)); + // And then write the clock divider register to enable the output + write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, app_pll_div_reg_val); + + // Wait for PLL to lock. + blocking_delay(10 * XS1_TIMER_KHZ); +} + +__attribute__((always_inline)) +static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int32_t total_error) +{ + const int set = (sw_pll->nominal_lut_idx - total_error); //Notice negative term for error + unsigned int frac_index = 0; + + if (set < 0) + { + frac_index = 0; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; + } + else if (set >= sw_pll->num_lut_entries) + { + frac_index = sw_pll->num_lut_entries - 1; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH; + } + else + { + frac_index = set; + if(sw_pll->lock_counter){ + sw_pll->lock_counter--; + // Keep last unlocked status + } + else + { + sw_pll->lock_status = SW_PLL_LOCKED; + } + } + + return sw_pll->lut_table_base[frac_index]; +} + + +void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, + const sw_pll_15q16_t Kp, + const sw_pll_15q16_t Ki, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const int16_t * const lut_table_base, + const size_t num_lut_entries, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const unsigned nominal_lut_idx, + const unsigned ppm_range) +{ + // Get PLL started and running at nominal + sw_pll_app_pll_init(get_local_tile_id(), + app_pll_ctl_reg_val, + app_pll_div_reg_val, + lut_table_base[nominal_lut_idx]); + + // Setup sw_pll with supplied user paramaters + sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); + + sw_pll->loop_rate_count = loop_rate_count; + sw_pll->current_reg_val = app_pll_div_reg_val; + + // Setup LUT params + sw_pll->lut_table_base = lut_table_base; + sw_pll->num_lut_entries = num_lut_entries; + sw_pll->nominal_lut_idx = nominal_lut_idx; + + // Setup general state + sw_pll->mclk_diff = 0; + sw_pll->ref_clk_pt_last = 0; + sw_pll->ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; + if(sw_pll->ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used + { + sw_pll->ref_clk_scaling_numerator = (1ULL << SW_PLL_PRE_DIV_BITS) / sw_pll->ref_clk_expected_inc + 1; //+1 helps with rounding accuracy + } + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + sw_pll->mclk_pt_last = 0; + sw_pll->mclk_expected_pt_inc = loop_rate_count * pll_ratio; + // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. + sw_pll->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + sw_pll->loop_counter = 0; + sw_pll->first_loop = 1; + + // Check we can actually support the numbers used in the maths we use + const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX + const float max = (float)sw_pll->ref_clk_expected_inc + * (float)sw_pll->ref_clk_scaling_numerator + * (float)sw_pll->mclk_expected_pt_inc; + // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency + xassert(max < calc_max); +} + + +__attribute__((always_inline)) +inline sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) +{ + sw_pll->error_accum += error; // Integral error. + sw_pll->error_accum = sw_pll->error_accum > sw_pll->i_windup_limit ? sw_pll->i_windup_limit : sw_pll->error_accum; + sw_pll->error_accum = sw_pll->error_accum < -sw_pll->i_windup_limit ? -sw_pll->i_windup_limit : sw_pll->error_accum; + + // Use long long maths to avoid overflow if ever we had a large error accum term + int64_t error_p = ((int64_t)sw_pll->Kp * (int64_t)error); + int64_t error_i = ((int64_t)sw_pll->Ki * (int64_t)sw_pll->error_accum); + + // Convert back to 32b since we are handling LUTs of around a hundred entries + int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); + sw_pll->current_reg_val = lookup_pll_frac(sw_pll, total_error); + + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->current_reg_val)); + + return sw_pll->lock_status; +} + +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +{ + if (++sw_pll->loop_counter == sw_pll->loop_rate_count) + { + sw_pll->loop_counter = 0; + + if (sw_pll->first_loop) // First loop around so ensure state is clear + { + sw_pll->mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data + sw_pll->error_accum = 0; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; + + sw_pll->first_loop = 0; + + // Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in table) + } + else + { + sw_pll_calc_error_from_port_timers(sw_pll, mclk_pt, ref_clk_pt); + sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->mclk_diff); + + // Save for next iteration to calc diff + sw_pll->mclk_pt_last = mclk_pt; + + } + } + + return sw_pll->lock_status; +} From 4eafeebd1296a41e5c7b7820b36e922ceaa55ab8 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 15 Nov 2023 18:35:30 +0000 Subject: [PATCH 031/118] WIP SDM C --- examples/simple_sdm/src/main.xc | 23 ++-- examples/simple_sdm/src/simple_sw_pll_sdm.c | 132 +++++++++++++++++--- lib_sw_pll/api/sw_pll.h | 20 +++ lib_sw_pll/src/sw_pll.c | 8 +- lib_sw_pll/src/sw_pll_pfd.h | 3 + lib_sw_pll/src/sw_pll_sdm.c | 41 +----- python/sw_pll/app_pll_model.py | 2 +- 7 files changed, 165 insertions(+), 64 deletions(-) diff --git a/examples/simple_sdm/src/main.xc b/examples/simple_sdm/src/main.xc index 5e64b6dc..6973b269 100644 --- a/examples/simple_sdm/src/main.xc +++ b/examples/simple_sdm/src/main.xc @@ -4,19 +4,24 @@ #include #include -extern void sw_pll_sdm_test(void); +extern void sw_pll_sdm_test(chanend c_sdm_control); +extern void sdm_task(chanend c_sdm_control); extern void clock_gen(void); int main(void) { - par - { - on tile[0]: par { - } - on tile[1]: par { - sw_pll_sdm_test(); - clock_gen(); + chan c_sdm_control; + + par + { + on tile[0]: par { + } + + on tile[1]: par { + sw_pll_sdm_test(c_sdm_control); + sdm_task(c_sdm_control); + clock_gen(); + } } - } return 0; } diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index b1d9a6be..435b645b 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -2,24 +2,26 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include +#include #include #include #include +#include + +#include #include "sw_pll.h" -#define MCLK_FREQUENCY 12288000 +#define MCLK_FREQUENCY 24576000 #define REF_FREQUENCY 48000 #define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY) #define CONTROL_LOOP_COUNT 512 -#define PPM_RANGE 150 +#define PPM_RANGE 150 //TODO eliminate -#define APP_PLL_CTL_12288 0x0881FA03 -#define APP_PLL_DIV_12288 0x8000001E -#define APP_PLL_NOMINAL_INDEX_12288 35 +#include "register_setup.h" +#define APP_PLL_NOMINAL_INDEX_12288 35 //TODO eliminate -//Found solution: IN 24.000MHz, OUT 12.288018MHz, VCO 3047.43MHz, RD 4, FD 507.905 (m = 19, n = 21), OD 2, FOD 31, ERR +1.50ppm -#include "fractions.h" +typedef int tileref_t; void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) { @@ -59,7 +61,109 @@ void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_r clock_start(clk_recovered_ref_clk); } -void sw_pll_sdm_test(void){ +typedef struct sdm_state_t{ + int32_t ds_x1; + int32_t ds_x2; + int32_t ds_x3; +}sdm_state_t; + + +void init_sigma_delta(sdm_state_t *sdm_state){ + sdm_state->ds_x1 = 0; + sdm_state->ds_x2 = 0; + sdm_state->ds_x3 = 0; +} + +__attribute__((always_inline)) +static inline int32_t do_sigma_delta(sdm_state_t *sdm_state, int32_t ds_in){ + // Third order, 9 level output delta sigma. 20 bit unsigned input. + int32_t ds_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; + if (ds_out > 8){ + ds_out = 8; + } + if (ds_out < 0){ + ds_out = 0; + } + sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (ds_out<<9) - (ds_out<<8); + sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (ds_out<<14); + sdm_state->ds_x1 += ds_in - (ds_out<<17); + + return ds_out; +} + +__attribute__((always_inline)) +static inline uint32_t ds_out_to_frac_reg(int32_t ds_out){ + // bit 31 is frac enable + // bits 15..8 are the f value + // bits 7..0 are the p value + // Freq - F + (f + 1)/(p + 1) + uint32_t frac_val = 0; + + if (ds_out == 0){ + frac_val = 0x00000007; // step 0/8 + } + else{ + frac_val = ((ds_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8 + } + + return frac_val; +} + +__attribute__((always_inline)) +static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ + write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); +} + +void sdm_task(chanend_t c_sdm_control){ + printf("sdm_task\n"); + + const uint32_t sdm_interval = 100; + + sdm_state_t sdm_state; + init_sigma_delta(&sdm_state); + + tileref_t this_tile = get_local_tile_id(); + + hwtimer_t tmr = hwtimer_alloc(); + int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval; + bool running = true; + int32_t ds_in = 666666; + + while(running){ + // Poll for new SDM control value + SELECT_RES( + CASE_THEN(c_sdm_control, ctrl_update), + DEFAULT_THEN(default_handler) + ) + { + ctrl_update: + { + ds_in = chan_in_word(c_sdm_control); + } + break; + + default_handler: + { + // Do nothing & fall-through + } + break; + } + + // calc new ds_out and then wait to write + int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); + uint32_t frac_val = ds_out_to_frac_reg(ds_out); + + hwtimer_wait_until(tmr, trigger_time); + trigger_time += sdm_interval; + write_frac_reg(this_tile, frac_val); + + static int cnt = 0; + if (cnt % 1000000 == 0) printintln(cnt); + cnt++; + } +} + +void sw_pll_sdm_test(chanend_t c_sdm_control){ // Declare mclk and refclk resources and connect up port_t p_mclk = PORT_MCLK_IN; @@ -75,17 +179,15 @@ void sw_pll_sdm_test(void){ setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO); sw_pll_state_t sw_pll; - sw_pll_init(&sw_pll, + sw_pll_sdm_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), CONTROL_LOOP_COUNT, PLL_RATIO, 0, - frac_values_80, - SW_PLL_NUM_LUT_ENTRIES(frac_values_80), - APP_PLL_CTL_12288, - APP_PLL_DIV_12288, - APP_PLL_NOMINAL_INDEX_12288, + APP_PLL_CTL_REG, + APP_PLL_DIV_REG, + APP_PLL_FRAC_REG, PPM_RANGE); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; @@ -97,7 +199,7 @@ void sw_pll_sdm_test(void){ uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. uint32_t t0 = get_reference_time(); - sw_pll_do_control(&sw_pll, mclk_pt, 0); + sw_pll_sdm_do_control(&sw_pll, mclk_pt, 0); uint32_t t1 = get_reference_time(); if(t1 - t0 > max_time){ max_time = t1 - t0; diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index ea3e646a..a3744683 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -9,6 +9,7 @@ #include #include #include +#include // Helpers used in this module #define TIMER_TIMEAFTER(A, B) ((int)((B) - (A)) < 0) // Returns non-zero if A is after B, accounting for wrap @@ -169,3 +170,22 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl sw_pll->i_windup_limit = 0; } } + +///////// SDM WORK IN PROGRESS ///////// + +void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, + const sw_pll_15q16_t Kp, + const sw_pll_15q16_t Ki, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint32_t app_pll_frac_reg_val, + const unsigned ppm_range); +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); +sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); +void sw_pll_app_pll_init(const unsigned tileid, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint16_t frac_val_nominal); //TODO hide me diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index f4ef7f34..e72f7239 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -15,10 +15,10 @@ static void blocking_delay(const uint32_t delay_ticks){ // Set secondary (App) PLL control register safely to work around chip bug. -static void sw_pll_app_pll_init(const unsigned tileid, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const uint16_t frac_val_nominal) +void sw_pll_app_pll_init(const unsigned tileid, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint16_t frac_val_nominal) { // Disable the PLL write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF)); diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h index ed15d67c..f6d95cfa 100644 --- a/lib_sw_pll/src/sw_pll_pfd.h +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -3,6 +3,9 @@ #include "sw_pll.h" +#define SW_PLL_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max + + __attribute__((always_inline)) static inline void sw_pll_calc_error_from_port_timers(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 9a2a4070..4ba2335a 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -6,7 +6,6 @@ #include #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked -#define SW_PLL_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max // Implement a delay in 100MHz timer ticks without using a timer resource static void blocking_delay(const uint32_t delay_ticks){ @@ -15,32 +14,6 @@ static void blocking_delay(const uint32_t delay_ticks){ } -// Set secondary (App) PLL control register safely to work around chip bug. -static void sw_pll_app_pll_init(const unsigned tileid, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const uint16_t frac_val_nominal) -{ - // Disable the PLL - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF)); - // Enable the PLL to invoke a reset on the appPLL. - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); - // Must write the CTL register twice so that the F and R divider values are captured using a running clock. - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); - // Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values. - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (app_pll_ctl_reg_val & 0xF7FFFFFF)); - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_CTL_NUM, app_pll_ctl_reg_val); - - // Write the fractional-n register and set to nominal - // We set the top bit to enable the frac-n block. - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | frac_val_nominal)); - // And then write the clock divider register to enable the output - write_sswitch_reg(tileid, XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, app_pll_div_reg_val); - - // Wait for PLL to lock. - blocking_delay(10 * XS1_TIMER_KHZ); -} - __attribute__((always_inline)) static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int32_t total_error) { @@ -82,29 +55,27 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, - const int16_t * const lut_table_base, - const size_t num_lut_entries, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, - const unsigned nominal_lut_idx, + const uint32_t app_pll_frac_reg_val, const unsigned ppm_range) { // Get PLL started and running at nominal sw_pll_app_pll_init(get_local_tile_id(), app_pll_ctl_reg_val, app_pll_div_reg_val, - lut_table_base[nominal_lut_idx]); + (uint16_t)(app_pll_frac_reg_val & 0xffff)); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); + sw_pll_reset(sw_pll, Kp, Ki, 0); // TODO work out windup limit sw_pll->loop_rate_count = loop_rate_count; sw_pll->current_reg_val = app_pll_div_reg_val; // Setup LUT params - sw_pll->lut_table_base = lut_table_base; - sw_pll->num_lut_entries = num_lut_entries; - sw_pll->nominal_lut_idx = nominal_lut_idx; + // sw_pll->lut_table_base = lut_table_base; + // sw_pll->num_lut_entries = num_lut_entries; + // sw_pll->nominal_lut_idx = nominal_lut_idx; // Setup general state sw_pll->mclk_diff = 0; diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index c0506d42..413741d0 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -118,7 +118,7 @@ class args: with redirect_stdout(f): # in pll_calc, op_div = OD, fb_div = F, f, p, ref_div = R, fin_op_div = ACD print_regs(args, self.OD + 1, [self.F + 1, self.f + 1, self.p + 1] , self.R + 1, self.ACD + 1) - text += f.getvalue() + text += f.getvalue().replace(" ", "_").replace("REG_0x", "REG 0x").replace("APP_PLL", "#define APP_PLL") return text From e0df4c10f2a03888f768f7a5912bec246c08ae02 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 10:44:10 +0000 Subject: [PATCH 032/118] Very basic working SDM loop --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 12 +++++++--- lib_sw_pll/api/sw_pll.h | 5 ++-- lib_sw_pll/src/sw_pll_sdm.c | 26 +++++++++++++-------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 435b645b..c6dc1a10 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -163,6 +163,10 @@ void sdm_task(chanend_t c_sdm_control){ } } +void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ + chan_out_word(c_sdm_control, dco_ctl); +} + void sw_pll_sdm_test(chanend_t c_sdm_control){ // Declare mclk and refclk resources and connect up @@ -176,12 +180,12 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ // Make a test output to observe the recovered mclk divided down to the refclk frequency xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3; port_t p_recovered_ref_clk = PORT_I2S_DAC_DATA; - setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO); + setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO / 2); // TODO fix me /2 sw_pll_state_t sw_pll; sw_pll_sdm_init(&sw_pll, SW_PLL_15Q16(0.0), - SW_PLL_15Q16(1.0), + SW_PLL_15Q16(32.0), CONTROL_LOOP_COUNT, PLL_RATIO, 0, @@ -199,7 +203,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. uint32_t t0 = get_reference_time(); - sw_pll_sdm_do_control(&sw_pll, mclk_pt, 0); + sw_pll_sdm_do_control(&sw_pll, c_sdm_control, mclk_pt, 0); uint32_t t1 = get_reference_time(); if(t1 - t0 > max_time){ max_time = t1 - t0; @@ -214,6 +218,8 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ } } + + #define DO_CLOCKS \ printf("Ref Hz: %d\n", clock_rate >> 1); \ \ diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index a3744683..f396f43c 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -183,9 +183,10 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, const uint32_t app_pll_div_reg_val, const uint32_t app_pll_frac_reg_val, const unsigned ppm_range); -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); -sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_pt); +int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); void sw_pll_app_pll_init(const unsigned tileid, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, const uint16_t frac_val_nominal); //TODO hide me +void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 4ba2335a..d8b16a30 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -67,7 +67,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, (uint16_t)(app_pll_frac_reg_val & 0xffff)); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, 0); // TODO work out windup limit + sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 sw_pll->loop_rate_count = loop_rate_count; sw_pll->current_reg_val = app_pll_div_reg_val; @@ -90,7 +90,9 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, sw_pll->mclk_pt_last = 0; sw_pll->mclk_expected_pt_inc = loop_rate_count * pll_ratio; // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. - sw_pll->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + sw_pll->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + sw_pll->mclk_max_diff = 1000; + printf("mclk_max_diff: %u\n",sw_pll->mclk_max_diff); sw_pll->loop_counter = 0; sw_pll->first_loop = 1; @@ -105,7 +107,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, __attribute__((always_inline)) -inline sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) +inline int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { sw_pll->error_accum += error; // Integral error. sw_pll->error_accum = sw_pll->error_accum > sw_pll->i_windup_limit ? sw_pll->i_windup_limit : sw_pll->error_accum; @@ -117,14 +119,11 @@ inline sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * co // Convert back to 32b since we are handling LUTs of around a hundred entries int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); - sw_pll->current_reg_val = lookup_pll_frac(sw_pll, total_error); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->current_reg_val)); - - return sw_pll->lock_status; + return total_error; } -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { if (++sw_pll->loop_counter == sw_pll->loop_rate_count) { @@ -139,12 +138,16 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const sw_pll->first_loop = 0; - // Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in table) + // Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in settings) } else { sw_pll_calc_error_from_port_timers(sw_pll, mclk_pt, ref_clk_pt); - sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->mclk_diff); + int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->mclk_diff); + printintln(sw_pll->mclk_diff); + printintln(error); + int dco_ctl = 478151 - error; + sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); // Save for next iteration to calc diff sw_pll->mclk_pt_last = mclk_pt; @@ -152,5 +155,8 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const } } + // printchar('+'); + + return sw_pll->lock_status; } From 828d6fbaf7826d89313a6e48c6a603d5c164bacb Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 11:12:33 +0000 Subject: [PATCH 033/118] Add double integral term --- CHANGELOG.rst | 5 +++++ doc/settings.json | 2 +- examples/i2s_slave/src/i2s_slave_sw_pll.c | 1 + examples/simple/src/simple_sw_pll.c | 1 + lib_sw_pll/api/sw_pll.h | 15 ++++++++++++++- lib_sw_pll/src/sw_pll.c | 9 +++++++-- tests/test_app/main.c | 3 +++ tests/test_app_low_level_api/main.c | 3 +++ 8 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 792d01b3..dd2edff0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ lib_sw_pll library change log ============================= +2.0.0 +----- + + * ADDED: Double integral term to controller + 1.1.0 ----- diff --git a/doc/settings.json b/doc/settings.json index 28c2bf8d..08890775 100644 --- a/doc/settings.json +++ b/doc/settings.json @@ -1,7 +1,7 @@ { "title": "XMOS SW PLL", "project": "SW PLL", - "version": "1.1.0", + "version": "2.0.0", "architectures": { "xcore.ai": ["15.2.x"] } diff --git a/examples/i2s_slave/src/i2s_slave_sw_pll.c b/examples/i2s_slave/src/i2s_slave_sw_pll.c index 5dc5627d..291a87f5 100644 --- a/examples/i2s_slave/src/i2s_slave_sw_pll.c +++ b/examples/i2s_slave/src/i2s_slave_sw_pll.c @@ -178,6 +178,7 @@ void sw_pll_test(void){ sw_pll_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), CONTROL_LOOP_COUNT, PLL_RATIO, BCLKS_PER_LRCLK, diff --git a/examples/simple/src/simple_sw_pll.c b/examples/simple/src/simple_sw_pll.c index dbcc4b84..766ce5f7 100644 --- a/examples/simple/src/simple_sw_pll.c +++ b/examples/simple/src/simple_sw_pll.c @@ -78,6 +78,7 @@ void sw_pll_test(void){ sw_pll_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), CONTROL_LOOP_COUNT, PLL_RATIO, 0, diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index ea3e646a..19ba770d 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -38,7 +38,9 @@ typedef struct sw_pll_state_t{ // User definied paramaters sw_pll_15q16_t Kp; // Proportional constant sw_pll_15q16_t Ki; // Integral constant + sw_pll_15q16_t Kii; // Double ntegral constant int32_t i_windup_limit; // Integral term windup limit + int32_t ii_windup_limit; // Double integral term windup limit unsigned loop_rate_count; // How often the control loop logic runs compared to control cal rate // Internal state @@ -47,6 +49,7 @@ typedef struct sw_pll_state_t{ uint32_t ref_clk_expected_inc; // Expected ref clock increment uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) + int32_t error_accum_accum; // Accumulation of the accumulation term (for II) unsigned loop_counter; // Intenal loop counter to determine when to do control uint16_t mclk_pt_last; // The last mclk port timer count uint32_t mclk_expected_pt_inc; // Expected increment of port timer count @@ -72,6 +75,7 @@ typedef struct sw_pll_state_t{ * \param \c sw_pll Pointer to the struct to be initialised. * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. + * \param \c Kii Double Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c loop_rate_count How many counts of the call to sw_pll_do_control before control is done. * Note this is only used by \c sw_pll_do_control. \c sw_pll_do_control_from_error * calls the control loop every time so this is ignored. @@ -94,6 +98,7 @@ typedef struct sw_pll_state_t{ void sw_pll_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, @@ -156,9 +161,10 @@ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, * \param \c sw_pll Pointer to the struct to be initialised. * \param \c Kp New Kp in \c sw_pll_15q16_t format. * \param \c Ki New Ki in \c sw_pll_15q16_t format. + * \param \c Kii New Kii in \c sw_pll_15q16_t format. * \param \c num_lut_entries The number of elements in the sw_pll LUT. */ -static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, size_t num_lut_entries) +static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) { sw_pll->Kp = Kp; sw_pll->Ki = Ki; @@ -168,4 +174,11 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl }else{ sw_pll->i_windup_limit = 0; } + sw_pll->error_accum_accum = 0; + if(Ki){ + sw_pll->i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT + }else{ + sw_pll->i_windup_limit = 0; + } + } diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index f4ef7f34..5bd860c0 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -78,6 +78,7 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 void sw_pll_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, @@ -95,7 +96,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, lut_table_base[nominal_lut_idx]); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); + sw_pll_reset(sw_pll, Kp, Ki, Kii, num_lut_entries); sw_pll->loop_rate_count = loop_rate_count; sw_pll->current_reg_val = app_pll_div_reg_val; @@ -171,15 +172,19 @@ __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { sw_pll->error_accum += error; // Integral error. + sw_pll->error_accum_accum += sw_pll->error_accum; // Double integral error. sw_pll->error_accum = sw_pll->error_accum > sw_pll->i_windup_limit ? sw_pll->i_windup_limit : sw_pll->error_accum; sw_pll->error_accum = sw_pll->error_accum < -sw_pll->i_windup_limit ? -sw_pll->i_windup_limit : sw_pll->error_accum; + sw_pll->error_accum_accum = sw_pll->error_accum_accum > sw_pll->ii_windup_limit ? sw_pll->ii_windup_limit : sw_pll->error_accum_accum; + sw_pll->error_accum_accum = sw_pll->error_accum_accum < -sw_pll->ii_windup_limit ? -sw_pll->ii_windup_limit : sw_pll->error_accum_accum; // Use long long maths to avoid overflow if ever we had a large error accum term int64_t error_p = ((int64_t)sw_pll->Kp * (int64_t)error); int64_t error_i = ((int64_t)sw_pll->Ki * (int64_t)sw_pll->error_accum); + int64_t error_ii = ((int64_t)sw_pll->Kii * (int64_t)sw_pll->error_accum_accum); // Convert back to 32b since we are handling LUTs of around a hundred entries - int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); + int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS); sw_pll->current_reg_val = lookup_pll_frac(sw_pll, total_error); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->current_reg_val)); diff --git a/tests/test_app/main.c b/tests/test_app/main.c index b10ee53e..7686ae61 100644 --- a/tests/test_app/main.c +++ b/tests/test_app/main.c @@ -50,6 +50,8 @@ int main(int argc, char** argv) { unsigned target_output_frequency = atoi(argv[i++]); fprintf(stderr, "target_output_frequency\t\t%d\n", target_output_frequency); + float kii = 0.0; + if(i + num_lut_entries != argc) { fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); return 1; @@ -67,6 +69,7 @@ int main(int argc, char** argv) { sw_pll_init( &sw_pll, SW_PLL_15Q16(kp), SW_PLL_15Q16(ki), + SW_PLL_15Q16(kii), loop_rate_count, pll_ratio, ref_clk_expected_inc, diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index 447859ec..438eb5e8 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -50,6 +50,8 @@ int main(int argc, char** argv) { unsigned target_output_frequency = atoi(argv[i++]); fprintf(stderr, "target_output_frequency\t\t%d\n", target_output_frequency); + float kii = 0.0; + if(i + num_lut_entries != argc) { fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); return 1; @@ -67,6 +69,7 @@ int main(int argc, char** argv) { sw_pll_init( &sw_pll, SW_PLL_15Q16(kp), SW_PLL_15Q16(ki), + SW_PLL_15Q16(kii), loop_rate_count, pll_ratio, ref_clk_expected_inc, From 83e4f8fb6bc19d6ab6af8adfcdb74744a60f62d2 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 11:17:51 +0000 Subject: [PATCH 034/118] Add missing version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2235bc7a..a89967a6 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="sw_pll", - version="1.1.0", + version="2.0.0", packages=["sw_pll"], package_dir={ "": "python" From 0577d8f9f3b26d2b5d9d840466360146e0e1bf85 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 11:54:12 +0000 Subject: [PATCH 035/118] Another bump missing --- lib_sw_pll/module_build_info | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_sw_pll/module_build_info b/lib_sw_pll/module_build_info index 56bc491b..02b9d008 100644 --- a/lib_sw_pll/module_build_info +++ b/lib_sw_pll/module_build_info @@ -1 +1 @@ -VERSION= 1.1.0 +VERSION= 2.0.0 From 0e35d3f47e4b41abc1ee83282d65b3657d61ba51 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 12:05:00 +0000 Subject: [PATCH 036/118] Fix check for zero Kii --- lib_sw_pll/api/sw_pll.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 19ba770d..7aadd9ab 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -175,7 +175,7 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl sw_pll->i_windup_limit = 0; } sw_pll->error_accum_accum = 0; - if(Ki){ + if(Kii){ sw_pll->i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT }else{ sw_pll->i_windup_limit = 0; From 4af5649b1aed783b4be9dc2cbd9fba955536cb65 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 12:12:56 +0000 Subject: [PATCH 037/118] Typo Ki -> Kii --- lib_sw_pll/api/sw_pll.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 7aadd9ab..1ca5753a 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -176,9 +176,9 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl } sw_pll->error_accum_accum = 0; if(Kii){ - sw_pll->i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT + sw_pll->ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT }else{ - sw_pll->i_windup_limit = 0; + sw_pll->ii_windup_limit = 0; } } From d74b857df922c2d1bfd3854ebbf7149449f43426 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 12:30:35 +0000 Subject: [PATCH 038/118] Add missing register setup file --- examples/simple_sdm/src/register_setup.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/simple_sdm/src/register_setup.h diff --git a/examples/simple_sdm/src/register_setup.h b/examples/simple_sdm/src/register_setup.h new file mode 100644 index 00000000..7b772379 --- /dev/null +++ b/examples/simple_sdm/src/register_setup.h @@ -0,0 +1,15 @@ +/* Autogenerated SDM App PLL setup by dco_model.py using 24.576_1M profile */ +/* Input freq: 24000000 + F: 146 + R: 0 + f: 4 + p: 10 + OD: 5 + ACD: 5 +*/ + +#define APP_PLL_CTL_REG 0x0A809200 +#define APP_PLL_DIV_REG 0x80000005 +#define APP_PLL_FRAC_REG 0x8000040A + + From 8041d8a5e3563b6a5aef1940ced94aa09310464c Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 13:53:41 +0000 Subject: [PATCH 039/118] Copyright --- examples/simple_sdm/src/register_setup.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/simple_sdm/src/register_setup.h b/examples/simple_sdm/src/register_setup.h index 7b772379..a2f61899 100644 --- a/examples/simple_sdm/src/register_setup.h +++ b/examples/simple_sdm/src/register_setup.h @@ -1,3 +1,6 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + /* Autogenerated SDM App PLL setup by dco_model.py using 24.576_1M profile */ /* Input freq: 24000000 F: 146 From 0fa6f32c1bf23b89fbd90c43f16e636b6a73cfc2 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 16 Nov 2023 13:53:56 +0000 Subject: [PATCH 040/118] Remove repeated fn --- lib_sw_pll/src/sw_pll.c | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index e72f7239..331512f7 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -2,6 +2,8 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "sw_pll.h" +#include "sw_pll_pfd.h" + #include #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked @@ -131,41 +133,6 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, xassert(max < calc_max); } -__attribute__((always_inline)) -static inline void sw_pll_calc_error_from_port_timers(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) -{ - uint16_t mclk_expected_pt = 0; - // See if we are using variable loop period sampling, if so, compensate for it by scaling the expected mclk count - if(sw_pll->ref_clk_expected_inc) - { - uint16_t ref_clk_expected_pt = sw_pll->ref_clk_pt_last + sw_pll->ref_clk_expected_inc; - // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 - int16_t ref_clk_diff = PORT_TIMEAFTER(ref_clk_pt, ref_clk_expected_pt) ? -(int16_t)(ref_clk_expected_pt - ref_clk_pt) : (int16_t)(ref_clk_pt - ref_clk_expected_pt); - sw_pll->ref_clk_pt_last = ref_clk_pt; - - // This allows for wrapping of the timer when CONTROL_LOOP_COUNT is high - // Note we use a pre-computed divide followed by a shift to replace a constant divide with a constant multiply + shift - uint32_t mclk_expected_pt_inc = ((uint64_t)sw_pll->mclk_expected_pt_inc - * ((uint64_t)sw_pll->ref_clk_expected_inc + ref_clk_diff) - * sw_pll->ref_clk_scaling_numerator) >> SW_PLL_PRE_DIV_BITS; - // Below is the line we would use if we do not pre-compute the divide. This can take a long time if we spill over 32b - // uint32_t mclk_expected_pt_inc = sw_pll->mclk_expected_pt_inc * (sw_pll->ref_clk_expected_inc + ref_clk_diff) / sw_pll->ref_clk_expected_inc; - mclk_expected_pt = sw_pll->mclk_pt_last + mclk_expected_pt_inc; - } - else // we are assuming mclk_pt is sampled precisely and needs no compoensation - { - mclk_expected_pt = sw_pll->mclk_pt_last + sw_pll->mclk_expected_pt_inc; - } - - // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 - sw_pll->mclk_diff = PORT_TIMEAFTER(mclk_pt, mclk_expected_pt) ? -(int16_t)(mclk_expected_pt - mclk_pt) : (int16_t)(mclk_pt - mclk_expected_pt); - - // Check to see if something has gone very wrong, for example ref clock stop/start. If so, reset state and keep trying - if(MAGNITUDE(sw_pll->mclk_diff) > sw_pll->mclk_max_diff) - { - sw_pll->first_loop = 1; - } -} __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) From d3742f7fe6093ac8ae6b624d26622557ee9b73b9 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 06:56:23 +0000 Subject: [PATCH 041/118] WIP make HW setup generic --- examples/shared/src/clock_gen.c | 55 ++++++++++++ examples/shared/src/clock_gen.h | 6 ++ examples/shared/src/resource_setup.c | 40 +++++++++ examples/shared/src/resource_setup.h | 13 +++ examples/simple/simple.cmake | 8 +- examples/simple/src/main.xc | 5 +- examples/simple/src/simple_sw_pll.c | 92 +-------------------- examples/simple_sdm/src/simple_sw_pll_sdm.c | 10 +-- lib_sw_pll/api/sw_pll.h | 2 +- 9 files changed, 130 insertions(+), 101 deletions(-) create mode 100644 examples/shared/src/clock_gen.c create mode 100644 examples/shared/src/clock_gen.h create mode 100644 examples/shared/src/resource_setup.c create mode 100644 examples/shared/src/resource_setup.h diff --git a/examples/shared/src/clock_gen.c b/examples/shared/src/clock_gen.c new file mode 100644 index 00000000..1aaf1750 --- /dev/null +++ b/examples/shared/src/clock_gen.c @@ -0,0 +1,55 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define DO_CLOCKS \ +printf("Ref Hz: %d\n", clock_rate >> 1); \ +\ +unsigned cycle_ticks_int = XS1_TIMER_HZ / clock_rate; \ +unsigned cycle_ticks_remaidner = XS1_TIMER_HZ % clock_rate; \ +unsigned carry = 0; \ +\ +period_trig += XS1_TIMER_HZ * 1; \ +unsigned time_now = hwtimer_get_time(period_tmr); \ +while(TIMER_TIMEAFTER(period_trig, time_now)) \ +{ \ + port_out(p_clock_gen, port_val); \ + hwtimer_wait_until(clock_tmr, time_trig); \ + time_trig += cycle_ticks_int; \ + carry += cycle_ticks_remaidner; \ + if(carry >= clock_rate){ \ + time_trig++; \ + carry -= clock_rate; \ + } \ + port_val ^= 1; \ + time_now = hwtimer_get_time(period_tmr); \ +} + +void clock_gen(unsigned ref_frequency, unsigned ppm_range) +{ + unsigned clock_rate = ref_frequency * 2; // Note double because we generate edges at this rate + unsigned ppm_range = 150; // Step from - to + this + + unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0)); + unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0)); + unsigned step_size = (clock_rate_high - clock_rate_low) / 20; + + printf("Sweep range: %d %d %d, step size: %d\n", clock_rate_low / 2, clock_rate / 2, clock_rate_high / 2, step_size); + + hwtimer_t period_tmr = hwtimer_alloc(); + unsigned period_trig = hwtimer_get_time(period_tmr); + + hwtimer_t clock_tmr = hwtimer_alloc(); + unsigned time_trig = hwtimer_get_time(clock_tmr); + + port_t p_clock_gen = PORT_I2S_BCLK; + port_enable(p_clock_gen); + unsigned port_val = 1; + + for(unsigned clock_rate = clock_rate_low; clock_rate <= clock_rate_high; clock_rate += 2 * step_size){ + DO_CLOCKS + } + for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){ + DO_CLOCKS + } + exit(0); +} \ No newline at end of file diff --git a/examples/shared/src/clock_gen.h b/examples/shared/src/clock_gen.h new file mode 100644 index 00000000..b2a3a475 --- /dev/null +++ b/examples/shared/src/clock_gen.h @@ -0,0 +1,6 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// Runs a task in a thread that produces a clock that sweeps a reference clock +// between + and - the ppm value specified. +void clock_gen(unsigned ref_frequency, unsigned ppm_range); \ No newline at end of file diff --git a/examples/shared/src/resource_setup.c b/examples/shared/src/resource_setup.c new file mode 100644 index 00000000..235d8e39 --- /dev/null +++ b/examples/shared/src/resource_setup.c @@ -0,0 +1,40 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) +{ + // Create clock from mclk port and use it to clock the p_ref_clk port. + clock_enable(clk_mclk); + port_enable(p_mclk); + clock_set_source_port(clk_mclk, p_mclk); + + // Clock p_ref_clk from MCLK + port_enable(p_ref_clk_in); + port_set_clock(p_ref_clk_in, clk_mclk); + + clock_start(clk_mclk); + + // Create clock from ref_clock_port and use it to clock the p_ref_clk_count port. + clock_enable(clk_word_clk); + clock_set_source_port(clk_word_clk, p_ref_clk_in); + port_enable(p_ref_clk_count); + port_set_clock(p_ref_clk_count, clk_word_clk); + + clock_start(clk_word_clk); +} + + +void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider) +{ + // Connect clock block with divide to mclk + clock_enable(clk_recovered_ref_clk); + clock_set_source_port(clk_recovered_ref_clk, p_mclk); + clock_set_divide(clk_recovered_ref_clk, divider / 2); + printf("Divider: %u\n", divider); + + // Output the divided mclk on a port + port_enable(p_recovered_ref_clk); + port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk); + port_set_out_clock(p_recovered_ref_clk); + clock_start(clk_recovered_ref_clk); +} \ No newline at end of file diff --git a/examples/shared/src/resource_setup.h b/examples/shared/src/resource_setup.h new file mode 100644 index 00000000..0b41e1ef --- /dev/null +++ b/examples/shared/src/resource_setup.h @@ -0,0 +1,13 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// Sets up the provided resources so that we can count PLL output clocks. +// We do this by clocking the input reference clock port with the output from the PLL +// and its internal counter is used to count the PLL clock cycles(normal timers cannot count custom clocks) +// It also sets up a dummy port clocked by the input reference to act as a timing barrier so that +// the output clock count can be precisely sampled. +void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count); + +// Sets up a divided version of the PLL output so it can visually be compared (eg. on a DSO) +// with the input reference clock to the PLL +void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider); \ No newline at end of file diff --git a/examples/simple/simple.cmake b/examples/simple/simple.cmake index 956d4558..f06999a6 100644 --- a/examples/simple/simple.cmake +++ b/examples/simple/simple.cmake @@ -1,9 +1,11 @@ #********************** # Gather Sources #********************** -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/src/*.xc) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c + ${CMAKE_CURRENT_LIST_DIR}/src/*.xc + ${CMAKE_CURRENT_LIST_DIR}../shared/src/*.c ) +set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}../shared/src ) #********************** diff --git a/examples/simple/src/main.xc b/examples/simple/src/main.xc index 8f716746..54c529d3 100644 --- a/examples/simple/src/main.xc +++ b/examples/simple/src/main.xc @@ -5,7 +5,10 @@ #include extern void sw_pll_test(void); -extern void clock_gen(void); +extern "C" { + #include "clock_gen.h" +} + int main(void) { diff --git a/examples/simple/src/simple_sw_pll.c b/examples/simple/src/simple_sw_pll.c index dbcc4b84..dd1dd879 100644 --- a/examples/simple/src/simple_sw_pll.c +++ b/examples/simple/src/simple_sw_pll.c @@ -7,6 +7,7 @@ #include #include "sw_pll.h" +#include "resource_setup.h" #define MCLK_FREQUENCY 12288000 #define REF_FREQUENCY 48000 @@ -21,44 +22,6 @@ //Found solution: IN 24.000MHz, OUT 12.288018MHz, VCO 3047.43MHz, RD 4, FD 507.905 (m = 19, n = 21), OD 2, FOD 31, ERR +1.50ppm #include "fractions.h" -void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) -{ - // Create clock from mclk port and use it to clock the p_ref_clk port. - clock_enable(clk_mclk); - port_enable(p_mclk); - clock_set_source_port(clk_mclk, p_mclk); - - // Clock p_ref_clk from MCLK - port_enable(p_ref_clk_in); - port_set_clock(p_ref_clk_in, clk_mclk); - - clock_start(clk_mclk); - - // Create clock from ref_clock_port and use it to clock the p_ref_clk_count port. - clock_enable(clk_word_clk); - clock_set_source_port(clk_word_clk, p_ref_clk_in); - port_enable(p_ref_clk_count); - port_set_clock(p_ref_clk_count, clk_word_clk); - - clock_start(clk_word_clk); -} - - -void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider) -{ - // Connect clock block with divide to mclk - clock_enable(clk_recovered_ref_clk); - clock_set_source_port(clk_recovered_ref_clk, p_mclk); - clock_set_divide(clk_recovered_ref_clk, divider / 2); - printf("Divider: %u\n", divider); - - // Output the divided mclk on a port - port_enable(p_recovered_ref_clk); - port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk); - port_set_out_clock(p_recovered_ref_clk); - clock_start(clk_recovered_ref_clk); -} - void sw_pll_test(void){ // Declare mclk and refclk resources and connect up @@ -111,56 +74,3 @@ void sw_pll_test(void){ } } } - -#define DO_CLOCKS \ -printf("Ref Hz: %d\n", clock_rate >> 1); \ -\ -unsigned cycle_ticks_int = XS1_TIMER_HZ / clock_rate; \ -unsigned cycle_ticks_remaidner = XS1_TIMER_HZ % clock_rate; \ -unsigned carry = 0; \ -\ -period_trig += XS1_TIMER_HZ * 1; \ -unsigned time_now = hwtimer_get_time(period_tmr); \ -while(TIMER_TIMEAFTER(period_trig, time_now)) \ -{ \ - port_out(p_clock_gen, port_val); \ - hwtimer_wait_until(clock_tmr, time_trig); \ - time_trig += cycle_ticks_int; \ - carry += cycle_ticks_remaidner; \ - if(carry >= clock_rate){ \ - time_trig++; \ - carry -= clock_rate; \ - } \ - port_val ^= 1; \ - time_now = hwtimer_get_time(period_tmr); \ -} - -void clock_gen(void) -{ - unsigned clock_rate = REF_FREQUENCY * 2; // Note double because we generate edges at this rate - unsigned ppm_range = 150; // Step from - to + this - - unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0)); - unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0)); - unsigned step_size = (clock_rate_high - clock_rate_low) / 20; - - printf("Sweep range: %d %d %d, step size: %d\n", clock_rate_low / 2, clock_rate / 2, clock_rate_high / 2, step_size); - - hwtimer_t period_tmr = hwtimer_alloc(); - unsigned period_trig = hwtimer_get_time(period_tmr); - - hwtimer_t clock_tmr = hwtimer_alloc(); - unsigned time_trig = hwtimer_get_time(clock_tmr); - - port_t p_clock_gen = PORT_I2S_BCLK; - port_enable(p_clock_gen); - unsigned port_val = 1; - - for(unsigned clock_rate = clock_rate_low; clock_rate <= clock_rate_high; clock_rate += 2 * step_size){ - DO_CLOCKS - } - for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){ - DO_CLOCKS - } - exit(0); -} \ No newline at end of file diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index c6dc1a10..9878d602 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -61,21 +61,21 @@ void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_r clock_start(clk_recovered_ref_clk); } -typedef struct sdm_state_t{ +typedef struct sw_pll_sdm_state_t{ int32_t ds_x1; int32_t ds_x2; int32_t ds_x3; -}sdm_state_t; +}sw_pll_sdm_state_t; -void init_sigma_delta(sdm_state_t *sdm_state){ +void init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ sdm_state->ds_x1 = 0; sdm_state->ds_x2 = 0; sdm_state->ds_x3 = 0; } __attribute__((always_inline)) -static inline int32_t do_sigma_delta(sdm_state_t *sdm_state, int32_t ds_in){ +static inline int32_t do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t ds_in){ // Third order, 9 level output delta sigma. 20 bit unsigned input. int32_t ds_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; if (ds_out > 8){ @@ -119,7 +119,7 @@ void sdm_task(chanend_t c_sdm_control){ const uint32_t sdm_interval = 100; - sdm_state_t sdm_state; + sw_pll_sdm_state_t sdm_state; init_sigma_delta(&sdm_state); tileref_t this_tile = get_local_tile_id(); diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index f396f43c..643df1fa 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -40,7 +40,7 @@ typedef struct sw_pll_state_t{ sw_pll_15q16_t Kp; // Proportional constant sw_pll_15q16_t Ki; // Integral constant int32_t i_windup_limit; // Integral term windup limit - unsigned loop_rate_count; // How often the control loop logic runs compared to control cal rate + unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate // Internal state int16_t mclk_diff; // Raw difference between mclk count and expected mclk count From e9b0edf2754fa5084c709abafba2892998ffb584 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 09:26:00 +0000 Subject: [PATCH 042/118] Refactor with struct of structs --- examples/shared/src/clock_gen.c | 11 ++- examples/shared/src/resource_setup.c | 3 + examples/shared/src/resource_setup.h | 4 + examples/simple/simple.cmake | 4 +- examples/simple/src/main.xc | 20 ++-- examples/simple_sdm/simple_sdm.cmake | 8 +- examples/simple_sdm/src/main.xc | 10 +- examples/simple_sdm/src/simple_sw_pll_sdm.c | 101 +------------------- lib_sw_pll/CMakeLists.txt | 2 +- lib_sw_pll/api/sw_pll.h | 68 ++++++------- lib_sw_pll/src/sw_pll.c | 66 ++++++------- lib_sw_pll/src/sw_pll_common.h | 16 ++++ lib_sw_pll/src/sw_pll_pfd.h | 47 +++++---- lib_sw_pll/src/sw_pll_sdm.c | 89 ++++++----------- 14 files changed, 187 insertions(+), 262 deletions(-) create mode 100644 lib_sw_pll/src/sw_pll_common.h diff --git a/examples/shared/src/clock_gen.c b/examples/shared/src/clock_gen.c index 1aaf1750..9c7c78bb 100644 --- a/examples/shared/src/clock_gen.c +++ b/examples/shared/src/clock_gen.c @@ -1,6 +1,13 @@ // Copyright 2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include + +#include "sw_pll_common.h" + #define DO_CLOCKS \ printf("Ref Hz: %d\n", clock_rate >> 1); \ \ @@ -24,10 +31,9 @@ while(TIMER_TIMEAFTER(period_trig, time_now)) \ time_now = hwtimer_get_time(period_tmr); \ } -void clock_gen(unsigned ref_frequency, unsigned ppm_range) +void clock_gen(unsigned ref_frequency, unsigned ppm_range) // Step from - to + this { unsigned clock_rate = ref_frequency * 2; // Note double because we generate edges at this rate - unsigned ppm_range = 150; // Step from - to + this unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0)); unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0)); @@ -51,5 +57,4 @@ void clock_gen(unsigned ref_frequency, unsigned ppm_range) for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){ DO_CLOCKS } - exit(0); } \ No newline at end of file diff --git a/examples/shared/src/resource_setup.c b/examples/shared/src/resource_setup.c index 235d8e39..082ed162 100644 --- a/examples/shared/src/resource_setup.c +++ b/examples/shared/src/resource_setup.c @@ -1,6 +1,9 @@ // Copyright 2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include "resource_setup.h" + void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) { // Create clock from mclk port and use it to clock the p_ref_clk port. diff --git a/examples/shared/src/resource_setup.h b/examples/shared/src/resource_setup.h index 0b41e1ef..c45ada45 100644 --- a/examples/shared/src/resource_setup.h +++ b/examples/shared/src/resource_setup.h @@ -1,6 +1,10 @@ // Copyright 2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include + +#pragma once + // Sets up the provided resources so that we can count PLL output clocks. // We do this by clocking the input reference clock port with the output from the PLL // and its internal counter is used to count the PLL clock cycles(normal timers cannot count custom clocks) diff --git a/examples/simple/simple.cmake b/examples/simple/simple.cmake index f06999a6..d27fa3d8 100644 --- a/examples/simple/simple.cmake +++ b/examples/simple/simple.cmake @@ -3,9 +3,9 @@ #********************** file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/src/*.xc - ${CMAKE_CURRENT_LIST_DIR}../shared/src/*.c ) + ${CMAKE_CURRENT_LIST_DIR}/../shared/src/*.c ) set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src - ${CMAKE_CURRENT_LIST_DIR}../shared/src + ${CMAKE_CURRENT_LIST_DIR}/../shared/src ) #********************** diff --git a/examples/simple/src/main.xc b/examples/simple/src/main.xc index 54c529d3..10680418 100644 --- a/examples/simple/src/main.xc +++ b/examples/simple/src/main.xc @@ -3,6 +3,7 @@ #include #include +#include extern void sw_pll_test(void); extern "C" { @@ -12,14 +13,17 @@ extern "C" { int main(void) { - par - { - on tile[0]: par { + par + { + on tile[0]: par { + } + on tile[1]: par { + sw_pll_test(); + { + clock_gen(48000, 150); + exit(0); + } + } } - on tile[1]: par { - sw_pll_test(); - clock_gen(); - } - } return 0; } diff --git a/examples/simple_sdm/simple_sdm.cmake b/examples/simple_sdm/simple_sdm.cmake index e9bf329a..532d531c 100644 --- a/examples/simple_sdm/simple_sdm.cmake +++ b/examples/simple_sdm/simple_sdm.cmake @@ -1,9 +1,11 @@ #********************** # Gather Sources #********************** -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c ${CMAKE_CURRENT_LIST_DIR}/src/*.xc) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.c + ${CMAKE_CURRENT_LIST_DIR}/src/*.xc + ${CMAKE_CURRENT_LIST_DIR}/../shared/src/*.c ) +set(APP_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/../shared/src ) #********************** diff --git a/examples/simple_sdm/src/main.xc b/examples/simple_sdm/src/main.xc index 6973b269..a8f0b3af 100644 --- a/examples/simple_sdm/src/main.xc +++ b/examples/simple_sdm/src/main.xc @@ -3,10 +3,13 @@ #include #include +#include extern void sw_pll_sdm_test(chanend c_sdm_control); extern void sdm_task(chanend c_sdm_control); -extern void clock_gen(void); +extern "C" { + #include "clock_gen.h" +} int main(void) { @@ -20,7 +23,10 @@ int main(void) on tile[1]: par { sw_pll_sdm_test(c_sdm_control); sdm_task(c_sdm_control); - clock_gen(); + { + clock_gen(48000, 250); + exit(0); + } } } return 0; diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 9878d602..f6c0b221 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -11,6 +11,7 @@ #include #include "sw_pll.h" +#include "resource_setup.h" #define MCLK_FREQUENCY 24576000 #define REF_FREQUENCY 48000 @@ -23,51 +24,6 @@ typedef int tileref_t; -void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) -{ - // Create clock from mclk port and use it to clock the p_ref_clk port. - clock_enable(clk_mclk); - port_enable(p_mclk); - clock_set_source_port(clk_mclk, p_mclk); - - // Clock p_ref_clk from MCLK - port_enable(p_ref_clk_in); - port_set_clock(p_ref_clk_in, clk_mclk); - - clock_start(clk_mclk); - - // Create clock from ref_clock_port and use it to clock the p_ref_clk_count port. - clock_enable(clk_word_clk); - clock_set_source_port(clk_word_clk, p_ref_clk_in); - port_enable(p_ref_clk_count); - port_set_clock(p_ref_clk_count, clk_word_clk); - - clock_start(clk_word_clk); -} - - -void setup_recovered_ref_clock_output(port_t p_recovered_ref_clk, xclock_t clk_recovered_ref_clk, port_t p_mclk, unsigned divider) -{ - // Connect clock block with divide to mclk - clock_enable(clk_recovered_ref_clk); - clock_set_source_port(clk_recovered_ref_clk, p_mclk); - clock_set_divide(clk_recovered_ref_clk, divider / 2); - printf("Divider: %u\n", divider); - - // Output the divided mclk on a port - port_enable(p_recovered_ref_clk); - port_set_clock(p_recovered_ref_clk, clk_recovered_ref_clk); - port_set_out_clock(p_recovered_ref_clk); - clock_start(clk_recovered_ref_clk); -} - -typedef struct sw_pll_sdm_state_t{ - int32_t ds_x1; - int32_t ds_x2; - int32_t ds_x3; -}sw_pll_sdm_state_t; - - void init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ sdm_state->ds_x1 = 0; sdm_state->ds_x2 = 0; @@ -217,58 +173,3 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ } } } - - - -#define DO_CLOCKS \ -printf("Ref Hz: %d\n", clock_rate >> 1); \ -\ -unsigned cycle_ticks_int = XS1_TIMER_HZ / clock_rate; \ -unsigned cycle_ticks_remaidner = XS1_TIMER_HZ % clock_rate; \ -unsigned carry = 0; \ -\ -period_trig += XS1_TIMER_HZ * 1; \ -unsigned time_now = hwtimer_get_time(period_tmr); \ -while(TIMER_TIMEAFTER(period_trig, time_now)) \ -{ \ - port_out(p_clock_gen, port_val); \ - hwtimer_wait_until(clock_tmr, time_trig); \ - time_trig += cycle_ticks_int; \ - carry += cycle_ticks_remaidner; \ - if(carry >= clock_rate){ \ - time_trig++; \ - carry -= clock_rate; \ - } \ - port_val ^= 1; \ - time_now = hwtimer_get_time(period_tmr); \ -} - -void clock_gen(void) -{ - unsigned clock_rate = REF_FREQUENCY * 2; // Note double because we generate edges at this rate - unsigned ppm_range = 150; // Step from - to + this - - unsigned clock_rate_low = (unsigned)(clock_rate * (1.0 - (float)ppm_range / 1000000.0)); - unsigned clock_rate_high = (unsigned)(clock_rate * (1.0 + (float)ppm_range / 1000000.0)); - unsigned step_size = (clock_rate_high - clock_rate_low) / 20; - - printf("Sweep range: %d %d %d, step size: %d\n", clock_rate_low / 2, clock_rate / 2, clock_rate_high / 2, step_size); - - hwtimer_t period_tmr = hwtimer_alloc(); - unsigned period_trig = hwtimer_get_time(period_tmr); - - hwtimer_t clock_tmr = hwtimer_alloc(); - unsigned time_trig = hwtimer_get_time(clock_tmr); - - port_t p_clock_gen = PORT_I2S_BCLK; - port_enable(p_clock_gen); - unsigned port_val = 1; - - for(unsigned clock_rate = clock_rate_low; clock_rate <= clock_rate_high; clock_rate += 2 * step_size){ - DO_CLOCKS - } - for(unsigned clock_rate = clock_rate_high; clock_rate > clock_rate_low; clock_rate -= 2 * step_size){ - DO_CLOCKS - } - exit(0); -} \ No newline at end of file diff --git a/lib_sw_pll/CMakeLists.txt b/lib_sw_pll/CMakeLists.txt index 5c5679d0..7130a7a1 100644 --- a/lib_sw_pll/CMakeLists.txt +++ b/lib_sw_pll/CMakeLists.txt @@ -14,7 +14,7 @@ set(XCORE_XS3A_SOURCES ${LIB_ASM_SOURCES}) set(LIB_COMPILE_FLAGS "-Os" "-g") ## Includes files -set(LIB_PUBLIC_INCLUDES api) +set(LIB_PUBLIC_INCLUDES api src) set(LIB_PRIVATE_INCLUDES src) ## Gather library sources diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 643df1fa..cb8ae083 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -11,23 +11,38 @@ #include #include -// Helpers used in this module -#define TIMER_TIMEAFTER(A, B) ((int)((B) - (A)) < 0) // Returns non-zero if A is after B, accounting for wrap -#define PORT_TIMEAFTER(NOW, EVENT_TIME) ((int16_t)((EVENT_TIME) - (NOW)) < 0) // Returns non-zero if A is after B, accounting for wrap -#define MAGNITUDE(A) (A < 0 ? -A : A) // Removes the sign of a value +// SW_PLL Component includes +#include "sw_pll_common.h" +#include "sw_pll_pfd.h" -typedef int32_t sw_pll_15q16_t; // Type for 15.16 signed fixed point -#define SW_PLL_NUM_FRAC_BITS 16 -#define SW_PLL_15Q16(val) ((sw_pll_15q16_t)((float)val * (1 << SW_PLL_NUM_FRAC_BITS))) -#define SW_PLL_NUM_LUT_ENTRIES(lut_array) (sizeof(lut_array) / sizeof(lut_array[0])) - typedef enum sw_pll_lock_status_t{ SW_PLL_UNLOCKED_LOW = -1, SW_PLL_LOCKED = 0, SW_PLL_UNLOCKED_HIGH = 1 } sw_pll_lock_status_t; +typedef struct sw_pll_pi_state_t{ + sw_pll_15q16_t Kp; // Proportional constant + sw_pll_15q16_t Ki; // Integral constant + int32_t i_windup_limit; // Integral term windup limit + int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) +} sw_pll_pi_state_t; + +typedef struct sw_pll_lut_state_t{ + const int16_t * lut_table_base; // Pointer to the base of the fractional look up table + size_t num_lut_entries; // Number of LUT entries + unsigned nominal_lut_idx; // Initial (mid point normally) LUT index + uint16_t current_reg_val; // Last value sent to the register, used by tests +} sw_pll_lut_state_t; + +typedef struct sw_pll_sdm_state_t{ + int32_t ds_x1; + int32_t ds_x2; + int32_t ds_x3; +}sw_pll_sdm_state_t; + + /** * \addtogroup sw_pll_api sw_pll_api * @@ -36,31 +51,18 @@ typedef enum sw_pll_lock_status_t{ */ typedef struct sw_pll_state_t{ - // User definied paramaters - sw_pll_15q16_t Kp; // Proportional constant - sw_pll_15q16_t Ki; // Integral constant - int32_t i_windup_limit; // Integral term windup limit - unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate - // Internal state - int16_t mclk_diff; // Raw difference between mclk count and expected mclk count - uint16_t ref_clk_pt_last; // Last ref clock value - uint32_t ref_clk_expected_inc; // Expected ref clock increment - uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide - int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) - unsigned loop_counter; // Intenal loop counter to determine when to do control - uint16_t mclk_pt_last; // The last mclk port timer count - uint32_t mclk_expected_pt_inc; // Expected increment of port timer count - uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration sw_pll_lock_status_t lock_status; // State showing whether the PLL has locked or is under/over uint8_t lock_counter; // Counter used to determine lock status uint8_t first_loop; // Flag which indicates if the sw_pll is initialising or not + unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate + unsigned loop_counter; // Intenal loop counter to determine when to do control - const int16_t * lut_table_base; // Pointer to the base of the fractional look up table - size_t num_lut_entries; // Number of LUT entries - unsigned nominal_lut_idx; // Initial (mid point normally) LUT index + sw_pll_pfd_state_t pfd_state; // Phase Frequency Detector + sw_pll_pi_state_t pi_state; // PI(II) controller + sw_pll_lut_state_t lut_state; // Look Up Table based DCO + sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO - uint16_t current_reg_val; // Last value sent to the register, used by tests }sw_pll_state_t; @@ -161,13 +163,13 @@ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, */ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, size_t num_lut_entries) { - sw_pll->Kp = Kp; - sw_pll->Ki = Ki; - sw_pll->error_accum = 0; + sw_pll->pi_state.Kp = Kp; + sw_pll->pi_state.Ki = Ki; + sw_pll->pi_state.error_accum = 0; if(Ki){ - sw_pll->i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT + sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT }else{ - sw_pll->i_windup_limit = 0; + sw_pll->pi_state.i_windup_limit = 0; } } diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index 331512f7..1e3e993f 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -7,7 +7,6 @@ #include #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked -#define SW_PLL_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max // Implement a delay in 100MHz timer ticks without using a timer resource static void blocking_delay(const uint32_t delay_ticks){ @@ -45,7 +44,7 @@ void sw_pll_app_pll_init(const unsigned tileid, __attribute__((always_inline)) static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int32_t total_error) { - const int set = (sw_pll->nominal_lut_idx - total_error); //Notice negative term for error + const int set = (sw_pll->lut_state.nominal_lut_idx - total_error); //Notice negative term for error unsigned int frac_index = 0; if (set < 0) @@ -54,9 +53,9 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 sw_pll->lock_counter = SW_PLL_LOCK_COUNT; sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; } - else if (set >= sw_pll->num_lut_entries) + else if (set >= sw_pll->lut_state.num_lut_entries) { - frac_index = sw_pll->num_lut_entries - 1; + frac_index = sw_pll->lut_state.num_lut_entries - 1; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH; } @@ -73,7 +72,7 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 } } - return sw_pll->lut_table_base[frac_index]; + return sw_pll->lut_state.lut_table_base[frac_index]; } @@ -100,35 +99,36 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); sw_pll->loop_rate_count = loop_rate_count; - sw_pll->current_reg_val = app_pll_div_reg_val; + sw_pll->lut_state.current_reg_val = app_pll_div_reg_val; // Setup LUT params - sw_pll->lut_table_base = lut_table_base; - sw_pll->num_lut_entries = num_lut_entries; - sw_pll->nominal_lut_idx = nominal_lut_idx; + sw_pll->lut_state.lut_table_base = lut_table_base; + sw_pll->lut_state.num_lut_entries = num_lut_entries; + sw_pll->lut_state.nominal_lut_idx = nominal_lut_idx; // Setup general state - sw_pll->mclk_diff = 0; - sw_pll->ref_clk_pt_last = 0; - sw_pll->ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; - if(sw_pll->ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used + sw_pll->pfd_state.mclk_diff = 0; + sw_pll->pfd_state.ref_clk_pt_last = 0; + sw_pll->pfd_state.ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; + if(sw_pll->pfd_state.ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used { - sw_pll->ref_clk_scaling_numerator = (1ULL << SW_PLL_PRE_DIV_BITS) / sw_pll->ref_clk_expected_inc + 1; //+1 helps with rounding accuracy + sw_pll->pfd_state.ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / sw_pll->pfd_state.ref_clk_expected_inc + 1; //+1 helps with rounding accuracy } sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - sw_pll->mclk_pt_last = 0; - sw_pll->mclk_expected_pt_inc = loop_rate_count * pll_ratio; + sw_pll->pfd_state.mclk_pt_last = 0; + sw_pll->pfd_state.mclk_expected_pt_inc = loop_rate_count * pll_ratio; // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. - sw_pll->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); - sw_pll->loop_counter = 0; + sw_pll->pfd_state.mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + + sw_pll->loop_counter = 0; sw_pll->first_loop = 1; // Check we can actually support the numbers used in the maths we use const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX - const float max = (float)sw_pll->ref_clk_expected_inc - * (float)sw_pll->ref_clk_scaling_numerator - * (float)sw_pll->mclk_expected_pt_inc; + const float max = (float)sw_pll->pfd_state.ref_clk_expected_inc + * (float)sw_pll->pfd_state.ref_clk_scaling_numerator + * (float)sw_pll->pfd_state.mclk_expected_pt_inc; // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency xassert(max < calc_max); } @@ -137,19 +137,19 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { - sw_pll->error_accum += error; // Integral error. - sw_pll->error_accum = sw_pll->error_accum > sw_pll->i_windup_limit ? sw_pll->i_windup_limit : sw_pll->error_accum; - sw_pll->error_accum = sw_pll->error_accum < -sw_pll->i_windup_limit ? -sw_pll->i_windup_limit : sw_pll->error_accum; + sw_pll->pi_state.error_accum += error; // Integral error. + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; // Use long long maths to avoid overflow if ever we had a large error accum term - int64_t error_p = ((int64_t)sw_pll->Kp * (int64_t)error); - int64_t error_i = ((int64_t)sw_pll->Ki * (int64_t)sw_pll->error_accum); + int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); + int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); // Convert back to 32b since we are handling LUTs of around a hundred entries int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); - sw_pll->current_reg_val = lookup_pll_frac(sw_pll, total_error); + sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error); - write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->current_reg_val)); + write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->lut_state.current_reg_val)); return sw_pll->lock_status; } @@ -162,8 +162,8 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint if (sw_pll->first_loop) // First loop around so ensure state is clear { - sw_pll->mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data - sw_pll->error_accum = 0; + sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data + sw_pll->pi_state.error_accum = 0; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -173,11 +173,11 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint } else { - sw_pll_calc_error_from_port_timers(sw_pll, mclk_pt, ref_clk_pt); - sw_pll_do_control_from_error(sw_pll, sw_pll->mclk_diff); + sw_pll_calc_error_from_port_timers(&sw_pll->pfd_state, &sw_pll->first_loop, mclk_pt, ref_clk_pt); + sw_pll_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); // Save for next iteration to calc diff - sw_pll->mclk_pt_last = mclk_pt; + sw_pll->pfd_state.mclk_pt_last = mclk_pt; } } diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h new file mode 100644 index 00000000..ca0cf133 --- /dev/null +++ b/lib_sw_pll/src/sw_pll_common.h @@ -0,0 +1,16 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +// Helpers used in this module +#define TIMER_TIMEAFTER(A, B) ((int)((B) - (A)) < 0) // Returns non-zero if A is after B, accounting for wrap +#define PORT_TIMEAFTER(NOW, EVENT_TIME) ((int16_t)((EVENT_TIME) - (NOW)) < 0) // Returns non-zero if A is after B, accounting for wrap +#define MAGNITUDE(A) (A < 0 ? -A : A) // Removes the sign of a value + + +typedef int32_t sw_pll_15q16_t; // Type for 15.16 signed fixed point + +#define SW_PLL_NUM_FRAC_BITS 16 +#define SW_PLL_15Q16(val) ((sw_pll_15q16_t)((float)val * (1 << SW_PLL_NUM_FRAC_BITS))) +#define SW_PLL_NUM_LUT_ENTRIES(lut_array) (sizeof(lut_array) / sizeof(lut_array[0])) \ No newline at end of file diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h index f6d95cfa..a2717bfb 100644 --- a/lib_sw_pll/src/sw_pll_pfd.h +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -1,43 +1,58 @@ -// Copyright 2022-2023 XMOS LIMITED. +// Copyright 2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. -#include "sw_pll.h" +#include "sw_pll_common.h" -#define SW_PLL_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max +#pragma once + +#define SW_PLL_PFD_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max + +typedef struct sw_pll_pfd_state_t{ + int16_t mclk_diff; // Raw difference between mclk count and expected mclk count + uint16_t ref_clk_pt_last; // Last ref clock value + uint32_t ref_clk_expected_inc; // Expected ref clock increment + uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide + uint16_t mclk_pt_last; // The last mclk port timer count + uint32_t mclk_expected_pt_inc; // Expected increment of port timer count + uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration +} sw_pll_pfd_state_t; __attribute__((always_inline)) -static inline void sw_pll_calc_error_from_port_timers(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +static inline void sw_pll_calc_error_from_port_timers( sw_pll_pfd_state_t * const pfd, + uint8_t *first_loop, + const uint16_t mclk_pt, + const uint16_t ref_clk_pt) { uint16_t mclk_expected_pt = 0; // See if we are using variable loop period sampling, if so, compensate for it by scaling the expected mclk count - if(sw_pll->ref_clk_expected_inc) + if(pfd->ref_clk_expected_inc) { - uint16_t ref_clk_expected_pt = sw_pll->ref_clk_pt_last + sw_pll->ref_clk_expected_inc; + uint16_t ref_clk_expected_pt = pfd->ref_clk_pt_last + pfd->ref_clk_expected_inc; // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 int16_t ref_clk_diff = PORT_TIMEAFTER(ref_clk_pt, ref_clk_expected_pt) ? -(int16_t)(ref_clk_expected_pt - ref_clk_pt) : (int16_t)(ref_clk_pt - ref_clk_expected_pt); - sw_pll->ref_clk_pt_last = ref_clk_pt; + pfd->ref_clk_pt_last = ref_clk_pt; // This allows for wrapping of the timer when CONTROL_LOOP_COUNT is high // Note we use a pre-computed divide followed by a shift to replace a constant divide with a constant multiply + shift - uint32_t mclk_expected_pt_inc = ((uint64_t)sw_pll->mclk_expected_pt_inc - * ((uint64_t)sw_pll->ref_clk_expected_inc + ref_clk_diff) - * sw_pll->ref_clk_scaling_numerator) >> SW_PLL_PRE_DIV_BITS; + uint32_t mclk_expected_pt_inc = ((uint64_t)pfd->mclk_expected_pt_inc + * ((uint64_t)pfd->ref_clk_expected_inc + ref_clk_diff) + * pfd->ref_clk_scaling_numerator) >> SW_PLL_PFD_PRE_DIV_BITS; // Below is the line we would use if we do not pre-compute the divide. This can take a long time if we spill over 32b - // uint32_t mclk_expected_pt_inc = sw_pll->mclk_expected_pt_inc * (sw_pll->ref_clk_expected_inc + ref_clk_diff) / sw_pll->ref_clk_expected_inc; - mclk_expected_pt = sw_pll->mclk_pt_last + mclk_expected_pt_inc; + // uint32_t mclk_expected_pt_inc = pfd->mclk_expected_pt_inc * (pfd->ref_clk_expected_inc + ref_clk_diff) / pfd->ref_clk_expected_inc; + mclk_expected_pt = pfd->mclk_pt_last + mclk_expected_pt_inc; } else // we are assuming mclk_pt is sampled precisely and needs no compoensation { - mclk_expected_pt = sw_pll->mclk_pt_last + sw_pll->mclk_expected_pt_inc; + mclk_expected_pt = pfd->mclk_pt_last + pfd->mclk_expected_pt_inc; } // This uses casting trickery to work out the difference between the timer values accounting for wrap at 65536 - sw_pll->mclk_diff = PORT_TIMEAFTER(mclk_pt, mclk_expected_pt) ? -(int16_t)(mclk_expected_pt - mclk_pt) : (int16_t)(mclk_pt - mclk_expected_pt); + pfd->mclk_diff = PORT_TIMEAFTER(mclk_pt, mclk_expected_pt) ? -(int16_t)(mclk_expected_pt - mclk_pt) : (int16_t)(mclk_pt - mclk_expected_pt); // Check to see if something has gone very wrong, for example ref clock stop/start. If so, reset state and keep trying - if(MAGNITUDE(sw_pll->mclk_diff) > sw_pll->mclk_max_diff) + if(MAGNITUDE(pfd->mclk_diff) > pfd->mclk_max_diff) { - sw_pll->first_loop = 1; + *first_loop = 1; } } \ No newline at end of file diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index d8b16a30..082a506e 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -2,7 +2,6 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "sw_pll.h" -#include "sw_pll_pfd.h" #include #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked @@ -14,41 +13,6 @@ static void blocking_delay(const uint32_t delay_ticks){ } -__attribute__((always_inline)) -static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int32_t total_error) -{ - const int set = (sw_pll->nominal_lut_idx - total_error); //Notice negative term for error - unsigned int frac_index = 0; - - if (set < 0) - { - frac_index = 0; - sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; - } - else if (set >= sw_pll->num_lut_entries) - { - frac_index = sw_pll->num_lut_entries - 1; - sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH; - } - else - { - frac_index = set; - if(sw_pll->lock_counter){ - sw_pll->lock_counter--; - // Keep last unlocked status - } - else - { - sw_pll->lock_status = SW_PLL_LOCKED; - } - } - - return sw_pll->lut_table_base[frac_index]; -} - - void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, @@ -70,7 +34,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 sw_pll->loop_rate_count = loop_rate_count; - sw_pll->current_reg_val = app_pll_div_reg_val; + sw_pll->lut_state.current_reg_val = app_pll_div_reg_val; // Setup LUT params // sw_pll->lut_table_base = lut_table_base; @@ -78,29 +42,32 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, // sw_pll->nominal_lut_idx = nominal_lut_idx; // Setup general state - sw_pll->mclk_diff = 0; - sw_pll->ref_clk_pt_last = 0; - sw_pll->ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; - if(sw_pll->ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used + sw_pll->pfd_state.mclk_diff = 0; + sw_pll->pfd_state.ref_clk_pt_last = 0; + sw_pll->pfd_state.ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; + if(sw_pll->pfd_state.ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used { - sw_pll->ref_clk_scaling_numerator = (1ULL << SW_PLL_PRE_DIV_BITS) / sw_pll->ref_clk_expected_inc + 1; //+1 helps with rounding accuracy + sw_pll->pfd_state.ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / sw_pll->pfd_state.ref_clk_expected_inc + 1; //+1 helps with rounding accuracy } + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - sw_pll->mclk_pt_last = 0; - sw_pll->mclk_expected_pt_inc = loop_rate_count * pll_ratio; + + sw_pll->pfd_state.mclk_pt_last = 0; + sw_pll->pfd_state.mclk_expected_pt_inc = loop_rate_count * pll_ratio; // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. - sw_pll->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); - sw_pll->mclk_max_diff = 1000; - printf("mclk_max_diff: %u\n",sw_pll->mclk_max_diff); + sw_pll->pfd_state.mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + sw_pll->pfd_state.mclk_max_diff = 1000; + printf("mclk_max_diff: %u\n",sw_pll->pfd_state.mclk_max_diff); + sw_pll->loop_counter = 0; sw_pll->first_loop = 1; // Check we can actually support the numbers used in the maths we use const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX - const float max = (float)sw_pll->ref_clk_expected_inc - * (float)sw_pll->ref_clk_scaling_numerator - * (float)sw_pll->mclk_expected_pt_inc; + const float max = (float)sw_pll->pfd_state.ref_clk_expected_inc + * (float)sw_pll->pfd_state.ref_clk_scaling_numerator + * (float)sw_pll->pfd_state.mclk_expected_pt_inc; // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency xassert(max < calc_max); } @@ -109,13 +76,13 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, __attribute__((always_inline)) inline int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { - sw_pll->error_accum += error; // Integral error. - sw_pll->error_accum = sw_pll->error_accum > sw_pll->i_windup_limit ? sw_pll->i_windup_limit : sw_pll->error_accum; - sw_pll->error_accum = sw_pll->error_accum < -sw_pll->i_windup_limit ? -sw_pll->i_windup_limit : sw_pll->error_accum; + sw_pll->pi_state.error_accum += error; // Integral error. + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; // Use long long maths to avoid overflow if ever we had a large error accum term - int64_t error_p = ((int64_t)sw_pll->Kp * (int64_t)error); - int64_t error_i = ((int64_t)sw_pll->Ki * (int64_t)sw_pll->error_accum); + int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); + int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); // Convert back to 32b since we are handling LUTs of around a hundred entries int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); @@ -131,8 +98,8 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen if (sw_pll->first_loop) // First loop around so ensure state is clear { - sw_pll->mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data - sw_pll->error_accum = 0; + sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data + sw_pll->pi_state.error_accum = 0; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -142,15 +109,15 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen } else { - sw_pll_calc_error_from_port_timers(sw_pll, mclk_pt, ref_clk_pt); - int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->mclk_diff); - printintln(sw_pll->mclk_diff); + sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); + int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); + printintln(sw_pll->pfd_state.mclk_diff); printintln(error); int dco_ctl = 478151 - error; sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); // Save for next iteration to calc diff - sw_pll->mclk_pt_last = mclk_pt; + sw_pll->pfd_state.mclk_pt_last = mclk_pt; } } From dba71afaa9a7c3ee5be5719f6c6650500f508727 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 10:06:38 +0000 Subject: [PATCH 043/118] Fix build error in i2s example --- examples/i2s_slave/src/i2s_slave_sw_pll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/i2s_slave/src/i2s_slave_sw_pll.c b/examples/i2s_slave/src/i2s_slave_sw_pll.c index 5dc5627d..2e8d8117 100644 --- a/examples/i2s_slave/src/i2s_slave_sw_pll.c +++ b/examples/i2s_slave/src/i2s_slave_sw_pll.c @@ -189,7 +189,7 @@ void sw_pll_test(void){ PPM_RANGE); - printf("i_windup_limit: %ld\n", sw_pll.i_windup_limit); + printf("i_windup_limit: %ld\n", sw_pll.pi_state.i_windup_limit); // Initialise app_data From 7604f86b94dc061b947fae1ef9596065e54c5ffb Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 10:17:17 +0000 Subject: [PATCH 044/118] Make pfd init common --- lib_sw_pll/src/sw_pll.c | 38 ++++++++---------------------- lib_sw_pll/src/sw_pll_pfd.c | 30 ++++++++++++++++++++++++ lib_sw_pll/src/sw_pll_pfd.h | 8 +++++++ lib_sw_pll/src/sw_pll_sdm.c | 46 +++++-------------------------------- 4 files changed, 54 insertions(+), 68 deletions(-) create mode 100644 lib_sw_pll/src/sw_pll_pfd.c diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index 1e3e993f..82ce422c 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -75,7 +75,6 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 return sw_pll->lut_state.lut_table_base[frac_index]; } - void sw_pll_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, @@ -98,39 +97,22 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, // Setup sw_pll with supplied user paramaters sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); - sw_pll->loop_rate_count = loop_rate_count; - sw_pll->lut_state.current_reg_val = app_pll_div_reg_val; + // Setup general controller state + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + + sw_pll->loop_rate_count = loop_rate_count; + sw_pll->loop_counter = 0; + sw_pll->first_loop = 1; // Setup LUT params + sw_pll->lut_state.current_reg_val = app_pll_div_reg_val; sw_pll->lut_state.lut_table_base = lut_table_base; sw_pll->lut_state.num_lut_entries = num_lut_entries; sw_pll->lut_state.nominal_lut_idx = nominal_lut_idx; - // Setup general state - sw_pll->pfd_state.mclk_diff = 0; - sw_pll->pfd_state.ref_clk_pt_last = 0; - sw_pll->pfd_state.ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; - if(sw_pll->pfd_state.ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used - { - sw_pll->pfd_state.ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / sw_pll->pfd_state.ref_clk_expected_inc + 1; //+1 helps with rounding accuracy - } - sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; - sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - sw_pll->pfd_state.mclk_pt_last = 0; - sw_pll->pfd_state.mclk_expected_pt_inc = loop_rate_count * pll_ratio; - // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. - sw_pll->pfd_state.mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); - - sw_pll->loop_counter = 0; - sw_pll->first_loop = 1; - - // Check we can actually support the numbers used in the maths we use - const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX - const float max = (float)sw_pll->pfd_state.ref_clk_expected_inc - * (float)sw_pll->pfd_state.ref_clk_scaling_numerator - * (float)sw_pll->pfd_state.mclk_expected_pt_inc; - // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency - xassert(max < calc_max); + // Setup PFD state + sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range); } diff --git a/lib_sw_pll/src/sw_pll_pfd.c b/lib_sw_pll/src/sw_pll_pfd.c new file mode 100644 index 00000000..0ea8b5a7 --- /dev/null +++ b/lib_sw_pll/src/sw_pll_pfd.c @@ -0,0 +1,30 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "sw_pll_pfd.h" + +void sw_pll_pfd_init(sw_pll_pfd_state_t *pfd_state, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const unsigned ppm_range) +{ + pfd_state->mclk_diff = 0; + pfd_state->ref_clk_pt_last = 0; + pfd_state->ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; + if(pfd_state->ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used + { + pfd_state->ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / pfd_state->ref_clk_expected_inc + 1; //+1 helps with rounding accuracy + } + pfd_state->mclk_pt_last = 0; + pfd_state->mclk_expected_pt_inc = loop_rate_count * pll_ratio; + // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. + pfd_state->mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); + // Check we can actually support the numbers used in the maths we use + const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX + const float max = (float)pfd_state->ref_clk_expected_inc + * (float)pfd_state->ref_clk_scaling_numerator + * (float)pfd_state->mclk_expected_pt_inc; + // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency + xassert(max < calc_max); +} diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h index a2717bfb..ecc99f9d 100644 --- a/lib_sw_pll/src/sw_pll_pfd.h +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -1,6 +1,9 @@ // Copyright 2023 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include #include "sw_pll_common.h" #pragma once @@ -17,6 +20,11 @@ typedef struct sw_pll_pfd_state_t{ uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration } sw_pll_pfd_state_t; +void sw_pll_pfd_init(sw_pll_pfd_state_t *pfd_state, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const unsigned ppm_range); __attribute__((always_inline)) static inline void sw_pll_calc_error_from_port_timers( sw_pll_pfd_state_t * const pfd, diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 082a506e..b0d5264d 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -6,12 +6,6 @@ #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked -// Implement a delay in 100MHz timer ticks without using a timer resource -static void blocking_delay(const uint32_t delay_ticks){ - uint32_t time_delay = get_reference_time() + delay_ticks; - while(TIMER_TIMEAFTER(time_delay, get_reference_time())); -} - void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, @@ -33,43 +27,16 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, // Setup sw_pll with supplied user paramaters sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 - sw_pll->loop_rate_count = loop_rate_count; - sw_pll->lut_state.current_reg_val = app_pll_div_reg_val; - - // Setup LUT params - // sw_pll->lut_table_base = lut_table_base; - // sw_pll->num_lut_entries = num_lut_entries; - // sw_pll->nominal_lut_idx = nominal_lut_idx; - - // Setup general state - sw_pll->pfd_state.mclk_diff = 0; - sw_pll->pfd_state.ref_clk_pt_last = 0; - sw_pll->pfd_state.ref_clk_expected_inc = ref_clk_expected_inc * loop_rate_count; - if(sw_pll->pfd_state.ref_clk_expected_inc) // Avoid div 0 error if ref_clk compensation not used - { - sw_pll->pfd_state.ref_clk_scaling_numerator = (1ULL << SW_PLL_PFD_PRE_DIV_BITS) / sw_pll->pfd_state.ref_clk_expected_inc + 1; //+1 helps with rounding accuracy - } - + // Setup general controller state sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; - - sw_pll->pfd_state.mclk_pt_last = 0; - sw_pll->pfd_state.mclk_expected_pt_inc = loop_rate_count * pll_ratio; - // Set max PPM deviation before we chose to reset the PLL state. Nominally twice the normal range. - sw_pll->pfd_state.mclk_max_diff = (uint64_t)(((uint64_t)ppm_range * 2ULL * (uint64_t)pll_ratio * (uint64_t)loop_rate_count) / 1000000); - sw_pll->pfd_state.mclk_max_diff = 1000; - printf("mclk_max_diff: %u\n",sw_pll->pfd_state.mclk_max_diff); - - sw_pll->loop_counter = 0; + + sw_pll->loop_rate_count = loop_rate_count; + sw_pll->loop_counter = 0; sw_pll->first_loop = 1; - // Check we can actually support the numbers used in the maths we use - const float calc_max = (float)0xffffffffffffffffULL / 1.1; // Add 10% headroom from ULL MAX - const float max = (float)sw_pll->pfd_state.ref_clk_expected_inc - * (float)sw_pll->pfd_state.ref_clk_scaling_numerator - * (float)sw_pll->pfd_state.mclk_expected_pt_inc; - // If you have hit this assert then you need to reduce loop_rate_count or possibly the PLL ratio and or MCLK frequency - xassert(max < calc_max); + // Setup PFD state + sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range); } @@ -124,6 +91,5 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen // printchar('+'); - return sw_pll->lock_status; } From 969427cffd261b95319db33c660d9e43e8992b42 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 11:00:18 +0000 Subject: [PATCH 045/118] Fix tests --- tests/test_app/main.c | 2 +- tests/test_app_low_level_api/main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_app/main.c b/tests/test_app/main.c index b10ee53e..919cf245 100644 --- a/tests/test_app/main.c +++ b/tests/test_app/main.c @@ -106,6 +106,6 @@ int main(int argc, char** argv) { // xsim doesn't support our register and the val that was set gets // dropped - printf("%i %x %hd %ld %u %lu\n", s, sw_pll.current_reg_val, sw_pll.mclk_diff, sw_pll.error_accum, sw_pll.first_loop, t1 - t0); + printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, sw_pll.pfd_state.mclk_diff, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); } } diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index 447859ec..74e64773 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -105,6 +105,6 @@ int main(int argc, char** argv) { // xsim doesn't support our register and the val that was set gets // dropped - printf("%i %x %hd %ld %u %lu\n", s, sw_pll.current_reg_val, error, sw_pll.error_accum, sw_pll.first_loop, t1 - t0); + printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, sw_pll.pfd_state.mclk_diff, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); } } From bc6b98361c96c495468e7407f1109a29a387ce1d Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 20 Nov 2023 11:30:52 +0000 Subject: [PATCH 046/118] Test fix --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 1 - lib_sw_pll/src/sw_pll_common.h | 1 - tests/test_app_low_level_api/main.c | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index f6c0b221..fa650547 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -20,7 +20,6 @@ #define PPM_RANGE 150 //TODO eliminate #include "register_setup.h" -#define APP_PLL_NOMINAL_INDEX_12288 35 //TODO eliminate typedef int tileref_t; diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index ca0cf133..ebadd18d 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -8,7 +8,6 @@ #define PORT_TIMEAFTER(NOW, EVENT_TIME) ((int16_t)((EVENT_TIME) - (NOW)) < 0) // Returns non-zero if A is after B, accounting for wrap #define MAGNITUDE(A) (A < 0 ? -A : A) // Removes the sign of a value - typedef int32_t sw_pll_15q16_t; // Type for 15.16 signed fixed point #define SW_PLL_NUM_FRAC_BITS 16 diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index 74e64773..f19f495c 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -105,6 +105,6 @@ int main(int argc, char** argv) { // xsim doesn't support our register and the val that was set gets // dropped - printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, sw_pll.pfd_state.mclk_diff, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); + printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, error, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); } } From a6a22953e0db9d1bcd424681b783a9d9b607c465 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 08:20:58 +0000 Subject: [PATCH 047/118] Add IIR to C SDM --- lib_sw_pll/api/sw_pll.h | 10 +++++----- lib_sw_pll/src/sw_pll_sdm.c | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index cb8ae083..69a0990b 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -27,6 +27,7 @@ typedef struct sw_pll_pi_state_t{ sw_pll_15q16_t Ki; // Integral constant int32_t i_windup_limit; // Integral term windup limit int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) + int32_t iir_y; // Optional IIR low pass filter state } sw_pll_pi_state_t; typedef struct sw_pll_lut_state_t{ @@ -37,11 +38,10 @@ typedef struct sw_pll_lut_state_t{ } sw_pll_lut_state_t; typedef struct sw_pll_sdm_state_t{ - int32_t ds_x1; - int32_t ds_x2; - int32_t ds_x3; -}sw_pll_sdm_state_t; - + int32_t ds_x1; // Sigma delta modulator state + int32_t ds_x2; // Sigma delta modulator state + int32_t ds_x3; // Sigma delta modulator state +} sw_pll_sdm_state_t; /** * \addtogroup sw_pll_api sw_pll_api diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index b0d5264d..6dc16e8d 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -26,6 +26,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, // Setup sw_pll with supplied user paramaters sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 + sw_pll->pi_state.iir_y = 0; // Setup general controller state sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -67,6 +68,7 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen { sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data sw_pll->pi_state.error_accum = 0; + sw_pll->pi_state.iir_y = 0; sw_pll->lock_counter = SW_PLL_LOCK_COUNT; sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -78,6 +80,13 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); + + // Filter some noise into DCO to reduce jitter + // First order IIR, make A=0.125 + // y = y + A(x-y) + sw_pll->pi_state.iir_y += ((error - sw_pll->pi_state.iir_y)>>3); + + printintln(sw_pll->pfd_state.mclk_diff); printintln(error); int dco_ctl = 478151 - error; From ae246d9d00ca0db6ed2b636136e2251ee240e938 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 08:21:42 +0000 Subject: [PATCH 048/118] Separate out low level equiv test --- tests/test_lib_sw_pll.py | 104 ---------------------- tests/test_lib_sw_pll_equiv.py | 152 +++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 104 deletions(-) create mode 100644 tests/test_lib_sw_pll_equiv.py diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index b4d43e11..65039008 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -409,107 +409,3 @@ def test_locked_values_within_desirable_ppm(basic_test_vector, test_f): assert not test_df.empty, "No locked values found, expected some" max_diff = (test_df["mclk"] - test_df["target"]).abs().max() assert max_diff < max_f_step, "Frequency oscillating more that expected when locked" - - -def test_low_level_equivalence(solution_12288, bin_dir): - """ - Simple low level test of equivalence using do_control_from_error - Feed in random numbers into C and Python DUTs and see if we get the same results - """ - - _, xtal_freq, target_mclk_f, sol = solution_12288 - - - # every sample to speed things up. - loop_rate_count = 1 - target_ref_f = 48000 - - # Generate init parameters - start_reg = sol.lut[0] - lut_size = len(sol.lut) - - args = DutArgs( - target_output_frequency=target_mclk_f, - kp=0.0, - ki=1.0, - loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 - # have to call 512 times to do 1 - # control update - pll_ratio=int(target_mclk_f / target_ref_f), - ref_clk_expected_inc=0, - app_pll_ctl_reg_val=0, - app_pll_div_reg_val=start_reg, - nominal_lut_idx=lut_size//2, - ppm_range=int(lut_size * 2), - lut=sol.lut, - ) - - pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) - - pll.update_frac_reg(start_reg) - - input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) - print(f"input_errors: {input_errors}") - - result_categories = { - "mclk": [], - "locked": [], - "time": [], - "clk_diff": [], - "clk_diff_i": [], - "first_loop": [], - "ticks": [] - } - names = ["C", "Python"] - duts = [Dut(args, pll, xe_file=DUT_XE_LOW_LEVEL), SimDut(args, pll)] - - results = {} - for name in names: - results[name] = copy.deepcopy(result_categories) - - for dut, name in zip(duts, names): - _, mclk_f, *_ = dut.do_control_from_error(0) - - locked = -1 - time = 0 - print(f"Running: {name}") - for input_error in input_errors: - - locked, mclk_f, e, ea, fl, ticks = dut.do_control_from_error(input_error) - - results[name]["mclk"].append(mclk_f) - results[name]["time"].append(time) - results[name]["locked"].append(locked) - results[name]["clk_diff"].append(e) - results[name]["clk_diff_i"].append(ea) - results[name]["first_loop"].append(fl) - results[name]["ticks"].append(ticks) - time += 1 - - # print(name, time, input_error, mclk_f) - - # Plot mclk output dut vs dut - duts = list(results.keys()) - for dut in duts: - mclk = results[dut]["mclk"] - times = results[dut]["time"] - clk_diff = results[dut]["clk_diff"] - clk_diff_i = results[dut]["clk_diff_i"] - locked = results[dut]["locked"] - - plt.plot(mclk, label=dut) - - plt.legend(loc="upper left") - plt.xlabel("Iteration") - plt.ylabel("mclk") - plt.savefig(bin_dir/f"c-vs-python-low-level-equivalence-mclk.png") - plt.close() - - # Check for equivalence - for compare_item in ["mclk", "clk_diff", "clk_diff_i"]: - C = results["C"][compare_item] - Python = results["Python"][compare_item] - assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" - - print("TEST PASSED!") - diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py new file mode 100644 index 00000000..85dc63fb --- /dev/null +++ b/tests/test_lib_sw_pll_equiv.py @@ -0,0 +1,152 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +""" +Assorted tests which run the test_app in xsim + +This file is structured as a fixture which takes a while to run +and generates a pandas.DataFrame containing some time domain +outputs from the control loops. Then a series of tests which +check different aspects of the content of this DataFrame. +""" + +import pytest +import numpy as np +import copy + +from sw_pll.app_pll_model import pll_solution, app_pll_frac_calc +from sw_pll.sw_pll_sim import sim_sw_pll_lut + +from test_lib_sw_pll import SimDut, Dut, DutArgs + +from pathlib import Path +from matplotlib import pyplot as plt + +DUT_XE_LOW_LEVEL = Path(__file__).parent / "../build/tests/test_app_low_level_api/test_app_low_level_api.xe" +BIN_PATH = Path(__file__).parent/"bin" + + + +@pytest.fixture(scope="module") +def solution_12288(): + """ + generate the solution, takes a while and no need + to do it more than once. + """ + xtal_freq = 24e6 + target_mclk_f = 12.288e6 + + ppm_max = 2.0 + sol = pll_solution(xtal_freq, target_mclk_f, ppm_max=ppm_max) + + return ppm_max, xtal_freq, target_mclk_f, sol + +@pytest.fixture(scope="module") +def bin_dir(): + d = BIN_PATH + d.mkdir(parents=True, exist_ok=True) + return d + + + +def test_low_level_equivalence(solution_12288, bin_dir): + """ + Simple low level test of equivalence using do_control_from_error + Feed in random numbers into C and Python DUTs and see if we get the same results + """ + + _, xtal_freq, target_mclk_f, sol = solution_12288 + + + # every sample to speed things up. + loop_rate_count = 1 + target_ref_f = 48000 + + # Generate init parameters + start_reg = sol.lut[0] + lut_size = len(sol.lut) + + args = DutArgs( + target_output_frequency=target_mclk_f, + kp=0.0, + ki=1.0, + loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 + # have to call 512 times to do 1 + # control update + pll_ratio=int(target_mclk_f / target_ref_f), + ref_clk_expected_inc=0, + app_pll_ctl_reg_val=0, + app_pll_div_reg_val=start_reg, + nominal_lut_idx=lut_size//2, + ppm_range=int(lut_size * 2), + lut=sol.lut, + ) + + pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) + + pll.update_frac_reg(start_reg) + + input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) + print(f"input_errors: {input_errors}") + + result_categories = { + "mclk": [], + "locked": [], + "time": [], + "clk_diff": [], + "clk_diff_i": [], + "first_loop": [], + "ticks": [] + } + names = ["C", "Python"] + duts = [Dut(args, pll, xe_file=DUT_XE_LOW_LEVEL), SimDut(args, pll)] + + results = {} + for name in names: + results[name] = copy.deepcopy(result_categories) + + for dut, name in zip(duts, names): + _, mclk_f, *_ = dut.do_control_from_error(0) + + locked = -1 + time = 0 + print(f"Running: {name}") + for input_error in input_errors: + + locked, mclk_f, e, ea, fl, ticks = dut.do_control_from_error(input_error) + + results[name]["mclk"].append(mclk_f) + results[name]["time"].append(time) + results[name]["locked"].append(locked) + results[name]["clk_diff"].append(e) + results[name]["clk_diff_i"].append(ea) + results[name]["first_loop"].append(fl) + results[name]["ticks"].append(ticks) + time += 1 + + # print(name, time, input_error, mclk_f) + + # Plot mclk output dut vs dut + duts = list(results.keys()) + for dut in duts: + mclk = results[dut]["mclk"] + times = results[dut]["time"] + clk_diff = results[dut]["clk_diff"] + clk_diff_i = results[dut]["clk_diff_i"] + locked = results[dut]["locked"] + + plt.plot(mclk, label=dut) + + plt.legend(loc="upper left") + plt.xlabel("Iteration") + plt.ylabel("mclk") + plt.savefig(bin_dir/f"c-vs-python-low-level-equivalence-mclk.png") + plt.close() + + # Check for equivalence + for compare_item in ["mclk", "clk_diff", "clk_diff_i"]: + C = results["C"][compare_item] + Python = results["Python"][compare_item] + assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" + + print("TEST PASSED!") + From 56a31a5cb31235d2124d1d7b076f054d4f01feee Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 08:27:11 +0000 Subject: [PATCH 049/118] Tidy test separation --- tests/test_lib_sw_pll.py | 1 - tests/test_lib_sw_pll_equiv.py | 25 +------------------------ 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 65039008..191803e1 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -25,7 +25,6 @@ from matplotlib import pyplot as plt DUT_XE = Path(__file__).parent / "../build/tests/test_app/test_app.xe" -DUT_XE_LOW_LEVEL = Path(__file__).parent / "../build/tests/test_app_low_level_api/test_app_low_level_api.xe" BIN_PATH = Path(__file__).parent/"bin" @dataclass diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 85dc63fb..09eb7e35 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -16,7 +16,7 @@ from sw_pll.app_pll_model import pll_solution, app_pll_frac_calc from sw_pll.sw_pll_sim import sim_sw_pll_lut -from test_lib_sw_pll import SimDut, Dut, DutArgs +from test_lib_sw_pll import SimDut, Dut, DutArgs, solution_12288, bin_dir from pathlib import Path from matplotlib import pyplot as plt @@ -25,29 +25,6 @@ BIN_PATH = Path(__file__).parent/"bin" - -@pytest.fixture(scope="module") -def solution_12288(): - """ - generate the solution, takes a while and no need - to do it more than once. - """ - xtal_freq = 24e6 - target_mclk_f = 12.288e6 - - ppm_max = 2.0 - sol = pll_solution(xtal_freq, target_mclk_f, ppm_max=ppm_max) - - return ppm_max, xtal_freq, target_mclk_f, sol - -@pytest.fixture(scope="module") -def bin_dir(): - d = BIN_PATH - d.mkdir(parents=True, exist_ok=True) - return d - - - def test_low_level_equivalence(solution_12288, bin_dir): """ Simple low level test of equivalence using do_control_from_error From c2e48993ad852a290edcf0b018e090e18dda7661 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 18:21:28 +0000 Subject: [PATCH 050/118] Move SDM code from example to lib --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 51 -------------- lib_sw_pll/api/sw_pll.h | 51 +------------- lib_sw_pll/src/sw_pll_common.h | 76 ++++++++++++++++++++- lib_sw_pll/src/sw_pll_pfd.h | 10 --- lib_sw_pll/src/sw_pll_sdm.c | 14 +++- lib_sw_pll/src/sw_pll_sdm.h | 55 +++++++++++++++ 6 files changed, 143 insertions(+), 114 deletions(-) create mode 100644 lib_sw_pll/src/sw_pll_sdm.h diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index fa650547..103f6ef6 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -21,54 +21,6 @@ #include "register_setup.h" -typedef int tileref_t; - -void init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ - sdm_state->ds_x1 = 0; - sdm_state->ds_x2 = 0; - sdm_state->ds_x3 = 0; -} - -__attribute__((always_inline)) -static inline int32_t do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t ds_in){ - // Third order, 9 level output delta sigma. 20 bit unsigned input. - int32_t ds_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; - if (ds_out > 8){ - ds_out = 8; - } - if (ds_out < 0){ - ds_out = 0; - } - sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (ds_out<<9) - (ds_out<<8); - sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (ds_out<<14); - sdm_state->ds_x1 += ds_in - (ds_out<<17); - - return ds_out; -} - -__attribute__((always_inline)) -static inline uint32_t ds_out_to_frac_reg(int32_t ds_out){ - // bit 31 is frac enable - // bits 15..8 are the f value - // bits 7..0 are the p value - // Freq - F + (f + 1)/(p + 1) - uint32_t frac_val = 0; - - if (ds_out == 0){ - frac_val = 0x00000007; // step 0/8 - } - else{ - frac_val = ((ds_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8 - } - - return frac_val; -} - -__attribute__((always_inline)) -static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ - write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); -} - void sdm_task(chanend_t c_sdm_control){ printf("sdm_task\n"); @@ -118,9 +70,6 @@ void sdm_task(chanend_t c_sdm_control){ } } -void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ - chan_out_word(c_sdm_control, dco_ctl); -} void sw_pll_sdm_test(chanend_t c_sdm_control){ diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 69a0990b..ccb9f46f 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -14,56 +14,7 @@ // SW_PLL Component includes #include "sw_pll_common.h" #include "sw_pll_pfd.h" - - -typedef enum sw_pll_lock_status_t{ - SW_PLL_UNLOCKED_LOW = -1, - SW_PLL_LOCKED = 0, - SW_PLL_UNLOCKED_HIGH = 1 -} sw_pll_lock_status_t; - -typedef struct sw_pll_pi_state_t{ - sw_pll_15q16_t Kp; // Proportional constant - sw_pll_15q16_t Ki; // Integral constant - int32_t i_windup_limit; // Integral term windup limit - int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) - int32_t iir_y; // Optional IIR low pass filter state -} sw_pll_pi_state_t; - -typedef struct sw_pll_lut_state_t{ - const int16_t * lut_table_base; // Pointer to the base of the fractional look up table - size_t num_lut_entries; // Number of LUT entries - unsigned nominal_lut_idx; // Initial (mid point normally) LUT index - uint16_t current_reg_val; // Last value sent to the register, used by tests -} sw_pll_lut_state_t; - -typedef struct sw_pll_sdm_state_t{ - int32_t ds_x1; // Sigma delta modulator state - int32_t ds_x2; // Sigma delta modulator state - int32_t ds_x3; // Sigma delta modulator state -} sw_pll_sdm_state_t; - -/** - * \addtogroup sw_pll_api sw_pll_api - * - * The public API for using the RTOS I2C slave driver. - * @{ - */ - -typedef struct sw_pll_state_t{ - - sw_pll_lock_status_t lock_status; // State showing whether the PLL has locked or is under/over - uint8_t lock_counter; // Counter used to determine lock status - uint8_t first_loop; // Flag which indicates if the sw_pll is initialising or not - unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate - unsigned loop_counter; // Intenal loop counter to determine when to do control - - sw_pll_pfd_state_t pfd_state; // Phase Frequency Detector - sw_pll_pi_state_t pi_state; // PI(II) controller - sw_pll_lut_state_t lut_state; // Look Up Table based DCO - sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO - -}sw_pll_state_t; +#include "sw_pll_sdm.h" /** diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index ebadd18d..fd10b5a9 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -12,4 +12,78 @@ typedef int32_t sw_pll_15q16_t; // Type for 15.16 signed fixed point #define SW_PLL_NUM_FRAC_BITS 16 #define SW_PLL_15Q16(val) ((sw_pll_15q16_t)((float)val * (1 << SW_PLL_NUM_FRAC_BITS))) -#define SW_PLL_NUM_LUT_ENTRIES(lut_array) (sizeof(lut_array) / sizeof(lut_array[0])) \ No newline at end of file +#define SW_PLL_NUM_LUT_ENTRIES(lut_array) (sizeof(lut_array) / sizeof(lut_array[0])) + +typedef enum sw_pll_lock_status_t{ + SW_PLL_UNLOCKED_LOW = -1, + SW_PLL_LOCKED = 0, + SW_PLL_UNLOCKED_HIGH = 1 +} sw_pll_lock_status_t; + +typedef struct sw_pll_pfd_state_t{ + int16_t mclk_diff; // Raw difference between mclk count and expected mclk count + uint16_t ref_clk_pt_last; // Last ref clock value + uint32_t ref_clk_expected_inc; // Expected ref clock increment + uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide + uint16_t mclk_pt_last; // The last mclk port timer count + uint32_t mclk_expected_pt_inc; // Expected increment of port timer count + uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration +} sw_pll_pfd_state_t; + +typedef struct sw_pll_pi_state_t{ + sw_pll_15q16_t Kp; // Proportional constant + sw_pll_15q16_t Ki; // Integral constant + int32_t i_windup_limit; // Integral term windup limit + int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) + int32_t iir_y; // Optional IIR low pass filter state +} sw_pll_pi_state_t; + +typedef struct sw_pll_lut_state_t{ + const int16_t * lut_table_base; // Pointer to the base of the fractional look up table + size_t num_lut_entries; // Number of LUT entries + unsigned nominal_lut_idx; // Initial (mid point normally) LUT index + uint16_t current_reg_val; // Last value sent to the register, used by tests +} sw_pll_lut_state_t; + + +typedef struct sw_pll_sdm_state_t{ + int32_t ds_x1; // Sigma delta modulator state + int32_t ds_x2; // Sigma delta modulator state + int32_t ds_x3; // Sigma delta modulator state +} sw_pll_sdm_state_t; + +/** + * \addtogroup sw_pll_api sw_pll_api + * + * The public API for using the RTOS I2C slave driver. + * @{ + */ + +typedef struct sw_pll_state_t{ + + sw_pll_lock_status_t lock_status; // State showing whether the PLL has locked or is under/over + uint8_t lock_counter; // Counter used to determine lock status + uint8_t first_loop; // Flag which indicates if the sw_pll is initialising or not + unsigned loop_rate_count; // How often the control loop logic runs compared to control call rate + unsigned loop_counter; // Intenal loop counter to determine when to do control + + sw_pll_pfd_state_t pfd_state; // Phase Frequency Detector + sw_pll_pi_state_t pi_state; // PI(II) controller + sw_pll_lut_state_t lut_state; // Look Up Table based DCO + sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO + +}sw_pll_state_t; + + +void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, + const sw_pll_15q16_t Kp, + const sw_pll_15q16_t Ki, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint32_t app_pll_frac_reg_val, + const unsigned ppm_range); + + diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h index ecc99f9d..ace7131f 100644 --- a/lib_sw_pll/src/sw_pll_pfd.h +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -10,16 +10,6 @@ #define SW_PLL_PFD_PRE_DIV_BITS 37 // Used pre-computing a divide to save on runtime div usage. Tradeoff between precision and max -typedef struct sw_pll_pfd_state_t{ - int16_t mclk_diff; // Raw difference between mclk count and expected mclk count - uint16_t ref_clk_pt_last; // Last ref clock value - uint32_t ref_clk_expected_inc; // Expected ref clock increment - uint64_t ref_clk_scaling_numerator; // Used for a cheap pre-computed divide rather than runtime divide - uint16_t mclk_pt_last; // The last mclk port timer count - uint32_t mclk_expected_pt_inc; // Expected increment of port timer count - uint16_t mclk_max_diff; // Maximum mclk_diff before control loop decides to skip that iteration -} sw_pll_pfd_state_t; - void sw_pll_pfd_init(sw_pll_pfd_state_t *pfd_state, const size_t loop_rate_count, const size_t pll_ratio, diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 6dc16e8d..8e4ffc3b 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -6,7 +6,6 @@ #define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked - void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, @@ -41,8 +40,15 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, } +void init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ + sdm_state->ds_x1 = 0; + sdm_state->ds_x2 = 0; + sdm_state->ds_x3 = 0; +} + + __attribute__((always_inline)) -inline int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) +int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { sw_pll->pi_state.error_accum += error; // Integral error. sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; @@ -58,6 +64,10 @@ inline int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, i return total_error; } +void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ + chan_out_word(c_sdm_control, dco_ctl); +} + sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { if (++sw_pll->loop_counter == sw_pll->loop_rate_count) diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h new file mode 100644 index 00000000..3baa23bf --- /dev/null +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -0,0 +1,55 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "sw_pll.h" + +#pragma once + +typedef int tileref_t; + +void init_sigma_delta(sw_pll_sdm_state_t *sdm_state); + +__attribute__((always_inline)) +static inline int32_t do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t ds_in){ + // Third order, 9 level output delta sigma. 20 bit unsigned input. + int32_t ds_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; + if (ds_out > 8){ + ds_out = 8; + } + if (ds_out < 0){ + ds_out = 0; + } + sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (ds_out<<9) - (ds_out<<8); + sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (ds_out<<14); + sdm_state->ds_x1 += ds_in - (ds_out<<17); + + return ds_out; +} + +__attribute__((always_inline)) +static inline uint32_t ds_out_to_frac_reg(int32_t ds_out){ + // bit 31 is frac enable + // bits 15..8 are the f value + // bits 7..0 are the p value + // Freq - F + (f + 1)/(p + 1) + uint32_t frac_val = 0; + + if (ds_out == 0){ + frac_val = 0x00000007; // step 0/8 + } + else{ + frac_val = ((ds_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8 + } + + return frac_val; +} + +__attribute__((always_inline)) +static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ + write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); +} + + +int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); +void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt); \ No newline at end of file From 7c82e6f3548aa2d9e5022912999f629f2be31a3e Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 18:21:55 +0000 Subject: [PATCH 051/118] Add initial SDM DCO test app --- CMakeLists.txt | 1 + tests/test_app_sdm_dco/CMakeLists.txt | 23 ++++++++ tests/test_app_sdm_dco/main.c | 78 +++++++++++++++++++++++++++ tests/test_app_sdm_dco/readme.txt | 0 4 files changed, 102 insertions(+) create mode 100644 tests/test_app_sdm_dco/CMakeLists.txt create mode 100644 tests/test_app_sdm_dco/main.c create mode 100644 tests/test_app_sdm_dco/readme.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index d16dc38a..6e134222 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,4 +22,5 @@ if(PROJECT_IS_TOP_LEVEL) add_subdirectory(modules/fwk_io) add_subdirectory(tests/test_app) add_subdirectory(tests/test_app_low_level_api) + add_subdirectory(tests/test_app_sdm_dco) endif() diff --git a/tests/test_app_sdm_dco/CMakeLists.txt b/tests/test_app_sdm_dco/CMakeLists.txt new file mode 100644 index 00000000..17ef842b --- /dev/null +++ b/tests/test_app_sdm_dco/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.21.0) + +add_executable(test_app_sdm_dco EXCLUDE_FROM_ALL main.c) + + +target_compile_options( + test_app_sdm_dco + PUBLIC + -g + -report + -fxscope + -target=XCORE-AI-EXPLORER +) + +target_link_options( + test_app_sdm_dco + PUBLIC + -report + -target=XCORE-AI-EXPLORER + -fcmdline-buffer-bytes=10000 # support for command line params +) + +target_link_libraries(test_app_sdm_dco PUBLIC lib_sw_pll) diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c new file mode 100644 index 00000000..41568712 --- /dev/null +++ b/tests/test_app_sdm_dco/main.c @@ -0,0 +1,78 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +/// +/// Application to call the control loop with the parameters fully +/// controllable by an external application. This app expects the +/// sw_pll_init parameters on the commannd line. These will be integers +/// for lut_table_base, skip the parameter in the list and append the whole +/// lut to the command line +/// +/// After init, the app will expect 2 integers to come in over stdin, These +/// are the mclk_pt and ref_pt. It will then run control and print out the +/// locked state and register value. +/// +/// +/// +#include "xs1.h" +#include +#include +#include +#include +#include +#include + +#define IN_LINE_SIZE 1000 + +int main(int argc, char** argv) { + + int i = 1; + + size_t loop_rate_count = atoi(argv[i++]); + fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); + size_t pll_ratio = atoi(argv[i++]); + fprintf(stderr, "pll_ratio\t\t%d\n", pll_ratio); + uint32_t ref_clk_expected_inc = atoi(argv[i++]); + fprintf(stderr, "ref_clk_expected_inc\t\t%lu\n", ref_clk_expected_inc); + unsigned ppm_range = atoi(argv[i++]); + fprintf(stderr, "ppm_range\t\t%d\n", ppm_range); + + if(i != argc) { + fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); + return 1; + } + + sw_pll_sdm_state_t sdm_state; + init_sigma_delta(&sdm_state); + + + for(;;) { + char read_buf[IN_LINE_SIZE]; + int len = 0; + for(;;) { + int val = fgetc(stdin); + if(EOF == val) { + return 0; + } + if('\n' == val) { + read_buf[len] = 0; + break; + } + else { + read_buf[len++] = val; + } + } + + int32_t ds_in; + sscanf(read_buf, "%ld", &ds_in); + fprintf(stderr, "%ld\n", ds_in); + uint32_t t0 = get_reference_time(); + // calc new ds_out and then wait to write + int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); + uint32_t frac_val = ds_out_to_frac_reg(ds_out); + uint32_t t1 = get_reference_time(); + + sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; + + printf("%ld %lu %d %lu\n", ds_out, frac_val, lock_status, t1 - t0); + } +} diff --git a/tests/test_app_sdm_dco/readme.txt b/tests/test_app_sdm_dco/readme.txt new file mode 100644 index 00000000..e69de29b From 0d261acd044775573d529883ec904e6893f03443 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 18:22:33 +0000 Subject: [PATCH 052/118] Keep param inside object in DCO --- python/sw_pll/dco_model.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index a8363560..24106a79 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -254,24 +254,25 @@ class sigma_delta_dco(sdm): or 1MHz: - 10ps jitter 100Hz-40kHz with very low freq noise floor -100dBc """ + + profiles = {"24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6}, + "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6}, + "24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6}, + "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6}} + + def __init__(self, profile): """ Create a sigmal delta DCO targetting either 24.576 or 22.5792MHz - """ - profiles = {"24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1}, - "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1}, - "24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1}, - "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1}} - + """ self.profile = profile self.p_value = 8 # 8 frac settings + 1 non frac setting - input_freq, F, R, f, p, OD, ACD = list(profiles[profile].values()) + input_freq, F, R, f, p, OD, ACD, _ = list(self.profiles[profile].values()) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) sdm.__init__(self) - def _sdm_out_to_freq(self, sdm_out): """ Translate the SDM steps to register settings @@ -293,7 +294,7 @@ def do_modulate(self, input): return frequency, lock_status - def print_stats(self, target_output_frequency): + def print_stats(self): """ Returns a summary of the SDM range and steps. """ @@ -301,6 +302,7 @@ def print_stats(self, target_output_frequency): steps = self.p_value + 1 # +1 we have frac off state min_freq = self._sdm_out_to_freq(0) max_freq = self._sdm_out_to_freq(self.p_value) + target_output_frequency = self.profiles[self.profile]["output_frequency"] ave_step_size = (max_freq - min_freq) / steps @@ -354,7 +356,7 @@ def write_register_file(self): sdm_dco = sigma_delta_dco("24.576_1M") sdm_dco.write_register_file() - sdm_dco.print_stats(24576000) + sdm_dco.print_stats() sdm_dco.plot_freq_range() for i in range(30): output_frequency = sdm_dco.do_modulate(500000) From 9bd2532c8b4b32f49efa5b5d85ab729051a90316 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 21 Nov 2023 18:22:54 +0000 Subject: [PATCH 053/118] WIP SDM DCO test --- tests/test_sdm_dco_equiv.py | 211 ++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 tests/test_sdm_dco_equiv.py diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py new file mode 100644 index 00000000..8af61336 --- /dev/null +++ b/tests/test_sdm_dco_equiv.py @@ -0,0 +1,211 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +""" +Assorted tests which run the test_app in xsim + +This file is structured as a fixture which takes a while to run +and generates a pandas.DataFrame containing some time domain +outputs from the control loops. Then a series of tests which +check different aspects of the content of this DataFrame. +""" + +import pytest +import numpy as np +import copy +from typing import Any +from dataclasses import dataclass, asdict +from pathlib import Path +from matplotlib import pyplot as plt + +from sw_pll.app_pll_model import app_pll_frac_calc +from sw_pll.dco_model import sigma_delta_dco + +from test_lib_sw_pll import bin_dir + + +DUT_XE_SDM_DCO = Path(__file__).parent / "../build/tests/test_app_pdf/test_app_sdm_dco.xe" + +@dataclass +class DutSDMDCOArgs: + loop_rate_count: int + pll_ratio: int + ref_clk_expected_inc: int + ppm_range: int + + +class SimDut: + """wrapper around sw_pll_ctrl so it works nicely with the tests""" + + def __init__(self, args: DutSDMDCOArgs, pll): + self.pll = pll + self.args = DutArgs(**asdict(args)) # copies the values + self.lut = self.args.lut + self.args.lut = len(self.lut) + nominal_control_rate_hz = args.target_output_frequency / args.pll_ratio / args.loop_rate_count + self.ctrl = sim_sw_pll_lut( + args.target_output_frequency, + nominal_control_rate_hz, + args.kp, + args.ki, ) + + + def __enter__(self): + """support context manager""" + return self + + def __exit__(self, *_): + """Support context manager. Nothing to do""" + + def do_control(self, mclk_pt, _ref_pt): + """ + Execute control using simulator + """ + f, l = self.ctrl.do_control_loop(mclk_pt) + + return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 + +class Dut_SDM_DCO: + """ + run DCO in xsim and provide access to the sdm function + """ + + def __init__(self, args: DutSDMDCOArgs, pll, xe_file=DUT_XE_SDM_DCO): + self.pll = pll + self.args = DutArgs(**asdict(args)) # copies the values + self.args.kp = self.args.kp + self.args.ki = self.args.ki + lut = self.args.lut + self.args.lut = len(args.lut) + # concatenate the parameters to the init function and the whole lut + # as the command line parameters to the xe. + list_args = [*(str(i) for i in asdict(self.args).values())] + [ + str(i) for i in lut + ] + + cmd = ["xsim", "--args", str(xe_file), *list_args] + + print(" ".join(cmd)) + + self.lut = lut + self._process = Popen( + cmd, + stdin=PIPE, + stdout=PIPE, + encoding="utf-8", + ) + + def __enter__(self): + """support context manager""" + return self + + def __exit__(self, *_): + """support context manager""" + self.close() + + def do_modulate(self, ds_in): + """ + returns ..... + """ + self._process.stdin.write(f"{ds_in}\n") + self._process.stdin.flush() + + ds_out, frac_val, locked, ticks = self._process.stdout.readline().strip().split() + + self.pll.update_frac_reg(int(reg, 16)) + return int(locked), self.pll.get_output_frequency(), int(ticks) + + def close(self): + """Send EOF to xsim and wait for it to exit""" + self._process.stdin.close() + self._process.wait() + +def test_sdm_dco_equivalence(bin_dir): + """ + Simple low level test of equivalence using do_control_from_error + Feed in random numbers into C and Python DUTs and see if we get the same results + """ + + available_profiles = list(sigma_delta_dco.profiles.keys()) + profile = available_profiles[0] + + dco_sim = sigma_delta_dco(profile) + dco_sim.write_register_file() + + dco_sim.print_stats() + + dco_dut = Dut_SDM_DCO() + + for ds_in in [400000] * 10: + sdm_out, lock_status = dco.do_modulate(ds_in) + print(sdm_out, lock_status) + + + # pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) + + # pll.update_frac_reg(start_reg) + + # input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) + # print(f"input_errors: {input_errors}") + + # result_categories = { + # "mclk": [], + # "locked": [], + # "time": [], + # "clk_diff": [], + # "clk_diff_i": [], + # "first_loop": [], + # "ticks": [] + # } + # names = ["C", "Python"] + # duts = [Dut(args, pll, xe_file=DUT_XE_LOW_LEVEL), SimDut(args, pll)] + + # results = {} + # for name in names: + # results[name] = copy.deepcopy(result_categories) + + # for dut, name in zip(duts, names): + # _, mclk_f, *_ = dut.do_control_from_error(0) + + # locked = -1 + # time = 0 + # print(f"Running: {name}") + # for input_error in input_errors: + + # locked, mclk_f, e, ea, fl, ticks = dut.do_control_from_error(input_error) + + # results[name]["mclk"].append(mclk_f) + # results[name]["time"].append(time) + # results[name]["locked"].append(locked) + # results[name]["clk_diff"].append(e) + # results[name]["clk_diff_i"].append(ea) + # results[name]["first_loop"].append(fl) + # results[name]["ticks"].append(ticks) + # time += 1 + + # # print(name, time, input_error, mclk_f) + + # # Plot mclk output dut vs dut + # duts = list(results.keys()) + # for dut in duts: + # mclk = results[dut]["mclk"] + # times = results[dut]["time"] + # clk_diff = results[dut]["clk_diff"] + # clk_diff_i = results[dut]["clk_diff_i"] + # locked = results[dut]["locked"] + + # plt.plot(mclk, label=dut) + + # plt.legend(loc="upper left") + # plt.xlabel("Iteration") + # plt.ylabel("mclk") + # plt.savefig(bin_dir/f"c-vs-python-low-level-equivalence-mclk.png") + # plt.close() + + # # Check for equivalence + # for compare_item in ["mclk", "clk_diff", "clk_diff_i"]: + # C = results["C"][compare_item] + # Python = results["Python"][compare_item] + # assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" + + # print("TEST PASSED!") + From 52138600b40b5bf4014ea3af8144465e16dc6a39 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 22 Nov 2023 13:00:29 +0000 Subject: [PATCH 054/118] Basic working SDM DCO test --- python/sw_pll/app_pll_model.py | 14 ++++++ python/sw_pll/dco_model.py | 13 +++-- tests/test_app_sdm_dco/main.c | 22 ++------- tests/test_sdm_dco_equiv.py | 90 +++++++++++++--------------------- 4 files changed, 59 insertions(+), 80 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 413741d0..27a76423 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -84,6 +84,7 @@ def update_frac(self, f, p, fractional=True): """ self.f = f self.p = p + # print(f"update_frac f:{self.f} p:{self.p}") self.fractional_enable = fractional return self.calc_frequency() @@ -98,6 +99,19 @@ def update_frac_reg(self, reg): return self.update_frac(f, p) + + def get_frac_reg(self): + """ + Returns the fractional reg value from current setting + """ + # print(f"get_frac_reg f:{self.f} p:{self.p}") + if self.fractional_enable: + reg = 0x80000000 | self.p | (self.f << 8) + return reg + + else: + return None + def gen_register_file_text(self): """ Helper used to generate text for the register setup h file diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 24106a79..e88abbc2 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -271,6 +271,9 @@ def __init__(self, profile): input_freq, F, R, f, p, OD, ACD, _ = list(self.profiles[profile].values()) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) + self.sdm_out = 0 + self.f = 0 + sdm.__init__(self) def _sdm_out_to_freq(self, sdm_out): @@ -279,18 +282,20 @@ def _sdm_out_to_freq(self, sdm_out): """ if sdm_out == 0: # Step 0 - return self.app_pll.update_frac(0, 0, False) + self.f = 0 + return self.app_pll.update_frac(self.f, 0, False) else: # Steps 1 to 8 inclusive - return self.app_pll.update_frac(sdm_out - 1, self.p_value - 1) + self.f = sdm_out - 1 + return self.app_pll.update_frac(self.f, self.p_value - 1) def do_modulate(self, input): """ Input a control value and output a SDM signal """ - sdm_out, lock_status = sdm.do_sigma_delta(self, input) + self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) - frequency = self._sdm_out_to_freq(sdm_out) + frequency = self._sdm_out_to_freq(self.sdm_out) return frequency, lock_status diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c index 41568712..bf7e7612 100644 --- a/tests/test_app_sdm_dco/main.c +++ b/tests/test_app_sdm_dco/main.c @@ -25,26 +25,9 @@ int main(int argc, char** argv) { - int i = 1; - - size_t loop_rate_count = atoi(argv[i++]); - fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); - size_t pll_ratio = atoi(argv[i++]); - fprintf(stderr, "pll_ratio\t\t%d\n", pll_ratio); - uint32_t ref_clk_expected_inc = atoi(argv[i++]); - fprintf(stderr, "ref_clk_expected_inc\t\t%lu\n", ref_clk_expected_inc); - unsigned ppm_range = atoi(argv[i++]); - fprintf(stderr, "ppm_range\t\t%d\n", ppm_range); - - if(i != argc) { - fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); - return 1; - } - sw_pll_sdm_state_t sdm_state; init_sigma_delta(&sdm_state); - for(;;) { char read_buf[IN_LINE_SIZE]; int len = 0; @@ -64,9 +47,10 @@ int main(int argc, char** argv) { int32_t ds_in; sscanf(read_buf, "%ld", &ds_in); - fprintf(stderr, "%ld\n", ds_in); - uint32_t t0 = get_reference_time(); + // fprintf(stderr, "%ld\n", ds_in); + // calc new ds_out and then wait to write + uint32_t t0 = get_reference_time(); int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); uint32_t frac_val = ds_out_to_frac_reg(ds_out); uint32_t t1 = get_reference_time(); diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 8af61336..766d8db5 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -16,77 +16,38 @@ from dataclasses import dataclass, asdict from pathlib import Path from matplotlib import pyplot as plt +from subprocess import Popen, PIPE -from sw_pll.app_pll_model import app_pll_frac_calc + +# from sw_pll.app_pll_model import app_pll_frac_calc from sw_pll.dco_model import sigma_delta_dco from test_lib_sw_pll import bin_dir -DUT_XE_SDM_DCO = Path(__file__).parent / "../build/tests/test_app_pdf/test_app_sdm_dco.xe" +DUT_XE_SDM_DCO = Path(__file__).parent / "../build/tests/test_app_sdm_dco/test_app_sdm_dco.xe" @dataclass class DutSDMDCOArgs: - loop_rate_count: int - pll_ratio: int - ref_clk_expected_inc: int - ppm_range: int - - -class SimDut: - """wrapper around sw_pll_ctrl so it works nicely with the tests""" - - def __init__(self, args: DutSDMDCOArgs, pll): - self.pll = pll - self.args = DutArgs(**asdict(args)) # copies the values - self.lut = self.args.lut - self.args.lut = len(self.lut) - nominal_control_rate_hz = args.target_output_frequency / args.pll_ratio / args.loop_rate_count - self.ctrl = sim_sw_pll_lut( - args.target_output_frequency, - nominal_control_rate_hz, - args.kp, - args.ki, ) - - - def __enter__(self): - """support context manager""" - return self - - def __exit__(self, *_): - """Support context manager. Nothing to do""" - - def do_control(self, mclk_pt, _ref_pt): - """ - Execute control using simulator - """ - f, l = self.ctrl.do_control_loop(mclk_pt) + dummy: int - return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 class Dut_SDM_DCO: """ run DCO in xsim and provide access to the sdm function """ - def __init__(self, args: DutSDMDCOArgs, pll, xe_file=DUT_XE_SDM_DCO): - self.pll = pll - self.args = DutArgs(**asdict(args)) # copies the values - self.args.kp = self.args.kp - self.args.ki = self.args.ki - lut = self.args.lut - self.args.lut = len(args.lut) + def __init__(self, pll, args:DutSDMDCOArgs, xe_file=DUT_XE_SDM_DCO): + self.args = DutSDMDCOArgs(**asdict(args)) # copies the values # concatenate the parameters to the init function and the whole lut # as the command line parameters to the xe. - list_args = [*(str(i) for i in asdict(self.args).values())] + [ - str(i) for i in lut - ] + list_args = [*(str(i) for i in asdict(self.args).values())] cmd = ["xsim", "--args", str(xe_file), *list_args] print(" ".join(cmd)) - self.lut = lut + self.pll = pll.app_pll self._process = Popen( cmd, stdin=PIPE, @@ -104,15 +65,22 @@ def __exit__(self, *_): def do_modulate(self, ds_in): """ - returns ..... + returns sigma delta out, calculated frac val, lock status and timing """ self._process.stdin.write(f"{ds_in}\n") self._process.stdin.flush() - ds_out, frac_val, locked, ticks = self._process.stdout.readline().strip().split() + from_dut = self._process.stdout.readline().strip() + ds_out, frac_val, locked, ticks = from_dut.split() - self.pll.update_frac_reg(int(reg, 16)) - return int(locked), self.pll.get_output_frequency(), int(ticks) + frac_val = int(frac_val) + if frac_val & 0x80000000: + self.pll.update_frac(0, 0, fractional=True) + frequency = self.pll.update_frac_reg(frac_val) + else: + frequency = self.pll.update_frac(0, 0, fractional=False) + + return int(ds_out), int(frac_val), frequency, int(locked), int(ticks) def close(self): """Send EOF to xsim and wait for it to exit""" @@ -125,6 +93,10 @@ def test_sdm_dco_equivalence(bin_dir): Feed in random numbers into C and Python DUTs and see if we get the same results """ + args = DutSDMDCOArgs( + dummy = 0 + ) + available_profiles = list(sigma_delta_dco.profiles.keys()) profile = available_profiles[0] @@ -133,12 +105,16 @@ def test_sdm_dco_equivalence(bin_dir): dco_sim.print_stats() - dco_dut = Dut_SDM_DCO() - - for ds_in in [400000] * 10: - sdm_out, lock_status = dco.do_modulate(ds_in) - print(sdm_out, lock_status) + dut_pll = sigma_delta_dco(profile) + dco_dut = Dut_SDM_DCO(dut_pll, args) + for ds_in in [400000] * 20: + frequency, lock_status = dco_sim.do_modulate(ds_in) + sim_frac_reg = dco_sim.app_pll.get_frac_reg() + if sim_frac_reg is None: sim_frac_reg = 0x00000007 + print(f"SIM: {dco_sim.sdm_out} {sim_frac_reg:#x} {frequency} {lock_status}") + sdm_out, frac_val, frequency, lock_status, ticks = dco_dut.do_modulate(ds_in) + print(f"DUT: {sdm_out} {frac_val:#x} {frequency} {lock_status} {ticks}\n") # pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) From 589d5b5d9a315402b0830da55c3398717be154d2 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 22 Nov 2023 14:39:15 +0000 Subject: [PATCH 055/118] Finished DCO SDM test but fails after 20 iters --- python/sw_pll/app_pll_model.py | 19 +++--- python/sw_pll/dco_model.py | 48 +++++++------- tests/test_lib_sw_pll.py | 8 +-- tests/test_lib_sw_pll_equiv.py | 2 +- tests/test_sdm_dco_equiv.py | 112 ++++++++------------------------- 5 files changed, 67 insertions(+), 122 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 27a76423..402236da 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -19,6 +19,9 @@ class app_pll_frac_calc: To keep the inherent jitter of the PLL output down to a minimum, it is recommended that R be kept small, ideally = 0 (which equiates to 1) but reduces lock range. """ + + frac_enable_mask = 0x80000000 + def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD_init, verbose=False): self.input_frequency = input_frequency self.F = F_init @@ -78,14 +81,16 @@ def update_all(self, F, R, OD, ACD, f, p): self.p = p return self.calc_frequency() - def update_frac(self, f, p, fractional=True): + def update_frac(self, f, p, fractional=None): """ Update only the fractional parts of the App PLL """ self.f = f self.p = p # print(f"update_frac f:{self.f} p:{self.p}") - self.fractional_enable = fractional + if fractional is not None: + self.fractional_enable = fractional + return self.calc_frequency() def update_frac_reg(self, reg): @@ -95,7 +100,8 @@ def update_frac_reg(self, reg): """ f = int((reg >> 8) & ((2**8)-1)) p = int(reg & ((2**8)-1)) - assert self.fractional_enable is True + + self.fractional_enable = True if (reg & self.frac_enable_mask) else False return self.update_frac(f, p) @@ -105,12 +111,11 @@ def get_frac_reg(self): Returns the fractional reg value from current setting """ # print(f"get_frac_reg f:{self.f} p:{self.p}") + reg = self.p | (self.f << 8) if self.fractional_enable: - reg = 0x80000000 | self.p | (self.f << 8) - return reg + reg |= self.frac_enable_mask - else: - return None + return reg def gen_register_file_text(self): """ diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index e88abbc2..eee44b7c 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -44,7 +44,7 @@ def __init__(self, header_file = "fractions.h", verbose=False): # fixed header input_freq, F, R, f, p, OD, ACD = self._parse_register_file(register_file) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) - self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2]) + self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2] & app_pll_frac_calc.frac_enable_mask) self.lock_status = -1 def _read_lut_header(self, header_file): @@ -120,13 +120,13 @@ def print_stats(self, target_output_frequency): steps = np.size(lut) register = int(lut[0]) - min_freq = self.app_pll.update_frac_reg(register) + min_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) register = int(lut[steps // 2]) - mid_freq = self.app_pll.update_frac_reg(register) + mid_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) register = int(lut[-1]) - max_freq = self.app_pll.update_frac_reg(register) + max_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) ave_step_size = (max_freq - min_freq) / steps @@ -151,7 +151,7 @@ def plot_freq_range(self): frequencies = [] for step in range(self.get_lut_size()): register = int(self.lut[step]) - self.app_pll.update_frac_reg(register) + self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) frequencies.append(self.app_pll.get_output_frequency()) plt.clf() @@ -187,7 +187,7 @@ def get_frequency_from_dco_control(self, dco_ctrl): register = int(self.lut[set_point]) - output_frequency = self.app_pll.update_frac_reg(register) + output_frequency = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) self.last_output_frequency = output_frequency return output_frequency, self.lock_status @@ -204,41 +204,41 @@ class sdm: """ def __init__(self): # Delta sigma modulator state - self.ds_x1 = 0 - self.ds_x2 = 0 - self.ds_x3 = 0 + self.sdm_x1 = 0 + self.sdm_x2 = 0 + self.sdm_x3 = 0 - self.ds_in_max = 980000 - self.ds_in_min = 60000 + self.sdm_in_max = 980000 + self.sdm_in_min = 60000 self.lock_status = -1 # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) - def do_sigma_delta(self, ds_in): - if ds_in > self.ds_in_max: - print(f"SDM Pos clip: {ds_in}, {self.ds_in_max}") - ds_in = self. ds_in_max + def do_sigma_delta(self, sdm_in): + if sdm_in > self.sdm_in_max: + print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") + sdm_in = self. sdm_in_max self.lock_status = 1 - elif ds_in < self.ds_in_min: - print(f"SDM Neg clip: {ds_in}, {self.ds_in_min}") - ds_in = self.ds_in_min + elif app_pll_frac_calc.frac_enable_mask < self.app_pll_frac_calc.frac_enable_mask_min: + print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") + sdm_in = self.sdm_in_min self.lock_status = -1 else: self.lock_status = 0 - sdm_out = int(self.ds_x3 * 0.002197265625) + sdm_out = int(self.sdm_x3 * 0.002197265625) if sdm_out > 8: sdm_out = 8 if sdm_out < 0: sdm_out = 0 - self.ds_x3 += int((self.ds_x2 * 0.03125) - (sdm_out * 768)) - self.ds_x2 += int((self.ds_x1 * 0.03125) - (sdm_out * 16384)) - self.ds_x1 += int(ds_in - (sdm_out * 131072)) + self.sdm_x3 += int((self.sdm_x2 * 0.03125) - (sdm_out * 768)) + self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) + self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) return sdm_out, self.lock_status @@ -283,11 +283,11 @@ def _sdm_out_to_freq(self, sdm_out): if sdm_out == 0: # Step 0 self.f = 0 - return self.app_pll.update_frac(self.f, 0, False) + return self.app_pll.update_frac(self.f, self.p_value - 1, False) else: # Steps 1 to 8 inclusive self.f = sdm_out - 1 - return self.app_pll.update_frac(self.f, self.p_value - 1) + return self.app_pll.update_frac(self.f, self.p_value - 1, True) def do_modulate(self, input): """ diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 191803e1..5be16b5a 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -132,7 +132,7 @@ def do_control(self, mclk_pt, ref_pt): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_frac_reg(int(reg, 16)) + self.pll.update_frac_reg(int(reg, 16) & app_pll_frac_calc.frac_enable_mask) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) def do_control_from_error(self, error): @@ -144,7 +144,7 @@ def do_control_from_error(self, error): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_frac_reg(int(reg, 16)) + self.pll.update_frac_reg(int(reg, 16) & app_pll_frac_calc.frac_enable_mask) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) @@ -235,7 +235,7 @@ def basic_test_vector(request, solution_12288, bin_dir): frequency_lut = [] for reg in sol.lut: - pll.update_frac_reg(reg) + pll.update_frac_reg(reg & app_pll_frac_calc.frac_enable_mask) frequency_lut.append(pll.get_output_frequency()) frequency_range_frac = (frequency_lut[-1] - frequency_lut[0])/frequency_lut[0] @@ -244,7 +244,7 @@ def basic_test_vector(request, solution_12288, bin_dir): plt.savefig(bin_dir/f"lut-{name}.png") plt.close() - pll.update_frac_reg(start_reg) + pll.update_frac_reg(start_reg & app_pll_frac_calc.frac_enable_mask) input_freqs = { "perfect": target_ref_f, diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 09eb7e35..4f42016b 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -60,7 +60,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) - pll.update_frac_reg(start_reg) + pll.update_frac_reg(start_reg & frac_enable_mask) input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) print(f"input_errors: {input_errors}") diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 766d8db5..4650123d 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -63,24 +63,20 @@ def __exit__(self, *_): """support context manager""" self.close() - def do_modulate(self, ds_in): + def do_modulate(self, sdm_in): """ returns sigma delta out, calculated frac val, lock status and timing """ - self._process.stdin.write(f"{ds_in}\n") + self._process.stdin.write(f"{sdm_in}\n") self._process.stdin.flush() from_dut = self._process.stdout.readline().strip() - ds_out, frac_val, locked, ticks = from_dut.split() + sdm_out, frac_val, locked, ticks = from_dut.split() frac_val = int(frac_val) - if frac_val & 0x80000000: - self.pll.update_frac(0, 0, fractional=True) - frequency = self.pll.update_frac_reg(frac_val) - else: - frequency = self.pll.update_frac(0, 0, fractional=False) - - return int(ds_out), int(frac_val), frequency, int(locked), int(ticks) + frequency = self.pll.update_frac_reg(frac_val) + + return int(sdm_out), int(frac_val), frequency, int(locked), int(ticks) def close(self): """Send EOF to xsim and wait for it to exit""" @@ -108,80 +104,24 @@ def test_sdm_dco_equivalence(bin_dir): dut_pll = sigma_delta_dco(profile) dco_dut = Dut_SDM_DCO(dut_pll, args) - for ds_in in [400000] * 20: - frequency, lock_status = dco_sim.do_modulate(ds_in) - sim_frac_reg = dco_sim.app_pll.get_frac_reg() - if sim_frac_reg is None: sim_frac_reg = 0x00000007 - print(f"SIM: {dco_sim.sdm_out} {sim_frac_reg:#x} {frequency} {lock_status}") - sdm_out, frac_val, frequency, lock_status, ticks = dco_dut.do_modulate(ds_in) - print(f"DUT: {sdm_out} {frac_val:#x} {frequency} {lock_status} {ticks}\n") - - # pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) - - # pll.update_frac_reg(start_reg) - - # input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) - # print(f"input_errors: {input_errors}") - - # result_categories = { - # "mclk": [], - # "locked": [], - # "time": [], - # "clk_diff": [], - # "clk_diff_i": [], - # "first_loop": [], - # "ticks": [] - # } - # names = ["C", "Python"] - # duts = [Dut(args, pll, xe_file=DUT_XE_LOW_LEVEL), SimDut(args, pll)] - - # results = {} - # for name in names: - # results[name] = copy.deepcopy(result_categories) - - # for dut, name in zip(duts, names): - # _, mclk_f, *_ = dut.do_control_from_error(0) - - # locked = -1 - # time = 0 - # print(f"Running: {name}") - # for input_error in input_errors: - - # locked, mclk_f, e, ea, fl, ticks = dut.do_control_from_error(input_error) - - # results[name]["mclk"].append(mclk_f) - # results[name]["time"].append(time) - # results[name]["locked"].append(locked) - # results[name]["clk_diff"].append(e) - # results[name]["clk_diff_i"].append(ea) - # results[name]["first_loop"].append(fl) - # results[name]["ticks"].append(ticks) - # time += 1 - - # # print(name, time, input_error, mclk_f) - - # # Plot mclk output dut vs dut - # duts = list(results.keys()) - # for dut in duts: - # mclk = results[dut]["mclk"] - # times = results[dut]["time"] - # clk_diff = results[dut]["clk_diff"] - # clk_diff_i = results[dut]["clk_diff_i"] - # locked = results[dut]["locked"] - - # plt.plot(mclk, label=dut) - - # plt.legend(loc="upper left") - # plt.xlabel("Iteration") - # plt.ylabel("mclk") - # plt.savefig(bin_dir/f"c-vs-python-low-level-equivalence-mclk.png") - # plt.close() - - # # Check for equivalence - # for compare_item in ["mclk", "clk_diff", "clk_diff_i"]: - # C = results["C"][compare_item] - # Python = results["Python"][compare_item] - # assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" - - # print("TEST PASSED!") + max_ticks = 0 + + for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 50): + frequency_sim, lock_status_sim = dco_sim.do_modulate(sdm_in) + frac_reg_sim = dco_sim.app_pll.get_frac_reg() + + print(f"SIM: {sdm_in} {dco_sim.sdm_out} {frac_reg_sim:#x} {frequency_sim} {lock_status_sim}") + + sdm_out_dut, frac_reg_dut, frequency_dut, lock_status_dut, ticks = dco_dut.do_modulate(sdm_in) + print(f"DUT: {sdm_in} {sdm_out_dut} {frac_reg_dut:#x} {frequency_dut} {lock_status_dut} {ticks}\n") + + max_ticks = ticks if ticks > max_ticks else max_ticks + + assert dco_sim.sdm_out == sdm_out_dut + assert frac_reg_sim == frac_reg_dut + assert frequency_sim == frequency_dut + assert lock_status_sim == lock_status_dut + + + print("TEST PASSED!") From d5006414df6ef107d154d156f6ba567913812e90 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 22 Nov 2023 17:12:51 +0000 Subject: [PATCH 056/118] SDM ctrl test firmware --- CMakeLists.txt | 1 + python/sw_pll/dco_model.py | 53 ++++++++-- tests/test_app_sdm_ctrl/CMakeLists.txt | 23 ++++ tests/test_app_sdm_ctrl/main.c | 139 +++++++++++++++++++++++++ tests/test_app_sdm_ctrl/readme.txt | 0 tests/test_lib_sw_pll_equiv.py | 2 +- tests/test_sdm_dco_equiv.py | 2 +- 7 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 tests/test_app_sdm_ctrl/CMakeLists.txt create mode 100644 tests/test_app_sdm_ctrl/main.c create mode 100644 tests/test_app_sdm_ctrl/readme.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e134222..3cfcc1bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,4 +23,5 @@ if(PROJECT_IS_TOP_LEVEL) add_subdirectory(tests/test_app) add_subdirectory(tests/test_app_low_level_api) add_subdirectory(tests/test_app_sdm_dco) + add_subdirectory(tests/test_app_sdm_ctrl) endif() diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index eee44b7c..23e7b132 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -90,13 +90,13 @@ def _parse_register_file(self, register_file): with open(register_file) as rf: reg_file = rf.read().replace('\n', '') - input_freq = int(re.search(".+Input freq:\s+(\d+).+", reg_file).groups()[0]) - F = int(re.search(".+F:\s+(\d+).+", reg_file).groups()[0]) - R = int(re.search(".+R:\s+(\d+).+", reg_file).groups()[0]) - f = int(re.search(".+f:\s+(\d+).+", reg_file).groups()[0]) - p = int(re.search(".+p:\s+(\d+).+", reg_file).groups()[0]) - OD = int(re.search(".+OD:\s+(\d+).+", reg_file).groups()[0]) - ACD = int(re.search(".+ACD:\s+(\d+).+", reg_file).groups()[0]) + input_freq = int(re.search(r".+Input freq:\s+(\d+).+", reg_file).groups()[0]) + F = int(re.search(r".+F:\s+(\d+).+", reg_file).groups()[0]) + R = int(re.search(r".+R:\s+(\d+).+", reg_file).groups()[0]) + f = int(re.search(r".+f:\s+(\d+).+", reg_file).groups()[0]) + p = int(re.search(r".+p:\s+(\d+).+", reg_file).groups()[0]) + OD = int(re.search(r".+OD:\s+(\d+).+", reg_file).groups()[0]) + ACD = int(re.search(r".+ACD:\s+(\d+).+", reg_file).groups()[0]) return input_freq, F, R, f, p, OD, ACD @@ -215,13 +215,14 @@ def __init__(self): # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) + # This is work in progress - the integer model matches the firmware better def do_sigma_delta(self, sdm_in): if sdm_in > self.sdm_in_max: print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") sdm_in = self. sdm_in_max self.lock_status = 1 - elif app_pll_frac_calc.frac_enable_mask < self.app_pll_frac_calc.frac_enable_mask_min: + elif sdm_in < self.sdm_in_min: print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") sdm_in = self.sdm_in_min self.lock_status = -1 @@ -240,6 +241,37 @@ def do_sigma_delta(self, sdm_in): self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) + return int(sdm_out), self.lock_status + + def do_sigma_delta_int(self, sdm_in): + # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) + # Third order, 9 level output delta sigma. 20 bit unsigned input. + sdm_in = int(sdm_in) + + if sdm_in > self.sdm_in_max: + print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") + sdm_in = self. sdm_in_max + self.lock_status = 1 + + elif sdm_in < self.sdm_in_min: + print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") + sdm_in = self.sdm_in_min + self.lock_status = -1 + + else: + self.lock_status = 0 + + sdm_out = ((self.sdm_x3<<4) + (self.sdm_x3<<1)) >> 13 + + if sdm_out > 8: + sdm_out = 8 + if sdm_out < 0: + sdm_out = 0 + + self.sdm_x3 += (self.sdm_x2>>5) - (sdm_out<<9) - (sdm_out<<8) + self.sdm_x2 += (self.sdm_x1>>5) - (sdm_out<<14) + self.sdm_x1 += sdm_in - (sdm_out<<17) + return sdm_out, self.lock_status @@ -289,11 +321,12 @@ def _sdm_out_to_freq(self, sdm_out): self.f = sdm_out - 1 return self.app_pll.update_frac(self.f, self.p_value - 1, True) - def do_modulate(self, input): + def do_modulate(self, ctrl_input): """ Input a control value and output a SDM signal """ - self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) + # self.sdm_out, lock_status = sdm.do_sigma_delta(self, ctrl_input) + self.sdm_out, lock_status = sdm.do_sigma_delta_int(self, ctrl_input) frequency = self._sdm_out_to_freq(self.sdm_out) diff --git a/tests/test_app_sdm_ctrl/CMakeLists.txt b/tests/test_app_sdm_ctrl/CMakeLists.txt new file mode 100644 index 00000000..257fcad2 --- /dev/null +++ b/tests/test_app_sdm_ctrl/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.21.0) + +add_executable(test_app_sdm_ctrl EXCLUDE_FROM_ALL main.c) + + +target_compile_options( + test_app_sdm_ctrl + PUBLIC + -g + -report + -fxscope + -target=XCORE-AI-EXPLORER +) + +target_link_options( + test_app_sdm_ctrl + PUBLIC + -report + -target=XCORE-AI-EXPLORER + -fcmdline-buffer-bytes=10000 # support for command line params +) + +target_link_libraries(test_app_sdm_ctrl PUBLIC lib_sw_pll) diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c new file mode 100644 index 00000000..ccee6198 --- /dev/null +++ b/tests/test_app_sdm_ctrl/main.c @@ -0,0 +1,139 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +/// +/// Application to call the control loop with the parameters fully +/// controllable by an external application. This app expects the +/// sw_pll_init parameters on the commannd line. These will be integers +/// for lut_table_base, skip the parameter in the list and append the whole +/// lut to the command line +/// +/// After init, the app will expect 2 integers to come in over stdin, These +/// are the mclk_pt and ref_pt. It will then run control and print out the +/// locked state and register value. +/// +/// +/// +#include "xs1.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IN_LINE_SIZE 1000 + +DECLARE_JOB(control_task, (int, char**, chanend_t)); +void control_task(int argc, char** argv, chanend_t c_sdm_control) { + + int i = 1; + + float kp = atoi(argv[i++]); + fprintf(stderr, "kp\t\t%f\n", kp); + float ki = atoi(argv[i++]); + fprintf(stderr, "ki\t\t%f\n", ki); + size_t loop_rate_count = atoi(argv[i++]); + fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); + size_t pll_ratio = atoi(argv[i++]); + fprintf(stderr, "pll_ratio\t\t%d\n", pll_ratio); + uint32_t ref_clk_expected_inc = atoi(argv[i++]); + fprintf(stderr, "ref_clk_expected_inc\t\t%lu\n", ref_clk_expected_inc); + uint32_t app_pll_ctl_reg_val = atoi(argv[i++]); + fprintf(stderr, "app_pll_ctl_reg_val\t\t%lu\n", app_pll_ctl_reg_val); + uint32_t app_pll_div_reg_val = atoi(argv[i++]); + fprintf(stderr, "app_pll_div_reg_val\t\t%lu\n", app_pll_div_reg_val); + uint32_t app_pll_frac_reg_val = atoi(argv[i++]); + fprintf(stderr, "app_pll_frac_reg_val\t\t%lu\n", app_pll_frac_reg_val); + unsigned ppm_range = atoi(argv[i++]); + fprintf(stderr, "ppm_range\t\t%d\n", ppm_range); + unsigned target_output_frequency = atoi(argv[i++]); + fprintf(stderr, "target_output_frequency\t\t%d\n", target_output_frequency); + + if(i != argc) { + fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); + exit(1); + } + + sw_pll_state_t sw_pll; + + sw_pll_sdm_init(&sw_pll, + SW_PLL_15Q16(kp), + SW_PLL_15Q16(ki), + loop_rate_count, + pll_ratio, + ref_clk_expected_inc, + app_pll_ctl_reg_val, + app_pll_div_reg_val, + app_pll_frac_reg_val, + ppm_range); + + + for(;;) { + char read_buf[IN_LINE_SIZE]; + int len = 0; + for(;;) { + int val = fgetc(stdin); + if(EOF == val) { + exit(0); + } + if('\n' == val) { + read_buf[len] = 0; + break; + } + else { + read_buf[len++] = val; + } + } + + int16_t mclk_diff; + sscanf(read_buf, "%hd", &mclk_diff); + + uint32_t t0 = get_reference_time(); + int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, mclk_diff); + uint32_t t1 = get_reference_time(); + + printf("%hd %lu %d %lu\n", mclk_diff, error, sw_pll.lock_status, t1 - t0); + } +} + +DECLARE_JOB(sdm_dummy, (chanend_t)); +void sdm_dummy(chanend_t c_sdm_control){ + int running = 1; + + int ds_in = 0; + while(running){ + // Poll for new SDM control value + SELECT_RES( + CASE_THEN(c_sdm_control, ctrl_update), + DEFAULT_THEN(default_handler) + ) + { + ctrl_update: + { + ds_in = chan_in_word(c_sdm_control); + fprintf(stderr, "%d\n", ds_in); + } + break; + + default_handler: + { + // Do nothing & fall-through + } + break; + } + } +} + + +int main(int argc, char** argv) { + + channel_t c_sdm_control = chan_alloc(); + + PAR_JOBS(PJOB(control_task, (argc, argv, c_sdm_control.end_a)), + PJOB(sdm_dummy, (c_sdm_control.end_a))); + + return 0; +} \ No newline at end of file diff --git a/tests/test_app_sdm_ctrl/readme.txt b/tests/test_app_sdm_ctrl/readme.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 4f42016b..074333a8 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -60,7 +60,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) - pll.update_frac_reg(start_reg & frac_enable_mask) + pll.update_frac_reg(start_reg & app_pll_frac_calc.frac_enable_mask) input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) print(f"input_errors: {input_errors}") diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 4650123d..5003c183 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -106,7 +106,7 @@ def test_sdm_dco_equivalence(bin_dir): max_ticks = 0 - for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 50): + for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 100): frequency_sim, lock_status_sim = dco_sim.do_modulate(sdm_in) frac_reg_sim = dco_sim.app_pll.get_frac_reg() From 9b058643dd3163bcdb65f3e43b2c992c50ac8bd9 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 22 Nov 2023 17:50:52 +0000 Subject: [PATCH 057/118] Fix mask op --- python/sw_pll/dco_model.py | 65 +++++++++------------------------- tests/test_lib_sw_pll.py | 8 ++--- tests/test_lib_sw_pll_equiv.py | 2 +- 3 files changed, 21 insertions(+), 54 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 23e7b132..0befc278 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -44,7 +44,7 @@ def __init__(self, header_file = "fractions.h", verbose=False): # fixed header input_freq, F, R, f, p, OD, ACD = self._parse_register_file(register_file) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) - self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2] & app_pll_frac_calc.frac_enable_mask) + self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2] | app_pll_frac_calc.frac_enable_mask) self.lock_status = -1 def _read_lut_header(self, header_file): @@ -90,13 +90,13 @@ def _parse_register_file(self, register_file): with open(register_file) as rf: reg_file = rf.read().replace('\n', '') - input_freq = int(re.search(r".+Input freq:\s+(\d+).+", reg_file).groups()[0]) - F = int(re.search(r".+F:\s+(\d+).+", reg_file).groups()[0]) - R = int(re.search(r".+R:\s+(\d+).+", reg_file).groups()[0]) - f = int(re.search(r".+f:\s+(\d+).+", reg_file).groups()[0]) - p = int(re.search(r".+p:\s+(\d+).+", reg_file).groups()[0]) - OD = int(re.search(r".+OD:\s+(\d+).+", reg_file).groups()[0]) - ACD = int(re.search(r".+ACD:\s+(\d+).+", reg_file).groups()[0]) + input_freq = int(re.search(".+Input freq:\s+(\d+).+", reg_file).groups()[0]) + F = int(re.search(".+F:\s+(\d+).+", reg_file).groups()[0]) + R = int(re.search(".+R:\s+(\d+).+", reg_file).groups()[0]) + f = int(re.search(".+f:\s+(\d+).+", reg_file).groups()[0]) + p = int(re.search(".+p:\s+(\d+).+", reg_file).groups()[0]) + OD = int(re.search(".+OD:\s+(\d+).+", reg_file).groups()[0]) + ACD = int(re.search(".+ACD:\s+(\d+).+", reg_file).groups()[0]) return input_freq, F, R, f, p, OD, ACD @@ -120,13 +120,13 @@ def print_stats(self, target_output_frequency): steps = np.size(lut) register = int(lut[0]) - min_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) + min_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask) register = int(lut[steps // 2]) - mid_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) + mid_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask) register = int(lut[-1]) - max_freq = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) + max_freq = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask) ave_step_size = (max_freq - min_freq) / steps @@ -151,7 +151,7 @@ def plot_freq_range(self): frequencies = [] for step in range(self.get_lut_size()): register = int(self.lut[step]) - self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) + self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask) frequencies.append(self.app_pll.get_output_frequency()) plt.clf() @@ -187,7 +187,7 @@ def get_frequency_from_dco_control(self, dco_ctrl): register = int(self.lut[set_point]) - output_frequency = self.app_pll.update_frac_reg(register & app_pll_frac_calc.frac_enable_mask) + output_frequency = self.app_pll.update_frac_reg(register | app_pll_frac_calc.frac_enable_mask) self.last_output_frequency = output_frequency return output_frequency, self.lock_status @@ -215,14 +215,13 @@ def __init__(self): # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) - # This is work in progress - the integer model matches the firmware better def do_sigma_delta(self, sdm_in): if sdm_in > self.sdm_in_max: print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") sdm_in = self. sdm_in_max self.lock_status = 1 - elif sdm_in < self.sdm_in_min: + elif app_pll_frac_calc.frac_enable_mask < self.app_pll_frac_calc.frac_enable_mask_min: print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") sdm_in = self.sdm_in_min self.lock_status = -1 @@ -241,37 +240,6 @@ def do_sigma_delta(self, sdm_in): self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) - return int(sdm_out), self.lock_status - - def do_sigma_delta_int(self, sdm_in): - # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) - # Third order, 9 level output delta sigma. 20 bit unsigned input. - sdm_in = int(sdm_in) - - if sdm_in > self.sdm_in_max: - print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") - sdm_in = self. sdm_in_max - self.lock_status = 1 - - elif sdm_in < self.sdm_in_min: - print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") - sdm_in = self.sdm_in_min - self.lock_status = -1 - - else: - self.lock_status = 0 - - sdm_out = ((self.sdm_x3<<4) + (self.sdm_x3<<1)) >> 13 - - if sdm_out > 8: - sdm_out = 8 - if sdm_out < 0: - sdm_out = 0 - - self.sdm_x3 += (self.sdm_x2>>5) - (sdm_out<<9) - (sdm_out<<8) - self.sdm_x2 += (self.sdm_x1>>5) - (sdm_out<<14) - self.sdm_x1 += sdm_in - (sdm_out<<17) - return sdm_out, self.lock_status @@ -321,12 +289,11 @@ def _sdm_out_to_freq(self, sdm_out): self.f = sdm_out - 1 return self.app_pll.update_frac(self.f, self.p_value - 1, True) - def do_modulate(self, ctrl_input): + def do_modulate(self, input): """ Input a control value and output a SDM signal """ - # self.sdm_out, lock_status = sdm.do_sigma_delta(self, ctrl_input) - self.sdm_out, lock_status = sdm.do_sigma_delta_int(self, ctrl_input) + self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) frequency = self._sdm_out_to_freq(self.sdm_out) diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 5be16b5a..2adafb5e 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -132,7 +132,7 @@ def do_control(self, mclk_pt, ref_pt): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_frac_reg(int(reg, 16) & app_pll_frac_calc.frac_enable_mask) + self.pll.update_frac_reg(int(reg, 16) | app_pll_frac_calc.frac_enable_mask) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) def do_control_from_error(self, error): @@ -144,7 +144,7 @@ def do_control_from_error(self, error): locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() - self.pll.update_frac_reg(int(reg, 16) & app_pll_frac_calc.frac_enable_mask) + self.pll.update_frac_reg(int(reg, 16) | app_pll_frac_calc.frac_enable_mask) return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) @@ -235,7 +235,7 @@ def basic_test_vector(request, solution_12288, bin_dir): frequency_lut = [] for reg in sol.lut: - pll.update_frac_reg(reg & app_pll_frac_calc.frac_enable_mask) + pll.update_frac_reg(reg | app_pll_frac_calc.frac_enable_mask) frequency_lut.append(pll.get_output_frequency()) frequency_range_frac = (frequency_lut[-1] - frequency_lut[0])/frequency_lut[0] @@ -244,7 +244,7 @@ def basic_test_vector(request, solution_12288, bin_dir): plt.savefig(bin_dir/f"lut-{name}.png") plt.close() - pll.update_frac_reg(start_reg & app_pll_frac_calc.frac_enable_mask) + pll.update_frac_reg(start_reg | app_pll_frac_calc.frac_enable_mask) input_freqs = { "perfect": target_ref_f, diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 074333a8..007334db 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -60,7 +60,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): pll = app_pll_frac_calc(xtal_freq, sol.F, sol.R, 1, 2, sol.OD, sol.ACD) - pll.update_frac_reg(start_reg & app_pll_frac_calc.frac_enable_mask) + pll.update_frac_reg(start_reg | app_pll_frac_calc.frac_enable_mask) input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) print(f"input_errors: {input_errors}") From ecd77c80f583e7faf327d49cb59c79187c150de1 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 22 Nov 2023 18:09:48 +0000 Subject: [PATCH 058/118] Fix SDM DCO equiv test --- python/sw_pll/dco_model.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 0befc278..5fed7062 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -215,13 +215,14 @@ def __init__(self): # generalized version without fixed point shifts. WIP!! # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) + # This is work in progress - the integer model matches the firmware better def do_sigma_delta(self, sdm_in): if sdm_in > self.sdm_in_max: print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") sdm_in = self. sdm_in_max self.lock_status = 1 - elif app_pll_frac_calc.frac_enable_mask < self.app_pll_frac_calc.frac_enable_mask_min: + elif sdm_in < self.sdm_in_min: print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") sdm_in = self.sdm_in_min self.lock_status = -1 @@ -240,6 +241,37 @@ def do_sigma_delta(self, sdm_in): self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) + return int(sdm_out), self.lock_status + + def do_sigma_delta_int(self, sdm_in): + # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) + # Third order, 9 level output delta sigma. 20 bit unsigned input. + sdm_in = int(sdm_in) + + if sdm_in > self.sdm_in_max: + print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") + sdm_in = self. sdm_in_max + self.lock_status = 1 + + elif sdm_in < self.sdm_in_min: + print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") + sdm_in = self.sdm_in_min + self.lock_status = -1 + + else: + self.lock_status = 0 + + sdm_out = ((self.sdm_x3<<4) + (self.sdm_x3<<1)) >> 13 + + if sdm_out > 8: + sdm_out = 8 + if sdm_out < 0: + sdm_out = 0 + + self.sdm_x3 += (self.sdm_x2>>5) - (sdm_out<<9) - (sdm_out<<8) + self.sdm_x2 += (self.sdm_x1>>5) - (sdm_out<<14) + self.sdm_x1 += sdm_in - (sdm_out<<17) + return sdm_out, self.lock_status @@ -293,7 +325,8 @@ def do_modulate(self, input): """ Input a control value and output a SDM signal """ - self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) + # self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) + self.sdm_out, lock_status = sdm.do_sigma_delta_int(self, input) frequency = self._sdm_out_to_freq(self.sdm_out) From 67729a5cd0c604ad217e501f88bd17c186ade6ec Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 08:47:51 +0000 Subject: [PATCH 059/118] Add SDM tests to build --- tools/ci/do-ci-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/do-ci-build.sh b/tools/ci/do-ci-build.sh index cdbeefee..d31e0345 100755 --- a/tools/ci/do-ci-build.sh +++ b/tools/ci/do-ci-build.sh @@ -5,4 +5,4 @@ set -ex cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake -cmake --build build --target all --target test_app --target test_app_low_level_api --target simple --target i2s_slave -j$(nproc) +cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple --target i2s_slave -j$(nproc) From b944eb00c73532af294fd2dc66e94203b4ec626d Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 11:39:51 +0000 Subject: [PATCH 060/118] Refactor and tidy --- lib_sw_pll/src/sw_pll.c | 3 --- lib_sw_pll/src/sw_pll_common.h | 3 +++ lib_sw_pll/src/sw_pll_sdm.c | 26 ++++++++++++++----------- lib_sw_pll/src/sw_pll_sdm.h | 5 ++++- tests/test_app/readme.txt | 0 tests/test_app_low_level_api/readme.txt | 0 tests/test_app_sdm_ctrl/main.c | 3 ++- tests/test_app_sdm_ctrl/readme.txt | 0 tests/test_app_sdm_dco/readme.txt | 0 9 files changed, 24 insertions(+), 16 deletions(-) delete mode 100644 tests/test_app/readme.txt delete mode 100644 tests/test_app_low_level_api/readme.txt delete mode 100644 tests/test_app_sdm_ctrl/readme.txt delete mode 100644 tests/test_app_sdm_dco/readme.txt diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index 82ce422c..fb405b24 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -2,12 +2,9 @@ // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "sw_pll.h" -#include "sw_pll_pfd.h" #include -#define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked - // Implement a delay in 100MHz timer ticks without using a timer resource static void blocking_delay(const uint32_t delay_ticks){ uint32_t time_delay = get_reference_time() + delay_ticks; diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index fd10b5a9..668182d9 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -3,6 +3,9 @@ #pragma once +// The number of consecutive lock positive reports of the control loop before declaring we are finally locked +#define SW_PLL_LOCK_COUNT 10 + // Helpers used in this module #define TIMER_TIMEAFTER(A, B) ((int)((B) - (A)) < 0) // Returns non-zero if A is after B, accounting for wrap #define PORT_TIMEAFTER(NOW, EVENT_TIME) ((int16_t)((EVENT_TIME) - (NOW)) < 0) // Returns non-zero if A is after B, accounting for wrap diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 8e4ffc3b..59516208 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -4,8 +4,6 @@ #include "sw_pll.h" #include -#define SW_PLL_LOCK_COUNT 10 // The number of consecutive lock positive reports of the control loop before declaring we are finally locked - void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, @@ -64,6 +62,20 @@ int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t return total_error; } +__attribute__((always_inline)) +int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error) +{ + // Filter some noise into DCO to reduce jitter + // First order IIR, make A=0.125 + // y = y + A(x-y) + sw_pll->pi_state.iir_y += ((error - sw_pll->pi_state.iir_y)>>3); + + int32_t dco_ctl = SW_PLL_SDM_MID_POINT - error; + + return dco_ctl; +} + + void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ chan_out_word(c_sdm_control, dco_ctl); } @@ -90,16 +102,8 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); + int32_t dco_ctl = sw_pll_sdm_post_control_proc(sw_pll, error); - // Filter some noise into DCO to reduce jitter - // First order IIR, make A=0.125 - // y = y + A(x-y) - sw_pll->pi_state.iir_y += ((error - sw_pll->pi_state.iir_y)>>3); - - - printintln(sw_pll->pfd_state.mclk_diff); - printintln(error); - int dco_ctl = 478151 - error; sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); // Save for next iteration to calc diff diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index 3baa23bf..b2c39939 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -5,6 +5,8 @@ #pragma once +#define SW_PLL_SDM_MID_POINT 478151 + typedef int tileref_t; void init_sigma_delta(sw_pll_sdm_state_t *sdm_state); @@ -52,4 +54,5 @@ static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt); \ No newline at end of file +int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); +sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt); diff --git a/tests/test_app/readme.txt b/tests/test_app/readme.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_app_low_level_api/readme.txt b/tests/test_app_low_level_api/readme.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index ccee6198..de39607e 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -93,9 +93,10 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { uint32_t t0 = get_reference_time(); int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, mclk_diff); + int32_t dco_ctl = sw_pll_sdm_post_control_proc(&sw_pll, error); uint32_t t1 = get_reference_time(); - printf("%hd %lu %d %lu\n", mclk_diff, error, sw_pll.lock_status, t1 - t0); + printf("%lu %d %lu\n", dco_ctl, sw_pll.lock_status, t1 - t0); } } diff --git a/tests/test_app_sdm_ctrl/readme.txt b/tests/test_app_sdm_ctrl/readme.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_app_sdm_dco/readme.txt b/tests/test_app_sdm_dco/readme.txt deleted file mode 100644 index e69de29b..00000000 From 85f537046683d9a5b5e7fdfcc4af3c19fb934ec5 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 11:40:49 +0000 Subject: [PATCH 061/118] Add failing SDM ctrl test --- python/sw_pll/controller_model.py | 2 +- python/sw_pll/dco_model.py | 16 +-- tests/test_sdm_ctrl_equiv.py | 165 ++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 tests/test_sdm_ctrl_equiv.py diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 92ef5a26..6de666b3 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -120,7 +120,7 @@ def get_dco_control_from_error(self, error, first_loop=False): # SIGMA DELTA MODULATOR IMPLEMENTATION ###################################### -class sdm_pi_ctrl(pi_ctrl, sigma_delta_dco): +class sdm_pi_ctrl(pi_ctrl): def __init__(self, Kp, Ki, Kii=None, verbose=False): """ Create instance absed on specific control constants diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 5fed7062..8a824eb9 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -90,13 +90,13 @@ def _parse_register_file(self, register_file): with open(register_file) as rf: reg_file = rf.read().replace('\n', '') - input_freq = int(re.search(".+Input freq:\s+(\d+).+", reg_file).groups()[0]) - F = int(re.search(".+F:\s+(\d+).+", reg_file).groups()[0]) - R = int(re.search(".+R:\s+(\d+).+", reg_file).groups()[0]) - f = int(re.search(".+f:\s+(\d+).+", reg_file).groups()[0]) - p = int(re.search(".+p:\s+(\d+).+", reg_file).groups()[0]) - OD = int(re.search(".+OD:\s+(\d+).+", reg_file).groups()[0]) - ACD = int(re.search(".+ACD:\s+(\d+).+", reg_file).groups()[0]) + input_freq = int(re.search(r".+Input freq:\s+(\d+).+", reg_file).groups()[0]) + F = int(re.search(r".+F:\s+(\d+).+", reg_file).groups()[0]) + R = int(re.search(r".+R:\s+(\d+).+", reg_file).groups()[0]) + f = int(re.search(r".+f:\s+(\d+).+", reg_file).groups()[0]) + p = int(re.search(r".+p:\s+(\d+).+", reg_file).groups()[0]) + OD = int(re.search(r".+OD:\s+(\d+).+", reg_file).groups()[0]) + ACD = int(re.search(r".+ACD:\s+(\d+).+", reg_file).groups()[0]) return input_freq, F, R, f, p, OD, ACD @@ -381,6 +381,8 @@ def write_register_file(self): reg_vals.write(self.app_pll.gen_register_file_text()) reg_vals.write("\n\n") + return register_file + if __name__ == '__main__': """ diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py new file mode 100644 index 00000000..5c519a73 --- /dev/null +++ b/tests/test_sdm_ctrl_equiv.py @@ -0,0 +1,165 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +""" +Assorted tests which run the test_app in xsim + +This file is structured as a fixture which takes a while to run +and generates a pandas.DataFrame containing some time domain +outputs from the control loops. Then a series of tests which +check different aspects of the content of this DataFrame. +""" + +import pytest +import numpy as np +import copy +from typing import Any +from dataclasses import dataclass, asdict +from pathlib import Path +from matplotlib import pyplot as plt +from subprocess import Popen, PIPE +import re + + +# from sw_pll.app_pll_model import app_pll_frac_calc +from sw_pll.dco_model import sigma_delta_dco +from sw_pll.controller_model import sdm_pi_ctrl +from test_lib_sw_pll import bin_dir + + +DUT_XE_SDM_CTRL = Path(__file__).parent / "../build/tests/test_app_sdm_ctrl/test_app_sdm_ctrl.xe" + +@dataclass +class DutSDMCTRLArgs: + kp: float + ki: float + loop_rate_count: int + pll_ratio: int + ref_clk_expected_inc: int + app_pll_ctl_reg_val: int + app_pll_div_reg_val: int + app_pll_frac_reg_val: int + ppm_range: int + target_output_frequency: int + + +class Dut_SDM_CTRL: + """ + run controller in xsim and provide access to the sdm function + """ + + def __init__(self, args:DutSDMCTRLArgs, xe_file=DUT_XE_SDM_CTRL): + self.args = DutSDMCTRLArgs(**asdict(args)) # copies the values + # concatenate the parameters to the init function and the whole lut + # as the command line parameters to the xe. + list_args = [*(str(i) for i in asdict(self.args).values())] + + cmd = ["xsim", "--args", str(xe_file), *list_args] + + print(" ".join(cmd)) + + self._process = Popen( + cmd, + stdin=PIPE, + stdout=PIPE, + encoding="utf-8", + ) + + def __enter__(self): + """support context manager""" + return self + + def __exit__(self, *_): + """support context manager""" + self.close() + + def do_control(self, mclk_diff): + """ + returns sigma delta out, calculated frac val, lock status and timing + """ + self._process.stdin.write(f"{mclk_diff}\n") + self._process.stdin.flush() + + from_dut = self._process.stdout.readline().strip() + error, locked, ticks = from_dut.split() + + return int(error), int(locked), int(ticks) + + def close(self): + """Send EOF to xsim and wait for it to exit""" + self._process.stdin.close() + self._process.wait() + + +def read_register_file(reg_file): + with open(reg_file) as rf: + text = "".join(rf.readlines()) + regex = r".+APP_PLL_CTL_REG 0[xX]([0-9a-fA-F]+)\n.+APP_PLL_DIV_REG.+0[xX]([0-9a-fA-F]+)\n.+APP_PLL_FRAC_REG.+0[xX]([0-9a-fA-F]+)\n" + match = re.search(regex, text) + + app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val = match.groups() + + return app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val + + + +def test_sdm_ctrl_equivalence(bin_dir): + """ + Simple low level test of equivalence using do_control_from_error + Feed in random numbers into C and Python DUTs and see if we get the same results + """ + + available_profiles = list(sigma_delta_dco.profiles.keys()) + profile_used = available_profiles[0] + profile = sigma_delta_dco.profiles[profile_used] + target_output_frequency = profile["output_frequency"] + ref_frequency = 48000 + ref_clk_expected_inc = 0 + + Kp = 0.0 + Ki = 32.0 + + ctrl_sim = sdm_pi_ctrl(Kp, Ki) + + dco = sigma_delta_dco(profile_used) + dco.print_stats() + register_file = dco.write_register_file() + app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val = read_register_file(register_file) + + + args = DutSDMCTRLArgs( + kp = Kp, + ki = Ki, + loop_rate_count = 1, + pll_ratio = target_output_frequency / ref_frequency, + ref_clk_expected_inc = ref_clk_expected_inc, + app_pll_ctl_reg_val = app_pll_ctl_reg_val, + app_pll_div_reg_val = app_pll_div_reg_val, + app_pll_frac_reg_val = app_pll_frac_reg_val, + ppm_range = 1000, + target_output_frequency = target_output_frequency + ) + + + ctrl_dut = Dut_SDM_CTRL(args) + + max_ticks = 0 + + for mclk_diff in [1] * 10: + + dco_ctl_sim = ctrl_sim.do_control_from_error(mclk_diff) + + dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) + + print(f"SIM: {mclk_diff} {dco_ctl_sim}") + print(f"DUT: {mclk_diff} {dco_ctl_dut} {lock_status_dut} {ticks}\n") + + max_ticks = ticks if ticks > max_ticks else max_ticks + + # assert dco_sim.sdm_out == sdm_out_dut + # assert frac_reg_sim == frac_reg_dut + # assert frequency_sim == frequency_dut + # assert lock_status_sim == lock_status_dut + + + print("TEST PASSED!") + From a446b23f700bb7cd29330d3250bac2f0ad1d0b25 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 14:54:51 +0000 Subject: [PATCH 062/118] Passing SDM ctrl app --- examples/simple_sdm/src/register_setup.h | 11 +++---- examples/simple_sdm/src/simple_sw_pll_sdm.c | 1 + lib_sw_pll/api/sw_pll.h | 2 ++ lib_sw_pll/src/sw_pll_common.h | 15 +-------- lib_sw_pll/src/sw_pll_sdm.c | 6 ++-- lib_sw_pll/src/sw_pll_sdm.h | 3 +- python/sw_pll/controller_model.py | 14 +++++--- python/sw_pll/dco_model.py | 19 ++++++----- tests/test_app/main.c | 4 +-- tests/test_app_low_level_api/main.c | 4 +-- tests/test_app_sdm_ctrl/main.c | 12 ++++--- tests/test_sdm_ctrl_equiv.py | 36 +++++++++++++-------- 12 files changed, 67 insertions(+), 60 deletions(-) diff --git a/examples/simple_sdm/src/register_setup.h b/examples/simple_sdm/src/register_setup.h index a2f61899..183e7ff5 100644 --- a/examples/simple_sdm/src/register_setup.h +++ b/examples/simple_sdm/src/register_setup.h @@ -1,18 +1,15 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - /* Autogenerated SDM App PLL setup by dco_model.py using 24.576_1M profile */ /* Input freq: 24000000 F: 146 R: 0 - f: 4 - p: 10 + f: 7 + p: 7 OD: 5 ACD: 5 */ #define APP_PLL_CTL_REG 0x0A809200 #define APP_PLL_DIV_REG 0x80000005 -#define APP_PLL_FRAC_REG 0x8000040A - +#define APP_PLL_FRAC_REG 0x80000707 +#define SW_PLL_SDM_CTRL_MID 553648 diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 103f6ef6..096c4e4e 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -96,6 +96,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ APP_PLL_CTL_REG, APP_PLL_DIV_REG, APP_PLL_FRAC_REG, + SW_PLL_SDM_CTRL_MID, PPM_RANGE); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index ccb9f46f..70f36d6c 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -135,7 +135,9 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, const uint32_t app_pll_frac_reg_val, + const int32_t ctrl_mid_point, const unsigned ppm_range); + sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_pt); int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); void sw_pll_app_pll_init(const unsigned tileid, diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index 668182d9..e5f5254a 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -50,6 +50,7 @@ typedef struct sw_pll_lut_state_t{ typedef struct sw_pll_sdm_state_t{ + int32_t ctrl_mid_point; // The mid point for the DCO input int32_t ds_x1; // Sigma delta modulator state int32_t ds_x2; // Sigma delta modulator state int32_t ds_x3; // Sigma delta modulator state @@ -76,17 +77,3 @@ typedef struct sw_pll_state_t{ sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO }sw_pll_state_t; - - -void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, - const sw_pll_15q16_t Kp, - const sw_pll_15q16_t Ki, - const size_t loop_rate_count, - const size_t pll_ratio, - const uint32_t ref_clk_expected_inc, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const uint32_t app_pll_frac_reg_val, - const unsigned ppm_range); - - diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 59516208..fa05fbc3 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -13,6 +13,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, const uint32_t app_pll_frac_reg_val, + const int32_t ctrl_mid_point, const unsigned ppm_range) { // Get PLL started and running at nominal @@ -23,6 +24,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, // Setup sw_pll with supplied user paramaters sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 + sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point; sw_pll->pi_state.iir_y = 0; // Setup general controller state @@ -70,7 +72,7 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro // y = y + A(x-y) sw_pll->pi_state.iir_y += ((error - sw_pll->pi_state.iir_y)>>3); - int32_t dco_ctl = SW_PLL_SDM_MID_POINT - error; + int32_t dco_ctl = sw_pll->sdm_state.ctrl_mid_point + sw_pll->pi_state.iir_y; return dco_ctl; } @@ -101,7 +103,7 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen else { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); - int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); + int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff); int32_t dco_ctl = sw_pll_sdm_post_control_proc(sw_pll, error); sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index b2c39939..6f0b324e 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -9,7 +9,6 @@ typedef int tileref_t; -void init_sigma_delta(sw_pll_sdm_state_t *sdm_state); __attribute__((always_inline)) static inline int32_t do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t ds_in){ @@ -51,7 +50,7 @@ static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); } - +void init_sigma_delta(sw_pll_sdm_state_t *sdm_state); int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 6de666b3..574c8cb0 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -121,7 +121,7 @@ def get_dco_control_from_error(self, error, first_loop=False): ###################################### class sdm_pi_ctrl(pi_ctrl): - def __init__(self, Kp, Ki, Kii=None, verbose=False): + def __init__(self, mod_init, Kp, Ki, Kii=None, verbose=False): """ Create instance absed on specific control constants """ @@ -132,7 +132,7 @@ def __init__(self, Kp, Ki, Kii=None, verbose=False): self.iir_y = 0 # Nominal setting for SDM - self.initial_setting = 478151 + self.initial_setting = mod_init def do_control_from_error(self, error): """ @@ -140,11 +140,14 @@ def do_control_from_error(self, error): low passs filtering stage. """ x = pi_ctrl.do_control_from_error(self, -error) + x = int(x) - # Filter some noise into DCO to reduce jitter + # Filter noise into DCO to reduce jitter # First order IIR, make A=0.125 # y = y + A(x-y) - self.iir_y = self.iir_y + (x - self.iir_y) * self.alpha + + # self.iir_y = int(self.iir_y + (x - self.iir_y) * self.alpha) + self.iir_y += (x - self.iir_y) >> 3 # This matches the firmware return self.initial_setting + self.iir_y @@ -160,10 +163,11 @@ def do_control_from_error(self, error): for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) + mod_init = 478151 Kp = 0.0 Ki = 0.1 Kii = 0.1 - sw_pll = sdm_pi_ctrl(Kp, Ki, Kii=Kii, verbose=True) + sw_pll = sdm_pi_ctrl(mod_init, Kp, Ki, Kii=Kii, verbose=True) for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 8a824eb9..ceac9ba8 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -281,16 +281,18 @@ class sigma_delta_dco(sdm): PLL solution profiles depending on target output clock These are designed to work with a SDM either running at - 500kHz: - - 50ps jitter 100Hz-40kHz with low freq noise floor -93dBc. - or 1MHz: + 1MHz: - 10ps jitter 100Hz-40kHz with very low freq noise floor -100dBc + or 500kHz: + - 50ps jitter 100Hz-40kHz with low freq noise floor -93dBc. + """ - profiles = {"24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6}, - "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6}, - "24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6}, - "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6}} + profiles = {"24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6, "mod_init":478151}, + "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6, "mod_init":498283}, + "24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6, "mod_init":553648}, + "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6, "mod_init":555326} + } def __init__(self, profile): @@ -300,7 +302,7 @@ def __init__(self, profile): self.profile = profile self.p_value = 8 # 8 frac settings + 1 non frac setting - input_freq, F, R, f, p, OD, ACD, _ = list(self.profiles[profile].values()) + input_freq, F, R, f, p, OD, ACD, _, _ = list(self.profiles[profile].values()) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) self.sdm_out = 0 @@ -379,6 +381,7 @@ def write_register_file(self): with open(register_file, "w") as reg_vals: reg_vals.write(f"/* Autogenerated SDM App PLL setup by {Path(__file__).name} using {self.profile} profile */\n") reg_vals.write(self.app_pll.gen_register_file_text()) + reg_vals.write(f"#define SW_PLL_SDM_CTRL_MID {self.profiles[self.profile]['mod_init']}") reg_vals.write("\n\n") return register_file diff --git a/tests/test_app/main.c b/tests/test_app/main.c index 919cf245..d3c5ca90 100644 --- a/tests/test_app/main.c +++ b/tests/test_app/main.c @@ -27,9 +27,9 @@ int main(int argc, char** argv) { int i = 1; - float kp = atoi(argv[i++]); + float kp = atof(argv[i++]); fprintf(stderr, "kp\t\t%f\n", kp); - float ki = atoi(argv[i++]); + float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index f19f495c..e6663901 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -27,9 +27,9 @@ int main(int argc, char** argv) { int i = 1; - float kp = atoi(argv[i++]); + float kp = atof(argv[i++]); fprintf(stderr, "kp\t\t%f\n", kp); - float ki = atoi(argv[i++]); + float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index de39607e..17d2454e 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -31,9 +31,9 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { int i = 1; - float kp = atoi(argv[i++]); + float kp = atof(argv[i++]); fprintf(stderr, "kp\t\t%f\n", kp); - float ki = atoi(argv[i++]); + float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); @@ -47,6 +47,8 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { fprintf(stderr, "app_pll_div_reg_val\t\t%lu\n", app_pll_div_reg_val); uint32_t app_pll_frac_reg_val = atoi(argv[i++]); fprintf(stderr, "app_pll_frac_reg_val\t\t%lu\n", app_pll_frac_reg_val); + int32_t ctrl_mid_point = atoi(argv[i++]); + fprintf(stderr, "ctrl_mid_point\t\t%ld\n", ctrl_mid_point); unsigned ppm_range = atoi(argv[i++]); fprintf(stderr, "ppm_range\t\t%d\n", ppm_range); unsigned target_output_frequency = atoi(argv[i++]); @@ -68,6 +70,7 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val, + ctrl_mid_point, ppm_range); @@ -92,11 +95,12 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { sscanf(read_buf, "%hd", &mclk_diff); uint32_t t0 = get_reference_time(); - int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, mclk_diff); + int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, -mclk_diff); int32_t dco_ctl = sw_pll_sdm_post_control_proc(&sw_pll, error); + // sw_pll_send_ctrl_to_sdm_task() uint32_t t1 = get_reference_time(); - printf("%lu %d %lu\n", dco_ctl, sw_pll.lock_status, t1 - t0); + printf("%ld %ld %d %lu\n", error, dco_ctl, sw_pll.lock_status, t1 - t0); } } diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index 5c519a73..e154416a 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -38,6 +38,7 @@ class DutSDMCTRLArgs: app_pll_ctl_reg_val: int app_pll_div_reg_val: int app_pll_frac_reg_val: int + ctrl_mid_point: int ppm_range: int target_output_frequency: int @@ -80,9 +81,10 @@ def do_control(self, mclk_diff): self._process.stdin.flush() from_dut = self._process.stdout.readline().strip() - error, locked, ticks = from_dut.split() + # print(f"from_dut: {from_dut}") + error, dco_ctrl, locked, ticks = from_dut.split() - return int(error), int(locked), int(ticks) + return int(error), int(dco_ctrl), int(locked), int(ticks) def close(self): """Send EOF to xsim and wait for it to exit""" @@ -93,13 +95,12 @@ def close(self): def read_register_file(reg_file): with open(reg_file) as rf: text = "".join(rf.readlines()) - regex = r".+APP_PLL_CTL_REG 0[xX]([0-9a-fA-F]+)\n.+APP_PLL_DIV_REG.+0[xX]([0-9a-fA-F]+)\n.+APP_PLL_FRAC_REG.+0[xX]([0-9a-fA-F]+)\n" + regex = r".+APP_PLL_CTL_REG 0[xX]([0-9a-fA-F]+)\n.+APP_PLL_DIV_REG 0[xX]([0-9a-fA-F]+)\n.+APP_PLL_FRAC_REG 0[xX]([0-9a-fA-F]+)\n.+SW_PLL_SDM_CTRL_MID (\d+)" match = re.search(regex, text) - app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val = match.groups() - - return app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val + app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val, ctrl_mid_point = match.groups() + return int(app_pll_ctl_reg_val, 16), int(app_pll_div_reg_val, 16), int(app_pll_frac_reg_val, 16), int(ctrl_mid_point) def test_sdm_ctrl_equivalence(bin_dir): @@ -112,19 +113,21 @@ def test_sdm_ctrl_equivalence(bin_dir): profile_used = available_profiles[0] profile = sigma_delta_dco.profiles[profile_used] target_output_frequency = profile["output_frequency"] + ctrl_mid_point = profile["mod_init"] ref_frequency = 48000 ref_clk_expected_inc = 0 Kp = 0.0 Ki = 32.0 - ctrl_sim = sdm_pi_ctrl(Kp, Ki) + ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, Kp, Ki) dco = sigma_delta_dco(profile_used) dco.print_stats() register_file = dco.write_register_file() - app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val = read_register_file(register_file) + app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val, read_ctrl_mid_point = read_register_file(register_file) + assert ctrl_mid_point == read_ctrl_mid_point, f"ctrl_mid_point doesn't match: {ctrl_mid_point} {read_ctrl_mid_point}" args = DutSDMCTRLArgs( kp = Kp, @@ -135,6 +138,7 @@ def test_sdm_ctrl_equivalence(bin_dir): app_pll_ctl_reg_val = app_pll_ctl_reg_val, app_pll_div_reg_val = app_pll_div_reg_val, app_pll_frac_reg_val = app_pll_frac_reg_val, + ctrl_mid_point = ctrl_mid_point, ppm_range = 1000, target_output_frequency = target_output_frequency ) @@ -144,19 +148,23 @@ def test_sdm_ctrl_equivalence(bin_dir): max_ticks = 0 - for mclk_diff in [1] * 10: + for i in range(50): + mclk_diff = np.random.randint(-10, 10) + # Run through the model dco_ctl_sim = ctrl_sim.do_control_from_error(mclk_diff) + error_sim = ctrl_sim.total_error - dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) + # Run through the firmware + error_dut, dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) - print(f"SIM: {mclk_diff} {dco_ctl_sim}") - print(f"DUT: {mclk_diff} {dco_ctl_dut} {lock_status_dut} {ticks}\n") + print(f"SIM: {mclk_diff} {error_sim} {dco_ctl_sim}") + print(f"DUT: {mclk_diff} {error_dut} {dco_ctl_dut} {lock_status_dut} {ticks}\n") max_ticks = ticks if ticks > max_ticks else max_ticks - # assert dco_sim.sdm_out == sdm_out_dut - # assert frac_reg_sim == frac_reg_dut + assert error_sim == error_dut + assert dco_ctl_sim == dco_ctl_dut # assert frequency_sim == frequency_dut # assert lock_status_sim == lock_status_dut From c35f689fd6b6400daf2c470213e900e6f421153b Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 15:13:40 +0000 Subject: [PATCH 063/118] add exclude for autgen files --- .xmos_ignore_source_check | 1 + 1 file changed, 1 insertion(+) diff --git a/.xmos_ignore_source_check b/.xmos_ignore_source_check index 63de3bba..8c89e922 100644 --- a/.xmos_ignore_source_check +++ b/.xmos_ignore_source_check @@ -1 +1,2 @@ python/sw_pll/pll_calc.py +**/register_setup.h \ No newline at end of file From 088d8841d962761032edaf2e4c4d03ade24fd5b1 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 15:14:40 +0000 Subject: [PATCH 064/118] Fix exclude --- .xmos_ignore_source_check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.xmos_ignore_source_check b/.xmos_ignore_source_check index 8c89e922..1350ef87 100644 --- a/.xmos_ignore_source_check +++ b/.xmos_ignore_source_check @@ -1,2 +1,2 @@ python/sw_pll/pll_calc.py -**/register_setup.h \ No newline at end of file +register_setup.h \ No newline at end of file From 1a614e93439b64556b5018f83119b7865b7eba42 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 15:18:01 +0000 Subject: [PATCH 065/118] remove unnecessary inheritance --- python/sw_pll/controller_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 574c8cb0..caca5edc 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -65,7 +65,7 @@ def do_control_from_error(self, error): # LOOK UP TABLE IMPLEMENTATION ############################## -class lut_pi_ctrl(pi_ctrl, lut_dco): +class lut_pi_ctrl(pi_ctrl): """ This class instantiates a control loop instance. It takes a lookup table function which can be generated from the error_from_h class which allows it use the actual pre-calculated transfer function. From ef3e62ad64bb2e49d9e12a843735d66eda656b76 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 23 Nov 2023 16:44:49 +0000 Subject: [PATCH 066/118] Tidy example and add lock detect for SDM --- examples/simple_sdm/src/main.xc | 2 +- examples/simple_sdm/src/simple_sw_pll_sdm.c | 26 ++++++++++----------- lib_sw_pll/src/sw_pll_sdm.c | 22 ++++++++++++++--- lib_sw_pll/src/sw_pll_sdm.h | 3 ++- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/examples/simple_sdm/src/main.xc b/examples/simple_sdm/src/main.xc index a8f0b3af..fc89c8a2 100644 --- a/examples/simple_sdm/src/main.xc +++ b/examples/simple_sdm/src/main.xc @@ -24,7 +24,7 @@ int main(void) sw_pll_sdm_test(c_sdm_control); sdm_task(c_sdm_control); { - clock_gen(48000, 250); + clock_gen(96000, 3000); exit(0); } } diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 096c4e4e..9e4f02f0 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -14,7 +14,7 @@ #include "resource_setup.h" #define MCLK_FREQUENCY 24576000 -#define REF_FREQUENCY 48000 +#define REF_FREQUENCY 96000 #define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY) #define CONTROL_LOOP_COUNT 512 #define PPM_RANGE 150 //TODO eliminate @@ -34,7 +34,8 @@ void sdm_task(chanend_t c_sdm_control){ hwtimer_t tmr = hwtimer_alloc(); int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval; bool running = true; - int32_t ds_in = 666666; + int32_t ds_in = 0; // Zero while uninitialized. + uint32_t frac_val = 0; while(running){ // Poll for new SDM control value @@ -56,17 +57,16 @@ void sdm_task(chanend_t c_sdm_control){ break; } - // calc new ds_out and then wait to write - int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); - uint32_t frac_val = ds_out_to_frac_reg(ds_out); + if(ds_in){ + hwtimer_wait_until(tmr, trigger_time); - hwtimer_wait_until(tmr, trigger_time); - trigger_time += sdm_interval; - write_frac_reg(this_tile, frac_val); + write_frac_reg(this_tile, frac_val); + trigger_time += sdm_interval; - static int cnt = 0; - if (cnt % 1000000 == 0) printintln(cnt); - cnt++; + // calc new ds_out and then wait to write + int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); + frac_val = ds_out_to_frac_reg(ds_out); + } } } @@ -84,7 +84,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ // Make a test output to observe the recovered mclk divided down to the refclk frequency xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3; port_t p_recovered_ref_clk = PORT_I2S_DAC_DATA; - setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO / 2); // TODO fix me /2 + setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO); sw_pll_state_t sw_pll; sw_pll_sdm_init(&sw_pll, @@ -97,7 +97,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ APP_PLL_DIV_REG, APP_PLL_FRAC_REG, SW_PLL_SDM_CTRL_MID, - PPM_RANGE); + 10000 /*PPM_RANGE*/); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index fa05fbc3..d828c049 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -23,7 +23,8 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, (uint16_t)(app_pll_frac_reg_val & 0xffff)); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, 65535); // TODO work out windup limit - this overflows at 65536 + sw_pll_reset(sw_pll, Kp, Ki, 0); + sw_pll->pi_state.i_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point; sw_pll->pi_state.iir_y = 0; @@ -74,6 +75,22 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro int32_t dco_ctl = sw_pll->sdm_state.ctrl_mid_point + sw_pll->pi_state.iir_y; + if(dco_ctl > SW_PLL_SDM_UPPER_LIMIT){ + dco_ctl = SW_PLL_SDM_UPPER_LIMIT; + sw_pll->lock_status = SW_PLL_UNLOCKED_HIGH; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + } else if (dco_ctl < SW_PLL_SDM_LOWER_LIMIT){ + dco_ctl = SW_PLL_SDM_LOWER_LIMIT; + sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; + sw_pll->lock_counter = SW_PLL_LOCK_COUNT; + } else { + if(sw_pll->lock_counter){ + sw_pll->lock_counter--; + } else { + sw_pll->lock_status = SW_PLL_LOCKED; + } + } + return dco_ctl; } @@ -97,7 +114,6 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; sw_pll->first_loop = 0; - // Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in settings) } else @@ -105,7 +121,7 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff); int32_t dco_ctl = sw_pll_sdm_post_control_proc(sw_pll, error); - + // printintln(dco_ctl); sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); // Save for next iteration to calc diff diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index 6f0b324e..eea3697f 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -5,7 +5,8 @@ #pragma once -#define SW_PLL_SDM_MID_POINT 478151 +#define SW_PLL_SDM_UPPER_LIMIT 980000 +#define SW_PLL_SDM_LOWER_LIMIT 60000 typedef int tileref_t; From c0f5969746e4881ac60d3a50d43615fd158f0df8 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 10:39:54 +0000 Subject: [PATCH 067/118] Refactor lock status in model --- python/sw_pll/app_pll_model.py | 1 - python/sw_pll/controller_model.py | 41 ++++++++++++-- python/sw_pll/dco_model.py | 91 ++++++++++++++----------------- python/sw_pll/sw_pll_sim.py | 17 ++++-- tests/test_app_sdm_dco/main.c | 4 +- tests/test_sdm_ctrl_equiv.py | 9 ++- tests/test_sdm_dco_equiv.py | 15 +++-- 7 files changed, 100 insertions(+), 78 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 402236da..1e4f7ab5 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -31,7 +31,6 @@ def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD self.f = f_init # fractional multiplier (+1.0) self.p = p_init # fractional divider (+1.0) self.output_frequency = None - self.lock_status_state = 0 self.fractional_enable = True self.verbose = verbose diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index caca5edc..45e91842 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -1,7 +1,7 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -from sw_pll.dco_model import lut_dco, sigma_delta_dco +from sw_pll.dco_model import lut_dco, sigma_delta_dco, lock_count_threshold import numpy as np @@ -121,7 +121,7 @@ def get_dco_control_from_error(self, error, first_loop=False): ###################################### class sdm_pi_ctrl(pi_ctrl): - def __init__(self, mod_init, Kp, Ki, Kii=None, verbose=False): + def __init__(self, mod_init, sdm_in_max, sdm_in_min, Kp, Ki, Kii=None, verbose=False): """ Create instance absed on specific control constants """ @@ -134,6 +134,14 @@ def __init__(self, mod_init, Kp, Ki, Kii=None, verbose=False): # Nominal setting for SDM self.initial_setting = mod_init + # Limits for SDM output + self.sdm_in_max = sdm_in_max + self.sdm_in_min = sdm_in_min + + # Lock status state + self.lock_status = -1 + self.lock_count = lock_count_threshold + def do_control_from_error(self, error): """ Run the control loop. Also contains an additional @@ -149,7 +157,28 @@ def do_control_from_error(self, error): # self.iir_y = int(self.iir_y + (x - self.iir_y) * self.alpha) self.iir_y += (x - self.iir_y) >> 3 # This matches the firmware - return self.initial_setting + self.iir_y + sdm_in = self.initial_setting + self.iir_y + + + if sdm_in > self.sdm_in_max: + print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") + sdm_in = self. sdm_in_max + self.lock_status = 1 + self.lock_count = lock_count_threshold + + elif sdm_in < self.sdm_in_min: + print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") + sdm_in = self.sdm_in_min + self.lock_status = -1 + self.lock_count = lock_count_threshold + + else: + if self.lock_count > 0: + self.lock_count -= 1 + else: + self.lock_status = 0 + + return sdm_in, self.lock_status if __name__ == '__main__': @@ -163,11 +192,11 @@ def do_control_from_error(self, error): for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) - mod_init = 478151 + mod_init = (sigma_delta_dco.sdm_in_max + sigma_delta_dco.sdm_in_min) / 2 Kp = 0.0 Ki = 0.1 Kii = 0.1 - sw_pll = sdm_pi_ctrl(mod_init, Kp, Ki, Kii=Kii, verbose=True) + sw_pll = sdm_pi_ctrl(mod_init, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki, Kii=Kii, verbose=True) for error_input in range(-10, 20): - dco_ctrl = sw_pll.do_control_from_error(error_input) + dco_ctrl, lock_status = sw_pll.do_control_from_error(error_input) diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index ceac9ba8..3329f18f 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -23,6 +23,7 @@ lock_status_lookup = {-1 : "UNLOCKED LOW", 0 : "LOCKED", 1 : "UNLOCKED HIGH"} +lock_count_threshold = 10 ############################## # LOOK UP TABLE IMPLEMENTATION @@ -46,6 +47,7 @@ def __init__(self, header_file = "fractions.h", verbose=False): # fixed header self.last_output_frequency = self.app_pll.update_frac_reg(self.lut[self.get_lut_size() // 2] | app_pll_frac_calc.frac_enable_mask) self.lock_status = -1 + self.lock_count = lock_count_threshold def _read_lut_header(self, header_file): """ @@ -178,12 +180,17 @@ def get_frequency_from_dco_control(self, dco_ctrl): if set_point < 0: set_point = 0 self.lock_status = -1 + self.lock_count = lock_count_threshold elif set_point >= num_entries: set_point = num_entries - 1 self.lock_status = 1 + self.lock_count = lock_count_threshold else: set_point = set_point - self.lock_status = 0 + if self.lock_count > 0: + self.lock_count -= 1 + else: + self.lock_status = 0 register = int(self.lut[set_point]) @@ -202,65 +209,51 @@ class sdm: Experimental - taken from lib_xua synchronous branch Third order, 9 level output delta sigma. 20 bit unsigned input. """ + # Limits for SDM modulator for stability + sdm_in_max = 980000 + sdm_in_min = 60000 + def __init__(self): # Delta sigma modulator state self.sdm_x1 = 0 self.sdm_x2 = 0 self.sdm_x3 = 0 - self.sdm_in_max = 980000 - self.sdm_in_min = 60000 - - self.lock_status = -1 - - # generalized version without fixed point shifts. WIP!! - # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) - # This is work in progress - the integer model matches the firmware better - def do_sigma_delta(self, sdm_in): - if sdm_in > self.sdm_in_max: - print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") - sdm_in = self. sdm_in_max - self.lock_status = 1 - - elif sdm_in < self.sdm_in_min: - print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") - sdm_in = self.sdm_in_min - self.lock_status = -1 - - else: - self.lock_status = 0 - - sdm_out = int(self.sdm_x3 * 0.002197265625) - - if sdm_out > 8: - sdm_out = 8 - if sdm_out < 0: - sdm_out = 0 + # # generalized version without fixed point shifts. WIP!! + # # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) + # # This is work in progress - the integer model matches the firmware better + # def do_sigma_delta(self, sdm_in): + # if sdm_in > self.sdm_in_max: + # print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") + # sdm_in = self. sdm_in_max + # self.lock_status = 1 + + # elif sdm_in < self.sdm_in_min: + # print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") + # sdm_in = self.sdm_in_min + # self.lock_status = -1 + + # else: + # self.lock_status = 0 + + # sdm_out = int(self.sdm_x3 * 0.002197265625) + + # if sdm_out > 8: + # sdm_out = 8 + # if sdm_out < 0: + # sdm_out = 0 - self.sdm_x3 += int((self.sdm_x2 * 0.03125) - (sdm_out * 768)) - self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) - self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) + # self.sdm_x3 += int((self.sdm_x2 * 0.03125) - (sdm_out * 768)) + # self.sdm_x2 += int((self.sdm_x1 * 0.03125) - (sdm_out * 16384)) + # self.sdm_x1 += int(sdm_in - (sdm_out * 131072)) - return int(sdm_out), self.lock_status + # return int(sdm_out), self.lock_status def do_sigma_delta_int(self, sdm_in): # takes a Q20 number from 60000 to 980000 (or 0.0572 to 0.934) # Third order, 9 level output delta sigma. 20 bit unsigned input. sdm_in = int(sdm_in) - if sdm_in > self.sdm_in_max: - print(f"SDM Pos clip: {sdm_in}, {self.sdm_in_max}") - sdm_in = self. sdm_in_max - self.lock_status = 1 - - elif sdm_in < self.sdm_in_min: - print(f"SDM Neg clip: {sdm_in}, {self.sdm_in_min}") - sdm_in = self.sdm_in_min - self.lock_status = -1 - - else: - self.lock_status = 0 - sdm_out = ((self.sdm_x3<<4) + (self.sdm_x3<<1)) >> 13 if sdm_out > 8: @@ -272,7 +265,7 @@ def do_sigma_delta_int(self, sdm_in): self.sdm_x2 += (self.sdm_x1>>5) - (sdm_out<<14) self.sdm_x1 += sdm_in - (sdm_out<<17) - return sdm_out, self.lock_status + return sdm_out class sigma_delta_dco(sdm): @@ -328,11 +321,11 @@ def do_modulate(self, input): Input a control value and output a SDM signal """ # self.sdm_out, lock_status = sdm.do_sigma_delta(self, input) - self.sdm_out, lock_status = sdm.do_sigma_delta_int(self, input) + self.sdm_out = sdm.do_sigma_delta_int(self, input) frequency = self._sdm_out_to_freq(self.sdm_out) - return frequency, lock_status + return frequency def print_stats(self): """ diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 57f0de8a..b6262b31 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -133,14 +133,19 @@ def __init__( self, Kii=None): self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) - self.controller = sdm_pi_ctrl(Kp, Ki, Kii) self.dco = sigma_delta_dco("24.576_1M") + self.controller = sdm_pi_ctrl( (self.dco.sdm_in_max + self.dco.sdm_in_min) / 2, + self.dco.sdm_in_max, + self.dco.sdm_in_min, + Kp, + Ki, + Kii) self.target_output_frequency = target_output_frequency self.time = 0.0 self.control_time_inc = 1 / nominal_nominal_control_rate_frequency - self.control_setting = (self.dco.ds_in_max + self.dco.ds_in_min) / 2 # Mid way + self.control_setting = (self.controller.sdm_in_max + self.controller.sdm_in_min) / 2 # Mid way def do_control_loop(self, output_clock_count, verbose=False): @@ -149,7 +154,7 @@ def do_control_loop(self, output_clock_count, verbose=False): """ error, first_loop = self.pfd.get_error(output_clock_count) - ctrl_output = self.controller.do_control_from_error(error) + ctrl_output, lock_status = self.controller.do_control_from_error(error) self.control_setting = ctrl_output if verbose: @@ -164,9 +169,9 @@ def do_sigma_delta(self): Run the SDM which needs to be run constantly at the SDM rate. See DCO (dco_model) for details """ - frequncy, lock_status = self.dco.do_modulate(self.control_setting) + frequncy = self.dco.do_modulate(self.control_setting) - return frequncy, lock_status + return frequncy def run_sd_sw_pll_sim(): @@ -199,7 +204,7 @@ def run_sd_sw_pll_sim(): for loop in range(simulation_iterations): - output_frequency, lock_status = sw_pll.do_sigma_delta() + output_frequency = sw_pll.do_sigma_delta() # Log results freq_log.append(output_frequency) diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c index bf7e7612..62361369 100644 --- a/tests/test_app_sdm_dco/main.c +++ b/tests/test_app_sdm_dco/main.c @@ -55,8 +55,6 @@ int main(int argc, char** argv) { uint32_t frac_val = ds_out_to_frac_reg(ds_out); uint32_t t1 = get_reference_time(); - sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; - - printf("%ld %lu %d %lu\n", ds_out, frac_val, lock_status, t1 - t0); + printf("%ld %lu %lu\n", ds_out, frac_val, t1 - t0); } } diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index e154416a..00fea979 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -120,7 +120,7 @@ def test_sdm_ctrl_equivalence(bin_dir): Kp = 0.0 Ki = 32.0 - ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, Kp, Ki) + ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki) dco = sigma_delta_dco(profile_used) dco.print_stats() @@ -152,21 +152,20 @@ def test_sdm_ctrl_equivalence(bin_dir): mclk_diff = np.random.randint(-10, 10) # Run through the model - dco_ctl_sim = ctrl_sim.do_control_from_error(mclk_diff) + dco_ctl_sim, lock_status_sim = ctrl_sim.do_control_from_error(mclk_diff) error_sim = ctrl_sim.total_error # Run through the firmware error_dut, dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) - print(f"SIM: {mclk_diff} {error_sim} {dco_ctl_sim}") + print(f"SIM: {mclk_diff} {error_sim} {dco_ctl_sim} {lock_status_sim}") print(f"DUT: {mclk_diff} {error_dut} {dco_ctl_dut} {lock_status_dut} {ticks}\n") max_ticks = ticks if ticks > max_ticks else max_ticks assert error_sim == error_dut assert dco_ctl_sim == dco_ctl_dut - # assert frequency_sim == frequency_dut - # assert lock_status_sim == lock_status_dut + assert lock_status_sim == lock_status_dut print("TEST PASSED!") diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 5003c183..1534b90f 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -65,18 +65,18 @@ def __exit__(self, *_): def do_modulate(self, sdm_in): """ - returns sigma delta out, calculated frac val, lock status and timing + returns sigma delta out, calculated frac val and timing """ self._process.stdin.write(f"{sdm_in}\n") self._process.stdin.flush() from_dut = self._process.stdout.readline().strip() - sdm_out, frac_val, locked, ticks = from_dut.split() + sdm_out, frac_val, ticks = from_dut.split() frac_val = int(frac_val) frequency = self.pll.update_frac_reg(frac_val) - return int(sdm_out), int(frac_val), frequency, int(locked), int(ticks) + return int(sdm_out), int(frac_val), frequency, int(ticks) def close(self): """Send EOF to xsim and wait for it to exit""" @@ -107,20 +107,19 @@ def test_sdm_dco_equivalence(bin_dir): max_ticks = 0 for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 100): - frequency_sim, lock_status_sim = dco_sim.do_modulate(sdm_in) + frequency_sim = dco_sim.do_modulate(sdm_in) frac_reg_sim = dco_sim.app_pll.get_frac_reg() - print(f"SIM: {sdm_in} {dco_sim.sdm_out} {frac_reg_sim:#x} {frequency_sim} {lock_status_sim}") + print(f"SIM: {sdm_in} {dco_sim.sdm_out} {frac_reg_sim:#x} {frequency_sim}") - sdm_out_dut, frac_reg_dut, frequency_dut, lock_status_dut, ticks = dco_dut.do_modulate(sdm_in) - print(f"DUT: {sdm_in} {sdm_out_dut} {frac_reg_dut:#x} {frequency_dut} {lock_status_dut} {ticks}\n") + sdm_out_dut, frac_reg_dut, frequency_dut, ticks = dco_dut.do_modulate(sdm_in) + print(f"DUT: {sdm_in} {sdm_out_dut} {frac_reg_dut:#x} {frequency_dut} {ticks}\n") max_ticks = ticks if ticks > max_ticks else max_ticks assert dco_sim.sdm_out == sdm_out_dut assert frac_reg_sim == frac_reg_dut assert frequency_sim == frequency_dut - assert lock_status_sim == lock_status_dut print("TEST PASSED!") From 20cedc98fe7a79fd431e89237ef0fbfe92be8b27 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 11:44:08 +0000 Subject: [PATCH 068/118] SDM C API tidy and doxy --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 20 +++- lib_sw_pll/api/sw_pll.h | 101 ++++++++++++++++++-- lib_sw_pll/src/sw_pll_common.h | 1 + lib_sw_pll/src/sw_pll_sdm.c | 26 +++-- lib_sw_pll/src/sw_pll_sdm.h | 9 +- tests/test_app_sdm_dco/main.c | 2 +- 6 files changed, 126 insertions(+), 33 deletions(-) diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 9e4f02f0..34c1030a 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -17,7 +17,6 @@ #define REF_FREQUENCY 96000 #define PLL_RATIO (MCLK_FREQUENCY / REF_FREQUENCY) #define CONTROL_LOOP_COUNT 512 -#define PPM_RANGE 150 //TODO eliminate #include "register_setup.h" @@ -27,14 +26,17 @@ void sdm_task(chanend_t c_sdm_control){ const uint32_t sdm_interval = 100; sw_pll_sdm_state_t sdm_state; - init_sigma_delta(&sdm_state); + sw_pll_init_sigma_delta(&sdm_state); tileref_t this_tile = get_local_tile_id(); hwtimer_t tmr = hwtimer_alloc(); int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval; bool running = true; - int32_t ds_in = 0; // Zero while uninitialized. + int32_t ds_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until + // the first control value has been received. This avoids issues with + // channel lockup if two tasks (eg. init and SDM) try to write at the same + // time. uint32_t frac_val = 0; while(running){ @@ -70,6 +72,9 @@ void sdm_task(chanend_t c_sdm_control){ } } +void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ + chan_out_word(c_sdm_control, dco_ctl); +} void sw_pll_sdm_test(chanend_t c_sdm_control){ @@ -97,7 +102,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ APP_PLL_DIV_REG, APP_PLL_FRAC_REG, SW_PLL_SDM_CTRL_MID, - 10000 /*PPM_RANGE*/); + 3000 /*PPM_RANGE*/); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; @@ -108,8 +113,13 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. uint32_t t0 = get_reference_time(); - sw_pll_sdm_do_control(&sw_pll, c_sdm_control, mclk_pt, 0); + bool ctrl_done = sw_pll_sdm_do_control(&sw_pll, mclk_pt, 0); uint32_t t1 = get_reference_time(); + + if(ctrl_done){ + sw_pll_send_ctrl_to_sdm_task(c_sdm_control, sw_pll.sdm_state.current_ctrl_val); + } + if(t1 - t0 > max_time){ max_time = t1 - t0; printf("Max ticks taken: %lu\n", max_time); diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 70f36d6c..0e3cdde5 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -43,6 +44,8 @@ * close to halfway to allow symmetrical range. * \param \c ppm_range The pre-calculated PPM range. Used to determine the maximum deviation * of counted mclk before the PLL resets its state. + * Note this is only used by \c sw_pll_do_control. \c sw_pll_do_control_from_error + * calls the control loop every time so this is ignored. * */ void sw_pll_init( sw_pll_state_t * const sw_pll, @@ -124,8 +127,35 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl } } -///////// SDM WORK IN PROGRESS ///////// - +/** + * sw_pll_sdm initialisation function. + * + * This must be called before use of sw_pll_sdm_do_control or sw_pll_sdm_do_control_from_error. + * Call this passing a pointer to the sw_pll_state_t stuct declared locally. + * + * \param \c sw_pll Pointer to the struct to be initialised. + * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. + * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. + * \param \c loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done. + * Note this is only used by \c sw_pll_sdm_do_control. \c sw_pll_sdm_do_control_from_error + * calls the control loop every time so this is ignored. + * \param \c pll_ratio Integer ratio between input reference clock and the PLL output. + * Only used by sw_pll_sdm_do_control. Don't care otherwise. + * \param \c ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. + * Pass in zero if you are sure the mclk sampling timing is precise. This + * will disable the scaling of the mclk count inside \c sw_pll_sdm_do_control. + * Only used by \c sw_pll_sdm_do_control. Don't care otherwise. + * \param \c app_pll_ctl_reg_val The setting of the app pll control register. + * \param \c app_pll_div_reg_val The setting of the app pll divider register. + * \param \c app_pll_frac_reg_val The setting of the app pll fractional register. + * \param \c ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally + * close to halfway to allow symmetrical range. + * \param \c ppm_range The pre-calculated PPM range. Used to determine the maximum deviation + * of counted mclk before the PLL resets its state. Note this is only used + * by \c sw_pll_sdm_do_control. \c sw_pll_sdm_do_control_from_error + * calls the control loop every time so this is ignored. + * + */ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, @@ -138,10 +168,65 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, const int32_t ctrl_mid_point, const unsigned ppm_range); -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_pt); +/** + * sw_pll_sdm_do_control control function. + * + * This must be called periodically for every reference clock transition. + * Typically, in an audio system, this would be at the I2S or reference clock input rate. + * Eg. 16kHz, 48kHz ... + * + * When this is called, the control loop will be executed every n times (set by init) and the + * Sigma Delta Modulator control value will be set according the error seen on the mclk count value. + * + * If control is executed, TRUE is returned from the function. + * The most recent calculated control output value can be found written to sw_pll->sdm_state.current_ctrl_val. + * + * If the precise sampling point of mclk is not easily controlled (for example in an I2S callback) + * then an additional timer count may be passed in which will scale the mclk count. See i2s_slave + * example to show how this is done. This will help reduce input jitter which, in turn, relates + * to output jitter being a PLL. + * + * \param \c sw_pll Pointer to the struct to be initialised. + * \param \c mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. + * \param \c ref_pt The 16b port timer ref ount at the time of calling \c sw_pll_do_control. This value + * is ignored when the pll is initialised with a zero \c ref_clk_expected_inc and the + * control loop will assume that \c mclk_pt sample timing is precise. + * + * \returns Whether or not control was executed (controoled by loop_rate_count) + */ +bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); + +/** + * low level sw_pll_sdm control function for use as pure PLL control loop. + * + * This must be called periodically. + * + * Takes the raw error input and applies the PI controller algorithm + * + * \param \c sw_pll Pointer to the struct to be initialised. + * \param \c error 16b signed input error value + * \returns The PI processed error + */ int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); -void sw_pll_app_pll_init(const unsigned tileid, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const uint16_t frac_val_nominal); //TODO hide me -void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); + +/** + * low level sw_pll_sdm post control processing function. + * + * This must be called after sw_pll_sdm_do_control_from_error. + * + * Takes the PI processed error and applies a low pass filter and calaculates the Sigma Delta Modulator + * control signal. It also checks the range and sets the PLL lock status if exceeded. + * + * \param \c sw_pll Pointer to the struct to be initialised. + * \param \c error 32b signed input error value from PI controller + * \returns The Sigma Delta Modulator Control signal + */ +int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); + +/** + * Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM + * is often run in a dedicated thread which could be on a remote tile. + * + * \param \c sdm_state Pointer to the struct to be initialised. + */ +void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state); diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index e5f5254a..c6ba3da1 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -50,6 +50,7 @@ typedef struct sw_pll_lut_state_t{ typedef struct sw_pll_sdm_state_t{ + int32_t current_ctrl_val; // The last control value calculated int32_t ctrl_mid_point; // The mid point for the DCO input int32_t ds_x1; // Sigma delta modulator state int32_t ds_x2; // Sigma delta modulator state diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index d828c049..b576baf3 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -27,6 +27,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, sw_pll->pi_state.i_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point; sw_pll->pi_state.iir_y = 0; + sw_pll->sdm_state.current_ctrl_val = ctrl_mid_point; // Setup general controller state sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -41,7 +42,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, } -void init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ +void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ sdm_state->ds_x1 = 0; sdm_state->ds_x2 = 0; sdm_state->ds_x3 = 0; @@ -95,12 +96,11 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro } -void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl){ - chan_out_word(c_sdm_control, dco_ctl); -} -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { + bool control_done = true; + if (++sw_pll->loop_counter == sw_pll->loop_rate_count) { sw_pll->loop_counter = 0; @@ -114,23 +114,21 @@ sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanen sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; sw_pll->first_loop = 0; - // Do not set PLL frac as last setting probably the best. At power on we set to nominal (midway in settings) + // Do not set current_ctrl_val as last setting probably the best. At power on we set to nominal (midway in settings) + } else { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff); - int32_t dco_ctl = sw_pll_sdm_post_control_proc(sw_pll, error); - // printintln(dco_ctl); - sw_pll_send_ctrl_to_sdm_task(c_sdm_control, dco_ctl); - + sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, error); + // Save for next iteration to calc diff sw_pll->pfd_state.mclk_pt_last = mclk_pt; - } + } else { + control_done = false; } - // printchar('+'); - - return sw_pll->lock_status; + return control_done; } diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index eea3697f..55a677af 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -51,8 +51,7 @@ static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); } -void init_sigma_delta(sw_pll_sdm_state_t *sdm_state); -int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); -void sw_pll_send_ctrl_to_sdm_task(chanend_t c_sdm_control, int32_t dco_ctl); -int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); -sw_pll_lock_status_t sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, chanend_t c_sdm_control, const uint16_t mclk_pt, const uint16_t ref_clk_pt); +extern void sw_pll_app_pll_init(const unsigned tileid, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const uint16_t frac_val_nominal); diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c index 62361369..563f2da1 100644 --- a/tests/test_app_sdm_dco/main.c +++ b/tests/test_app_sdm_dco/main.c @@ -26,7 +26,7 @@ int main(int argc, char** argv) { sw_pll_sdm_state_t sdm_state; - init_sigma_delta(&sdm_state); + sw_pll_init_sigma_delta(&sdm_state); for(;;) { char read_buf[IN_LINE_SIZE]; From 4992035d2095e9367c8a0b454f1335d83f1e9d03 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 11:56:50 +0000 Subject: [PATCH 069/118] Os in test apps --- tests/test_app/CMakeLists.txt | 1 + tests/test_app_low_level_api/CMakeLists.txt | 1 + tests/test_app_sdm_ctrl/CMakeLists.txt | 1 + tests/test_app_sdm_dco/CMakeLists.txt | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/test_app/CMakeLists.txt b/tests/test_app/CMakeLists.txt index 2e6b903c..617bdc27 100644 --- a/tests/test_app/CMakeLists.txt +++ b/tests/test_app/CMakeLists.txt @@ -7,6 +7,7 @@ target_compile_options( test_app PUBLIC -g + -Os -report -fxscope -target=XCORE-AI-EXPLORER diff --git a/tests/test_app_low_level_api/CMakeLists.txt b/tests/test_app_low_level_api/CMakeLists.txt index 4d7cda58..e465f00d 100644 --- a/tests/test_app_low_level_api/CMakeLists.txt +++ b/tests/test_app_low_level_api/CMakeLists.txt @@ -7,6 +7,7 @@ target_compile_options( test_app_low_level_api PUBLIC -g + -Os -report -fxscope -target=XCORE-AI-EXPLORER diff --git a/tests/test_app_sdm_ctrl/CMakeLists.txt b/tests/test_app_sdm_ctrl/CMakeLists.txt index 257fcad2..163104e6 100644 --- a/tests/test_app_sdm_ctrl/CMakeLists.txt +++ b/tests/test_app_sdm_ctrl/CMakeLists.txt @@ -7,6 +7,7 @@ target_compile_options( test_app_sdm_ctrl PUBLIC -g + -Os -report -fxscope -target=XCORE-AI-EXPLORER diff --git a/tests/test_app_sdm_dco/CMakeLists.txt b/tests/test_app_sdm_dco/CMakeLists.txt index 17ef842b..edace81f 100644 --- a/tests/test_app_sdm_dco/CMakeLists.txt +++ b/tests/test_app_sdm_dco/CMakeLists.txt @@ -7,6 +7,7 @@ target_compile_options( test_app_sdm_dco PUBLIC -g + -Os -report -fxscope -target=XCORE-AI-EXPLORER From 80bd5a9b2c4421473b73d4d52ab4fa14aad5e5ac Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 12:25:04 +0000 Subject: [PATCH 070/118] Improve model comments --- python/sw_pll/analysis_tools.py | 10 ++++++++- python/sw_pll/controller_model.py | 2 +- python/sw_pll/dco_model.py | 9 ++++---- python/sw_pll/sw_pll_sim.py | 34 +++++++++++++++++++++++++++++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/python/sw_pll/analysis_tools.py b/python/sw_pll/analysis_tools.py index a0f4faea..dd9be5e6 100644 --- a/python/sw_pll/analysis_tools.py +++ b/python/sw_pll/analysis_tools.py @@ -7,6 +7,15 @@ from scipy.io import wavfile # soundfile has some issues writing high Fs files class audio_modulator: + """ + This test helper generates a wav file with a fixed sample rate and tone frequency + of a certain length. + A method then allows sections of it to be frequency modulated by a value in Hz. + The modulated signal (which uses cumultaive phase to avoid discontinuites) + may then be plotted as an FFT to understand the SNR/THD and may also be saved + as a wav file. + """ + def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000): self.sample_rate = sample_rate self.test_tone_hz = test_tone_hz @@ -18,7 +27,6 @@ def apply_frequency_deviation(self, start_s, end_s, delta_freq): end_idx = int(end_s * self.sample_rate) self.modulator[start_idx:end_idx] += delta_freq - def modulate_waveform(self): # Now create the frequency modulated waveform # this is designed to accumulate the phase so doesn't see discontinuities diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 45e91842..a66a7b31 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -27,7 +27,7 @@ def __init__(self, Kp, Ki, Kii=None, i_windup_limit=None, ii_windup_limit=None, def _reset_controller(self): """ - Reset anu accumulated state + Reset any accumulated state """ self.error_accum = 0.0 self.error_accum_accum = 0.0 diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 3329f18f..9c867ff7 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -384,11 +384,10 @@ def write_register_file(self): """ This module is not intended to be run directly. This is here for internal testing only. """ - # dco = lut_dco() - # print(f"LUT size: {dco.get_lut_size()}") - # # print(f"LUT : {dco.get_lut()}") - # dco.plot_freq_range() - # dco.print_stats(12288000) + dco = lut_dco() + print(f"LUT size: {dco.get_lut_size()}") + dco.plot_freq_range() + dco.print_stats(12288000) sdm_dco = sigma_delta_dco("24.576_1M") sdm_dco.write_register_file() diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index b6262b31..fcd03bda 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -27,12 +27,20 @@ def plot_simulation(freq_log, target_freq_log, real_time_log, name="sw_pll_track ############################## class sim_sw_pll_lut: + """ + Complete SW PLL simulation class which contains all of the components including + Phase Frequency Detector, Controller and Digitally Controlled Oscillator using + a Look Up Table method. + """ def __init__( self, target_output_frequency, nominal_nominal_control_rate_frequency, Kp, Ki, Kii=None): + """ + Init a Lookup Table based SW_PLL instance + """ self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) self.controller = lut_pi_ctrl(Kp, Ki, verbose=False) @@ -44,7 +52,10 @@ def __init__( self, def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False): """ - This should be called once every control period nominally + This should be called once every control period. + + output_clock_count is fed into the PDF and period_fraction is an optional jitter + reduction term where the control period is not exact, but can be compensated for. """ error, first_loop = self.pfd.get_error(output_clock_count, period_fraction=period_fraction) @@ -64,6 +75,9 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False def run_lut_sw_pll_sim(): + """ + Test program / example showing how to run the simulator object + """ nominal_output_hz = 12288000 nominal_control_rate_hz = 93.75 output_frequency = nominal_output_hz @@ -125,12 +139,21 @@ def run_lut_sw_pll_sim(): ###################################### class sim_sw_pll_sd: + """ + Complete SW PLL simulation class which contains all of the components including + Phase Frequency Detector, Controller and Digitally Controlled Oscillator using + a Sigma Delta Modulator method. + """ + def __init__( self, target_output_frequency, nominal_nominal_control_rate_frequency, Kp, Ki, Kii=None): + """ + Init a Sigma Delta Modulator based SW_PLL instance + """ self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) self.dco = sigma_delta_dco("24.576_1M") @@ -150,7 +173,11 @@ def __init__( self, def do_control_loop(self, output_clock_count, verbose=False): """ - Run the control loop which runs at a tiny fraction of the SDM rate + Run the control loop (which runs at a tiny fraction of the SDM rate) + This should be called once every control period. + + output_clock_count is fed into the PDF and period_fraction is an optional jitter + reduction term where the control period is not exact, but can be compensated for. """ error, first_loop = self.pfd.get_error(output_clock_count) @@ -175,6 +202,9 @@ def do_sigma_delta(self): def run_sd_sw_pll_sim(): + """ + Test program / example showing how to run the simulator object + """ nominal_output_hz = 24576000 nominal_control_rate_hz = 100 nominal_sd_rate_hz = 1e6 From 55b5ca50e642d6511703569b6ca1292ae591914d Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 12:31:45 +0000 Subject: [PATCH 071/118] Run python model examples and archive --- Jenkinsfile | 12 ++++++++++++ tools/ci/do-ci-build.sh | 2 +- tools/ci/do-model-examples.sh | 10 ++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tools/ci/do-model-examples.sh diff --git a/Jenkinsfile b/Jenkinsfile index 1d1376cc..eaafbcd4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -81,6 +81,18 @@ pipeline { } } } + stage('Python examples'){ + steps { + dir('lib_sw_pll') { + withVenv { + catchError { + sh './tools/ci/do-model-examples.sh' + } + archiveArtifacts artifacts: "python/sw_pll/*.png,python/sw_pll/*.wav", allowEmptyArchive: false + } + } + } + } } post { cleanup { diff --git a/tools/ci/do-ci-build.sh b/tools/ci/do-ci-build.sh index d31e0345..fd7079b6 100755 --- a/tools/ci/do-ci-build.sh +++ b/tools/ci/do-ci-build.sh @@ -5,4 +5,4 @@ set -ex cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake -cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple --target i2s_slave -j$(nproc) +cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple --target simple_sdm --target i2s_slave -j$(nproc) diff --git a/tools/ci/do-model-examples.sh b/tools/ci/do-model-examples.sh new file mode 100644 index 00000000..07cfa755 --- /dev/null +++ b/tools/ci/do-model-examples.sh @@ -0,0 +1,10 @@ +#! /usr/bin/env bash +# +# test stuff + +set -ex + +pushd python/sw_pll +python sw_pll_sim.py +popd + From a844de203bb5e855b6a7191389b2aa5e13d6d509 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 12:48:40 +0000 Subject: [PATCH 072/118] Run through all SDM profiles for test and improve artefact store --- Jenkinsfile | 2 +- python/sw_pll/app_pll_model.py | 11 ++++- tests/test_lib_sw_pll.py | 2 +- tests/test_sdm_ctrl_equiv.py | 90 ++++++++++++++++++---------------- tests/test_sdm_dco_equiv.py | 44 +++++++++-------- tools/ci/do-model-examples.sh | 0 6 files changed, 83 insertions(+), 66 deletions(-) mode change 100644 => 100755 tools/ci/do-model-examples.sh diff --git a/Jenkinsfile b/Jenkinsfile index eaafbcd4..148d9cab 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -73,7 +73,7 @@ pipeline { } zip archive: true, zipFile: "build.zip", dir: "build" zip archive: true, zipFile: "tests.zip", dir: "tests/bin" - archiveArtifacts artifacts: "tests/bin/timing-report.txt", allowEmptyArchive: false + archiveArtifacts artifacts: "tests/bin/timing-report*.txt", allowEmptyArchive: false junit 'tests/results.xml' } diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 1e4f7ab5..04d2bca6 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -23,6 +23,9 @@ class app_pll_frac_calc: frac_enable_mask = 0x80000000 def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD_init, verbose=False): + """ + Constructor initialising a PLL instance + """ self.input_frequency = input_frequency self.F = F_init self.R = R_init @@ -37,6 +40,9 @@ def __init__(self, input_frequency, F_init, R_init, f_init, p_init, OD_init, ACD self.calc_frequency() def calc_frequency(self): + """ + Calculate the output frequency based on current object settings + """ if self.verbose: print(f"F: {self.F} R: {self.R} OD: {self.OD} ACD: {self.ACD} f: {self.f} p: {self.p}") print(f"input_frequency: {self.input_frequency}") @@ -66,6 +72,9 @@ def calc_frequency(self): return self.output_frequency def get_output_frequency(self): + """ + Get last calculated frequency + """ return self.output_frequency def update_all(self, F, R, OD, ACD, f, p): @@ -129,7 +138,7 @@ def gen_register_file_text(self): text += f" ACD: {self.ACD}\n" text += "*/\n\n" - # This is a way of calling a printing function and capturing the STDOUT + # This is a way of calling a printing function from another module and capturing the STDOUT class args: app = True f = io.StringIO() diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 2adafb5e..0f4a3880 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -329,7 +329,7 @@ def basic_test_vector(request, solution_12288, bin_dir): df.to_csv(bin_dir/f"basic-test-vector-{name}.csv") - with open(bin_dir/f"timing-report.txt", "a") as tr: + with open(bin_dir/f"timing-report-lut.txt", "a") as tr: max_ticks = int(df[["ticks"]].max()) tr.write(f"{name} max ticks: {max_ticks}\n") diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index 00fea979..5b27bba6 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -110,63 +110,67 @@ def test_sdm_ctrl_equivalence(bin_dir): """ available_profiles = list(sigma_delta_dco.profiles.keys()) - profile_used = available_profiles[0] - profile = sigma_delta_dco.profiles[profile_used] - target_output_frequency = profile["output_frequency"] - ctrl_mid_point = profile["mod_init"] - ref_frequency = 48000 - ref_clk_expected_inc = 0 - Kp = 0.0 - Ki = 32.0 + with open(bin_dir/f"timing-report-sdm-ctrl.txt", "a") as tr: - ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki) + for profile_used in available_profiles: + profile = sigma_delta_dco.profiles[profile_used] + target_output_frequency = profile["output_frequency"] + ctrl_mid_point = profile["mod_init"] + ref_frequency = 48000 + ref_clk_expected_inc = 0 - dco = sigma_delta_dco(profile_used) - dco.print_stats() - register_file = dco.write_register_file() - app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val, read_ctrl_mid_point = read_register_file(register_file) + Kp = 0.0 + Ki = 32.0 - assert ctrl_mid_point == read_ctrl_mid_point, f"ctrl_mid_point doesn't match: {ctrl_mid_point} {read_ctrl_mid_point}" + ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki) - args = DutSDMCTRLArgs( - kp = Kp, - ki = Ki, - loop_rate_count = 1, - pll_ratio = target_output_frequency / ref_frequency, - ref_clk_expected_inc = ref_clk_expected_inc, - app_pll_ctl_reg_val = app_pll_ctl_reg_val, - app_pll_div_reg_val = app_pll_div_reg_val, - app_pll_frac_reg_val = app_pll_frac_reg_val, - ctrl_mid_point = ctrl_mid_point, - ppm_range = 1000, - target_output_frequency = target_output_frequency - ) + dco = sigma_delta_dco(profile_used) + dco.print_stats() + register_file = dco.write_register_file() + app_pll_ctl_reg_val, app_pll_div_reg_val, app_pll_frac_reg_val, read_ctrl_mid_point = read_register_file(register_file) + assert ctrl_mid_point == read_ctrl_mid_point, f"ctrl_mid_point doesn't match: {ctrl_mid_point} {read_ctrl_mid_point}" - ctrl_dut = Dut_SDM_CTRL(args) + args = DutSDMCTRLArgs( + kp = Kp, + ki = Ki, + loop_rate_count = 1, + pll_ratio = target_output_frequency / ref_frequency, + ref_clk_expected_inc = ref_clk_expected_inc, + app_pll_ctl_reg_val = app_pll_ctl_reg_val, + app_pll_div_reg_val = app_pll_div_reg_val, + app_pll_frac_reg_val = app_pll_frac_reg_val, + ctrl_mid_point = ctrl_mid_point, + ppm_range = 1000, + target_output_frequency = target_output_frequency + ) - max_ticks = 0 - for i in range(50): - mclk_diff = np.random.randint(-10, 10) + ctrl_dut = Dut_SDM_CTRL(args) - # Run through the model - dco_ctl_sim, lock_status_sim = ctrl_sim.do_control_from_error(mclk_diff) - error_sim = ctrl_sim.total_error + max_ticks = 0 - # Run through the firmware - error_dut, dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) + for i in range(50): + mclk_diff = np.random.randint(-10, 10) - print(f"SIM: {mclk_diff} {error_sim} {dco_ctl_sim} {lock_status_sim}") - print(f"DUT: {mclk_diff} {error_dut} {dco_ctl_dut} {lock_status_dut} {ticks}\n") + # Run through the model + dco_ctl_sim, lock_status_sim = ctrl_sim.do_control_from_error(mclk_diff) + error_sim = ctrl_sim.total_error - max_ticks = ticks if ticks > max_ticks else max_ticks + # Run through the firmware + error_dut, dco_ctl_dut, lock_status_dut, ticks = ctrl_dut.do_control(mclk_diff) - assert error_sim == error_dut - assert dco_ctl_sim == dco_ctl_dut - assert lock_status_sim == lock_status_dut + print(f"SIM: {mclk_diff} {error_sim} {dco_ctl_sim} {lock_status_sim}") + print(f"DUT: {mclk_diff} {error_dut} {dco_ctl_dut} {lock_status_dut} {ticks}\n") + max_ticks = ticks if ticks > max_ticks else max_ticks - print("TEST PASSED!") + assert error_sim == error_dut + assert dco_ctl_sim == dco_ctl_dut + assert lock_status_sim == lock_status_dut + + tr.write(f"SDM Control {profile_used} max ticks: {max_ticks}\n") + + print(f"{profile_used} TEST PASSED!") diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 1534b90f..8433a06e 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -94,33 +94,37 @@ def test_sdm_dco_equivalence(bin_dir): ) available_profiles = list(sigma_delta_dco.profiles.keys()) - profile = available_profiles[0] - dco_sim = sigma_delta_dco(profile) - dco_sim.write_register_file() + with open(bin_dir/f"timing-report-sdm-dco.txt", "a") as tr: - dco_sim.print_stats() + for profile in available_profiles: - dut_pll = sigma_delta_dco(profile) - dco_dut = Dut_SDM_DCO(dut_pll, args) + dco_sim = sigma_delta_dco(profile) + dco_sim.write_register_file() - max_ticks = 0 + dco_sim.print_stats() - for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 100): - frequency_sim = dco_sim.do_modulate(sdm_in) - frac_reg_sim = dco_sim.app_pll.get_frac_reg() - - print(f"SIM: {sdm_in} {dco_sim.sdm_out} {frac_reg_sim:#x} {frequency_sim}") - - sdm_out_dut, frac_reg_dut, frequency_dut, ticks = dco_dut.do_modulate(sdm_in) - print(f"DUT: {sdm_in} {sdm_out_dut} {frac_reg_dut:#x} {frequency_dut} {ticks}\n") + dut_pll = sigma_delta_dco(profile) + dco_dut = Dut_SDM_DCO(dut_pll, args) + + max_ticks = 0 + + for sdm_in in np.linspace(dco_sim.sdm_in_min, dco_sim.sdm_in_max, 100): + frequency_sim = dco_sim.do_modulate(sdm_in) + frac_reg_sim = dco_sim.app_pll.get_frac_reg() + + print(f"SIM: {sdm_in} {dco_sim.sdm_out} {frac_reg_sim:#x} {frequency_sim}") + + sdm_out_dut, frac_reg_dut, frequency_dut, ticks = dco_dut.do_modulate(sdm_in) + print(f"DUT: {sdm_in} {sdm_out_dut} {frac_reg_dut:#x} {frequency_dut} {ticks}\n") - max_ticks = ticks if ticks > max_ticks else max_ticks + max_ticks = ticks if ticks > max_ticks else max_ticks - assert dco_sim.sdm_out == sdm_out_dut - assert frac_reg_sim == frac_reg_dut - assert frequency_sim == frequency_dut + assert dco_sim.sdm_out == sdm_out_dut + assert frac_reg_sim == frac_reg_dut + assert frequency_sim == frequency_dut + tr.write(f"SDM DCO {profile} max ticks: {max_ticks}\n") - print("TEST PASSED!") + print(f"{profile} TEST PASSED!") diff --git a/tools/ci/do-model-examples.sh b/tools/ci/do-model-examples.sh old mode 100644 new mode 100755 From db9926095305ad21d65bbc0577aba0b5c5825f1f Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 13:51:08 +0000 Subject: [PATCH 073/118] Finish adding II term and fix example run --- examples/simple_sdm/src/main.xc | 2 +- examples/simple_sdm/src/simple_sw_pll_sdm.c | 1 + lib_sw_pll/api/sw_pll.h | 13 ++++++++++++- lib_sw_pll/src/sw_pll.c | 9 +++++++-- lib_sw_pll/src/sw_pll_common.h | 3 +++ lib_sw_pll/src/sw_pll_sdm.c | 4 +++- python/sw_pll/sw_pll_sim.py | 8 +++++++- tests/test_app_sdm_ctrl/main.c | 3 +++ tests/test_sdm_ctrl_equiv.py | 3 +++ 9 files changed, 40 insertions(+), 6 deletions(-) diff --git a/examples/simple_sdm/src/main.xc b/examples/simple_sdm/src/main.xc index fc89c8a2..714750a2 100644 --- a/examples/simple_sdm/src/main.xc +++ b/examples/simple_sdm/src/main.xc @@ -24,7 +24,7 @@ int main(void) sw_pll_sdm_test(c_sdm_control); sdm_task(c_sdm_control); { - clock_gen(96000, 3000); + clock_gen(96000, 300); exit(0); } } diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 34c1030a..0216070b 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -95,6 +95,7 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ sw_pll_sdm_init(&sw_pll, SW_PLL_15Q16(0.0), SW_PLL_15Q16(32.0), + SW_PLL_15Q16(0.25), CONTROL_LOOP_COUNT, PLL_RATIO, 0, diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 0e3cdde5..a6a2910e 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -27,6 +27,7 @@ * \param \c sw_pll Pointer to the struct to be initialised. * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. + * \param \c Kii Double integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c loop_rate_count How many counts of the call to sw_pll_do_control before control is done. * Note this is only used by \c sw_pll_do_control. \c sw_pll_do_control_from_error * calls the control loop every time so this is ignored. @@ -51,6 +52,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, @@ -113,9 +115,11 @@ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, * \param \c sw_pll Pointer to the struct to be initialised. * \param \c Kp New Kp in \c sw_pll_15q16_t format. * \param \c Ki New Ki in \c sw_pll_15q16_t format. + * \param \c Kii New Ki in \c sw_pll_15q16_t format. + * \param \c num_lut_entries The number of elements in the sw_pll LUT. */ -static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, size_t num_lut_entries) +static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) { sw_pll->pi_state.Kp = Kp; sw_pll->pi_state.Ki = Ki; @@ -125,6 +129,11 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl }else{ sw_pll->pi_state.i_windup_limit = 0; } + if(Kii){ + sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT + }else{ + sw_pll->pi_state.ii_windup_limit = 0; + } } /** @@ -136,6 +145,7 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl * \param \c sw_pll Pointer to the struct to be initialised. * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. + * \param \c Kii Double integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. * \param \c loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done. * Note this is only used by \c sw_pll_sdm_do_control. \c sw_pll_sdm_do_control_from_error * calls the control loop every time so this is ignored. @@ -159,6 +169,7 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index fb405b24..c31dff16 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -75,6 +75,7 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 void sw_pll_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, @@ -92,7 +93,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, lut_table_base[nominal_lut_idx]); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, num_lut_entries); + sw_pll_reset(sw_pll, Kp, Ki, Kii, num_lut_entries); // Setup general controller state sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; @@ -117,15 +118,19 @@ __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { sw_pll->pi_state.error_accum += error; // Integral error. + sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error. sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; + sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; // Use long long maths to avoid overflow if ever we had a large error accum term int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); + int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum); // Convert back to 32b since we are handling LUTs of around a hundred entries - int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); + int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS); sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->lut_state.current_reg_val)); diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index c6ba3da1..08b7b8c9 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -36,8 +36,11 @@ typedef struct sw_pll_pfd_state_t{ typedef struct sw_pll_pi_state_t{ sw_pll_15q16_t Kp; // Proportional constant sw_pll_15q16_t Ki; // Integral constant + sw_pll_15q16_t Kii; // Double integral constant int32_t i_windup_limit; // Integral term windup limit + int32_t ii_windup_limit; // Double integral term windup limit int32_t error_accum; // Accumulation of the raw mclk_diff term (for I) + int32_t error_accum_accum; // Accumulation of the raw mclk_diff term (for II) int32_t iir_y; // Optional IIR low pass filter state } sw_pll_pi_state_t; diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index b576baf3..60aba950 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -7,6 +7,7 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, const sw_pll_15q16_t Kp, const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, const size_t loop_rate_count, const size_t pll_ratio, const uint32_t ref_clk_expected_inc, @@ -23,8 +24,9 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, (uint16_t)(app_pll_frac_reg_val & 0xffff)); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, 0); + sw_pll_reset(sw_pll, Kp, Ki, Kii, 0); sw_pll->pi_state.i_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; + sw_pll->pi_state.ii_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point; sw_pll->pi_state.iir_y = 0; sw_pll->sdm_state.current_ctrl_val = ctrl_mid_point; diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index fcd03bda..aabb6055 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -1,6 +1,7 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. +from sw_pll.app_pll_model import get_pll_solution from sw_pll.pfd_model import port_timer_pfd from sw_pll.dco_model import lut_dco, sigma_delta_dco, lock_status_lookup from sw_pll.controller_model import lut_pi_ctrl, sdm_pi_ctrl @@ -79,8 +80,13 @@ def run_lut_sw_pll_sim(): Test program / example showing how to run the simulator object """ nominal_output_hz = 12288000 - nominal_control_rate_hz = 93.75 + + # This generates the needed header files read later by sim_sw_pll_lut + # 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + get_pll_solution(24000000, nominal_output_hz, max_denom=80, min_F=200, ppm_max=5, fracmin=0.695, fracmax=0.905) + output_frequency = nominal_output_hz + nominal_control_rate_hz = 93.75 simulation_iterations = 100 Kp = 0.0 Ki = 1.0 diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index 17d2454e..aac24625 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -35,6 +35,8 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { fprintf(stderr, "kp\t\t%f\n", kp); float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); + float kii = atof(argv[i++]); + fprintf(stderr, "kii\t\t%f\n", kii); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); size_t pll_ratio = atoi(argv[i++]); @@ -64,6 +66,7 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { sw_pll_sdm_init(&sw_pll, SW_PLL_15Q16(kp), SW_PLL_15Q16(ki), + SW_PLL_15Q16(kii), loop_rate_count, pll_ratio, ref_clk_expected_inc, diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index 5b27bba6..97c9c307 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -32,6 +32,7 @@ class DutSDMCTRLArgs: kp: float ki: float + kii: float loop_rate_count: int pll_ratio: int ref_clk_expected_inc: int @@ -122,6 +123,7 @@ def test_sdm_ctrl_equivalence(bin_dir): Kp = 0.0 Ki = 32.0 + Kii = 0.0 ctrl_sim = sdm_pi_ctrl(ctrl_mid_point, sigma_delta_dco.sdm_in_max, sigma_delta_dco.sdm_in_min, Kp, Ki) @@ -135,6 +137,7 @@ def test_sdm_ctrl_equivalence(bin_dir): args = DutSDMCTRLArgs( kp = Kp, ki = Ki, + ki = Ki, loop_rate_count = 1, pll_ratio = target_output_frequency / ref_frequency, ref_clk_expected_inc = ref_clk_expected_inc, From 8d018153a50f868d64bbcefc5841ecade18d8469 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 13:55:27 +0000 Subject: [PATCH 074/118] Changelog and typo fix --- CHANGELOG.rst | 2 ++ tests/test_sdm_ctrl_equiv.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd2edff0..d6bcbaa2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ lib_sw_pll library change log ----- * ADDED: Double integral term to controller + * ADDED: Sigma Delta Modulator option for PLL + * CHANGED: Refactored Python model into analogous objects 1.1.0 ----- diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index 97c9c307..b120bde0 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -137,7 +137,7 @@ def test_sdm_ctrl_equivalence(bin_dir): args = DutSDMCTRLArgs( kp = Kp, ki = Ki, - ki = Ki, + kii = Kii, loop_rate_count = 1, pll_ratio = target_output_frequency / ref_frequency, ref_clk_expected_inc = ref_clk_expected_inc, From fcccf6e4f2b0bf1929b2292e629e23659ab135df Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 14:21:18 +0000 Subject: [PATCH 075/118] Missing version bump --- doc/substitutions.rst-inc | 4 ++-- lib_sw_pll/src/sw_pll_pfd.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/substitutions.rst-inc b/doc/substitutions.rst-inc index 65b3d554..565f54db 100644 --- a/doc/substitutions.rst-inc +++ b/doc/substitutions.rst-inc @@ -1,5 +1,5 @@ -.. |full_version_str| replace:: v1.1.0 -.. |tools_version| replace:: 15.2.x +.. |full_version_str| replace:: v2.0.0 +.. |tools_version| replace:: 15.2.1 .. |I2S| replace:: I\ :sup:`2`\ S .. |I2C| replace:: I\ :sup:`2`\ C .. |trademark| replace:: :sup:`TM` diff --git a/lib_sw_pll/src/sw_pll_pfd.h b/lib_sw_pll/src/sw_pll_pfd.h index ace7131f..5de773ca 100644 --- a/lib_sw_pll/src/sw_pll_pfd.h +++ b/lib_sw_pll/src/sw_pll_pfd.h @@ -53,4 +53,4 @@ static inline void sw_pll_calc_error_from_port_timers( sw_pll_pfd_state_t * con { *first_loop = 1; } -} \ No newline at end of file +} From 781d70add8df86ef1e4b63742c6a0aa30e85e8c4 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 15:15:46 +0000 Subject: [PATCH 076/118] Add Kii into tests --- tests/test_app/main.c | 6 +++--- tests/test_app_low_level_api/main.c | 6 +++--- tests/test_lib_sw_pll.py | 31 +++++++++++++++++++---------- tests/test_lib_sw_pll_equiv.py | 10 ++++++++-- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/tests/test_app/main.c b/tests/test_app/main.c index f711f5fd..94391904 100644 --- a/tests/test_app/main.c +++ b/tests/test_app/main.c @@ -31,6 +31,8 @@ int main(int argc, char** argv) { fprintf(stderr, "kp\t\t%f\n", kp); float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); + float kii = atof(argv[i++]); + fprintf(stderr, "kii\t\t%f\n", kii); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); size_t pll_ratio = atoi(argv[i++]); @@ -50,8 +52,6 @@ int main(int argc, char** argv) { unsigned target_output_frequency = atoi(argv[i++]); fprintf(stderr, "target_output_frequency\t\t%d\n", target_output_frequency); - float kii = 0.0; - if(i + num_lut_entries != argc) { fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); return 1; @@ -109,6 +109,6 @@ int main(int argc, char** argv) { // xsim doesn't support our register and the val that was set gets // dropped - printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, sw_pll.pfd_state.mclk_diff, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); + printf("%i %x %hd %ld %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, sw_pll.pfd_state.mclk_diff, sw_pll.pi_state.error_accum, sw_pll.pi_state.error_accum_accum, sw_pll.first_loop, t1 - t0); } } diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index c1ae5d53..004c327d 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -31,6 +31,8 @@ int main(int argc, char** argv) { fprintf(stderr, "kp\t\t%f\n", kp); float ki = atof(argv[i++]); fprintf(stderr, "ki\t\t%f\n", ki); + float kii = atof(argv[i++]); + fprintf(stderr, "kii\t\t%f\n", kii); size_t loop_rate_count = atoi(argv[i++]); fprintf(stderr, "loop_rate_count\t\t%d\n", loop_rate_count); size_t pll_ratio = atoi(argv[i++]); @@ -50,8 +52,6 @@ int main(int argc, char** argv) { unsigned target_output_frequency = atoi(argv[i++]); fprintf(stderr, "target_output_frequency\t\t%d\n", target_output_frequency); - float kii = 0.0; - if(i + num_lut_entries != argc) { fprintf(stderr, "wrong number of params sent to main.c in xcore test app\n"); return 1; @@ -108,6 +108,6 @@ int main(int argc, char** argv) { // xsim doesn't support our register and the val that was set gets // dropped - printf("%i %x %hd %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, error, sw_pll.pi_state.error_accum, sw_pll.first_loop, t1 - t0); + printf("%i %x %hd %ld %ld %u %lu\n", s, sw_pll.lut_state.current_reg_val, error, sw_pll.pi_state.error_accum, sw_pll.pi_state.error_accum_accum, sw_pll.first_loop, t1 - t0); } } diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 0f4a3880..0054987d 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -31,6 +31,7 @@ class DutArgs: kp: float ki: float + kii: float loop_rate_count: int pll_ratio: int ref_clk_expected_inc: int @@ -55,7 +56,8 @@ def __init__(self, args: DutArgs, pll): args.target_output_frequency, nominal_control_rate_hz, args.kp, - args.ki, ) + args.ki, + args.kii ) def lut_func(self, error): """Sim requires a function to provide access to the LUT. This is that""" @@ -74,7 +76,7 @@ def do_control(self, mclk_pt, _ref_pt): """ f, l = self.ctrl.do_control_loop(mclk_pt) - return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 + return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, self.ctrl.controller.error_accum_accum, 0, 0 def do_control_from_error(self, error): """ @@ -83,7 +85,7 @@ def do_control_from_error(self, error): dco_ctl = self.ctrl.controller.get_dco_control_from_error(error) f, l = self.ctrl.dco.get_frequency_from_dco_control(dco_ctl) - return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, 0, 0 + return l, f, self.ctrl.controller.diff, self.ctrl.controller.error_accum, self.ctrl.controller.error_accum_accum, 0, 0 class Dut: """ @@ -95,6 +97,7 @@ def __init__(self, args: DutArgs, pll, xe_file=DUT_XE): self.args = DutArgs(**asdict(args)) # copies the values self.args.kp = self.args.kp self.args.ki = self.args.ki + self.args.kii = self.args.kii lut = self.args.lut self.args.lut = len(args.lut) # concatenate the parameters to the init function and the whole lut @@ -125,27 +128,27 @@ def __exit__(self, *_): def do_control(self, mclk_pt, ref_pt): """ - returns lock_state, reg_val, mclk_diff, error_acum, first_loop, ticks + returns lock_state, reg_val, mclk_diff, error_acum, error_acum_acum, first_loop, ticks """ self._process.stdin.write(f"{mclk_pt % 2**16} {ref_pt % 2**16}\n") self._process.stdin.flush() - locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() + locked, reg, diff, acum, acum_acum, first_loop, ticks = self._process.stdout.readline().strip().split() self.pll.update_frac_reg(int(reg, 16) | app_pll_frac_calc.frac_enable_mask) - return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) + return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(acum_acum), int(first_loop), int(ticks) def do_control_from_error(self, error): """ - returns lock_state, reg_val, mclk_diff, error_acum, first_loop, ticks + returns lock_state, reg_val, mclk_diff, error_acum, error_acum_acum, first_loop, ticks """ self._process.stdin.write(f"{error % 2**16}\n") self._process.stdin.flush() - locked, reg, diff, acum, first_loop, ticks = self._process.stdout.readline().strip().split() + locked, reg, diff, acum, acum_acum, first_loop, ticks = self._process.stdout.readline().strip().split() self.pll.update_frac_reg(int(reg, 16) | app_pll_frac_calc.frac_enable_mask) - return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(first_loop), int(ticks) + return int(locked), self.pll.get_output_frequency(), int(diff), int(acum), int(acum_acum), int(first_loop), int(ticks) def close(self): @@ -215,6 +218,7 @@ def basic_test_vector(request, solution_12288, bin_dir): target_output_frequency=target_mclk_f, kp=0.0, ki=1.0, + kii=0.0, loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 # have to call 512 times to do 1 # control update @@ -267,6 +271,7 @@ def basic_test_vector(request, solution_12288, bin_dir): "actual_diff": [], "clk_diff": [], "clk_diff_i": [], + "clk_diff_ii": [], "first_loop": [], "ticks": [] } @@ -288,7 +293,7 @@ def basic_test_vector(request, solution_12288, bin_dir): loop_time = ref_pt_per_loop / ref_f mclk_count = loop_time * mclk_f mclk_pt = mclk_pt + mclk_count - locked, mclk_f, e, ea, fl, ticks = dut.do_control(int(mclk_pt), int(ref_pt)) + locked, mclk_f, e, ea, eaa, fl, ticks = dut.do_control(int(mclk_pt), int(ref_pt)) results["target"].append(ref_f * (target_mclk_f / target_ref_f)) results["ref_f"].append(ref_f) @@ -301,6 +306,7 @@ def basic_test_vector(request, solution_12288, bin_dir): results["clk_diff"].append(e) results["actual_diff"].append(mclk_count - (ref_pt_per_loop * (target_mclk_f/target_ref_f))) results["clk_diff_i"].append(ea) + results["clk_diff_ii"].append(eaa) results["first_loop"].append(fl) results["ticks"].append(ticks) @@ -317,6 +323,11 @@ def basic_test_vector(request, solution_12288, bin_dir): plt.savefig(bin_dir/f"basic-test-vector-{name}-error-acum.png") plt.close() + plt.figure() + df[["target", "clk_diff_ii"]].plot(secondary_y=["target"]) + plt.savefig(bin_dir/f"basic-test-vector-{name}-error-acum-acum.png") + plt.close() + plt.figure() df[["exp_mclk_count", "mclk_count"]].plot() plt.savefig(bin_dir/f"basic-test-vector-{name}-counts.png") diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 007334db..dc369d1f 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -46,6 +46,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): target_output_frequency=target_mclk_f, kp=0.0, ki=1.0, + kii=0.0, loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 # have to call 512 times to do 1 # control update @@ -71,6 +72,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): "time": [], "clk_diff": [], "clk_diff_i": [], + "clk_diff_ii": [], "first_loop": [], "ticks": [] } @@ -89,13 +91,14 @@ def test_low_level_equivalence(solution_12288, bin_dir): print(f"Running: {name}") for input_error in input_errors: - locked, mclk_f, e, ea, fl, ticks = dut.do_control_from_error(input_error) + locked, mclk_f, e, ea, eaa, fl, ticks = dut.do_control_from_error(input_error) results[name]["mclk"].append(mclk_f) results[name]["time"].append(time) results[name]["locked"].append(locked) results[name]["clk_diff"].append(e) results[name]["clk_diff_i"].append(ea) + results[name]["clk_diff_ii"].append(eaa) results[name]["first_loop"].append(fl) results[name]["ticks"].append(ticks) time += 1 @@ -120,9 +123,12 @@ def test_low_level_equivalence(solution_12288, bin_dir): plt.close() # Check for equivalence - for compare_item in ["mclk", "clk_diff", "clk_diff_i"]: + for compare_item in ["mclk", "clk_diff", "clk_diff_i", "clk_diff_ii"]: C = results["C"][compare_item] Python = results["Python"][compare_item] + print("***", compare_item) + print(C, Python) + print() assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" print("TEST PASSED!") From 4efa99893e6a81e0c7e934d0eb8bd52b97b861ed Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 16:51:21 +0000 Subject: [PATCH 077/118] Fix missing handling of Kii --- lib_sw_pll/api/sw_pll.h | 3 +++ python/sw_pll/controller_model.py | 6 ++++-- python/sw_pll/sw_pll_sim.py | 2 +- tests/test_app_sdm_ctrl/main.c | 1 - tests/test_lib_sw_pll.py | 2 +- tests/test_lib_sw_pll_equiv.py | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index a6a2910e..714a0179 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -123,7 +123,10 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl { sw_pll->pi_state.Kp = Kp; sw_pll->pi_state.Ki = Ki; + sw_pll->pi_state.Kii = Kii; + sw_pll->pi_state.error_accum = 0; + sw_pll->pi_state.error_accum_accum = 0; if(Ki){ sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT }else{ diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index a66a7b31..06c56b20 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -30,7 +30,8 @@ def _reset_controller(self): Reset any accumulated state """ self.error_accum = 0.0 - self.error_accum_accum = 0.0 + self.error_accum_accum = 0.0 + self.self.total_error = 0.0 def do_control_from_error(self, error): """ @@ -187,8 +188,9 @@ def do_control_from_error(self, error): """ Kp = 1.0 Ki = 0.1 + Kii = 0.0 - sw_pll = lut_pi_ctrl(Kp, Ki, verbose=True) + sw_pll = lut_pi_ctrl(Kp, Ki, Kii=Kii, verbose=True) for error_input in range(-10, 20): dco_ctrl = sw_pll.do_control_from_error(error_input) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index aabb6055..c58e3a14 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -44,7 +44,7 @@ def __init__( self, """ self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency) - self.controller = lut_pi_ctrl(Kp, Ki, verbose=False) + self.controller = lut_pi_ctrl(Kp, Ki, Kii=Kii, verbose=False) self.dco = lut_dco(verbose=False) self.target_output_frequency = target_output_frequency diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index aac24625..202bae21 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -100,7 +100,6 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { uint32_t t0 = get_reference_time(); int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, -mclk_diff); int32_t dco_ctl = sw_pll_sdm_post_control_proc(&sw_pll, error); - // sw_pll_send_ctrl_to_sdm_task() uint32_t t1 = get_reference_time(); printf("%ld %ld %d %lu\n", error, dco_ctl, sw_pll.lock_status, t1 - t0); diff --git a/tests/test_lib_sw_pll.py b/tests/test_lib_sw_pll.py index 0054987d..0179c37f 100644 --- a/tests/test_lib_sw_pll.py +++ b/tests/test_lib_sw_pll.py @@ -57,7 +57,7 @@ def __init__(self, args: DutArgs, pll): nominal_control_rate_hz, args.kp, args.ki, - args.kii ) + Kii=args.kii ) def lut_func(self, error): """Sim requires a function to provide access to the LUT. This is that""" diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index dc369d1f..d285c4f7 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -112,6 +112,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): times = results[dut]["time"] clk_diff = results[dut]["clk_diff"] clk_diff_i = results[dut]["clk_diff_i"] + clk_diff_ii = results[dut]["clk_diff_ii"] locked = results[dut]["locked"] plt.plot(mclk, label=dut) From b6d69eb9e8722bfcc80188812310cb162eea5682 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 17:15:43 +0000 Subject: [PATCH 078/118] II clipping now tested and equivalent --- lib_sw_pll/src/sw_pll.c | 3 ++- tests/test_lib_sw_pll_equiv.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index c31dff16..b20f5d83 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -118,9 +118,10 @@ __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { sw_pll->pi_state.error_accum += error; // Integral error. - sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error. sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + + sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error. sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index d285c4f7..7b2a55a7 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -45,8 +45,8 @@ def test_low_level_equivalence(solution_12288, bin_dir): args = DutArgs( target_output_frequency=target_mclk_f, kp=0.0, - ki=1.0, - kii=0.0, + ki=2.0, + kii=1.0, loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 # have to call 512 times to do 1 # control update @@ -124,7 +124,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): plt.close() # Check for equivalence - for compare_item in ["mclk", "clk_diff", "clk_diff_i", "clk_diff_ii"]: + for compare_item in ["clk_diff", "clk_diff_i", "clk_diff_ii", "mclk"]: C = results["C"][compare_item] Python = results["Python"][compare_item] print("***", compare_item) From ae58cb92c117395ab7a656f6ec60aec6978e067f Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 27 Nov 2023 17:17:08 +0000 Subject: [PATCH 079/118] Var typo --- python/sw_pll/controller_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sw_pll/controller_model.py b/python/sw_pll/controller_model.py index 06c56b20..17f165fe 100644 --- a/python/sw_pll/controller_model.py +++ b/python/sw_pll/controller_model.py @@ -31,7 +31,7 @@ def _reset_controller(self): """ self.error_accum = 0.0 self.error_accum_accum = 0.0 - self.self.total_error = 0.0 + self.total_error = 0.0 def do_control_from_error(self, error): """ From 1fe88c9fc6c8eaf07b6143a882a027d914acb5c6 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 08:37:58 +0000 Subject: [PATCH 080/118] Remove some copy pasta test comments --- tests/test_app_low_level_api/main.c | 1 + tests/test_lib_sw_pll_equiv.py | 19 +++++-------------- tests/test_sdm_ctrl_equiv.py | 8 -------- tests/test_sdm_dco_equiv.py | 14 ++------------ 4 files changed, 8 insertions(+), 34 deletions(-) diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index 004c327d..64cbc882 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -102,6 +102,7 @@ int main(int argc, char** argv) { int16_t error; sscanf(read_buf, "%hu", &error); fprintf(stderr, "%hu\n", error); + uint32_t t0 = get_reference_time(); sw_pll_lock_status_t s = sw_pll_do_control_from_error(&sw_pll, error); uint32_t t1 = get_reference_time(); diff --git a/tests/test_lib_sw_pll_equiv.py b/tests/test_lib_sw_pll_equiv.py index 7b2a55a7..e713b0d5 100644 --- a/tests/test_lib_sw_pll_equiv.py +++ b/tests/test_lib_sw_pll_equiv.py @@ -1,13 +1,5 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -""" -Assorted tests which run the test_app in xsim - -This file is structured as a fixture which takes a while to run -and generates a pandas.DataFrame containing some time domain -outputs from the control loops. Then a series of tests which -check different aspects of the content of this DataFrame. -""" import pytest import numpy as np @@ -46,10 +38,8 @@ def test_low_level_equivalence(solution_12288, bin_dir): target_output_frequency=target_mclk_f, kp=0.0, ki=2.0, - kii=1.0, - loop_rate_count=loop_rate_count, # copied from ed's setup in 3800 - # have to call 512 times to do 1 - # control update + kii=1.0, # NOTE WE SPECIFICALLY ENABLE KII in this test as it is not tested elsewhere + loop_rate_count=loop_rate_count, pll_ratio=int(target_mclk_f / target_ref_f), ref_clk_expected_inc=0, app_pll_ctl_reg_val=0, @@ -63,7 +53,7 @@ def test_low_level_equivalence(solution_12288, bin_dir): pll.update_frac_reg(start_reg | app_pll_frac_calc.frac_enable_mask) - input_errors = np.random.randint(-lut_size // 2, lut_size // 2, size = 40) + input_errors = np.random.randint(-lut_size // 10, lut_size // 10, size = 40) print(f"input_errors: {input_errors}") result_categories = { @@ -128,7 +118,8 @@ def test_low_level_equivalence(solution_12288, bin_dir): C = results["C"][compare_item] Python = results["Python"][compare_item] print("***", compare_item) - print(C, Python) + for c, p in zip(C, Python): + print(c, p) print() assert np.allclose(C, Python), f"Error in low level equivalence checking of: {compare_item}" diff --git a/tests/test_sdm_ctrl_equiv.py b/tests/test_sdm_ctrl_equiv.py index b120bde0..3dda415a 100644 --- a/tests/test_sdm_ctrl_equiv.py +++ b/tests/test_sdm_ctrl_equiv.py @@ -1,13 +1,5 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -""" -Assorted tests which run the test_app in xsim - -This file is structured as a fixture which takes a while to run -and generates a pandas.DataFrame containing some time domain -outputs from the control loops. Then a series of tests which -check different aspects of the content of this DataFrame. -""" import pytest import numpy as np diff --git a/tests/test_sdm_dco_equiv.py b/tests/test_sdm_dco_equiv.py index 8433a06e..fa8e84f6 100644 --- a/tests/test_sdm_dco_equiv.py +++ b/tests/test_sdm_dco_equiv.py @@ -1,13 +1,5 @@ # Copyright 2023 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. -""" -Assorted tests which run the test_app in xsim - -This file is structured as a fixture which takes a while to run -and generates a pandas.DataFrame containing some time domain -outputs from the control loops. Then a series of tests which -check different aspects of the content of this DataFrame. -""" import pytest import numpy as np @@ -18,8 +10,6 @@ from matplotlib import pyplot as plt from subprocess import Popen, PIPE - -# from sw_pll.app_pll_model import app_pll_frac_calc from sw_pll.dco_model import sigma_delta_dco from test_lib_sw_pll import bin_dir @@ -85,8 +75,8 @@ def close(self): def test_sdm_dco_equivalence(bin_dir): """ - Simple low level test of equivalence using do_control_from_error - Feed in random numbers into C and Python DUTs and see if we get the same results + Simple low level test of equivalence using do_modulate + Feed in a sweep of DCO control vals into C and Python DUTs and see if we get the same results """ args = DutSDMDCOArgs( From fa28fce82904f1092112f76b659b9e513940d409 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 10:45:22 +0000 Subject: [PATCH 081/118] Update readme and add diagrams + source --- .gitignore | 1 + README.rst | 49 +++---- doc/diagram_source/PLL_block_diagram.drawio | 60 ++++++++ .../resource_setup_sw_pll_Example.drawio | 135 ++++++++++++++++++ .../sw_pll_model_architecture.drawio | 47 ++++++ doc/images/PLL_block_diagram.png | Bin 0 -> 27751 bytes doc/images/modulated_fft_lut.png | Bin 0 -> 33597 bytes doc/images/modulated_fft_sdm.png | Bin 0 -> 35419 bytes doc/images/resource_setup_example.png | Bin 0 -> 64438 bytes doc/images/sdm_dco_range.png | Bin 0 -> 51636 bytes doc/images/tracking_lut.png | Bin 0 -> 42954 bytes doc/images/tracking_sdm.png | Bin 0 -> 38225 bytes 12 files changed, 266 insertions(+), 26 deletions(-) create mode 100644 doc/diagram_source/PLL_block_diagram.drawio create mode 100644 doc/diagram_source/resource_setup_sw_pll_Example.drawio create mode 100644 doc/diagram_source/sw_pll_model_architecture.drawio create mode 100644 doc/images/PLL_block_diagram.png create mode 100644 doc/images/modulated_fft_lut.png create mode 100644 doc/images/modulated_fft_sdm.png create mode 100644 doc/images/resource_setup_example.png create mode 100644 doc/images/sdm_dco_range.png create mode 100644 doc/images/tracking_lut.png create mode 100644 doc/images/tracking_sdm.png diff --git a/.gitignore b/.gitignore index 7c9e7606..c763e2f3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ compile_commands.json .python-version __pycache__/ modules/ +doc/doc/_build diff --git a/README.rst b/README.rst index 5379723d..6352f979 100644 --- a/README.rst +++ b/README.rst @@ -3,23 +3,30 @@ lib_sw_pll This library contains software that, together with the on-chip application PLL, provides a PLL that will generate a clock phase locked to an input clock. +It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators, a Phase Frequency Detector and +configurable Proportional Integral (PI) controllers which together form a hybrid Software/Hardware Phase Locked Loop (PLL). + ******************************** Building and running the example ******************************** -Ensure a correctly configured installation of the XMOS tools. +Ensure a correctly configured installation of the XMOS tools and open an XTC command shell. Please check that the XMOS tools are correctly +sourced by running the following command:: + + $ xcc + xcc: no input files .. note:: Instructions for installing and configuring the XMOS tools appear on `the XMOS web site `_. -Place the fwk_core and fwk_io repositories in the modules directory of lib_sw_pll. +Place the fwk_core and fwk_io repositories in the modules directory of lib_sw_pll. These are required dependencies for the example apps. To do so, from the root of lib_sw_pll (where this read me file exists) type:: mkdir modules - pushd modules + cd modules git clone --recurse-submodules git@github.com:xmos/fwk_core.git git clone --recurse-submodules git@github.com:xmos/fwk_io.git - popd + cd .. .. note:: The fwk_core and fwk_io repositories have not been sub-moduled into this Git repository because only the examples depend upon them. @@ -30,43 +37,33 @@ On linux:: cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake cd build - make simple + make simple_lut simple_sdm i2s_slave_lut On Windows:: - cmake -G "NMake Makefiles" -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake + cmake -G "Ninja" -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake cd build - nmake simple + ninja simple_lut simple_sdm i2s_slave_lut -To run the firmware, first connect LRCLK and BCLK (connects the test clock output to the PLL input) -and run the following command where can be *simple* which uses the XCORE-AI-EXPLORER board -or *i2s_slave* which uses either the EVK3600 of EVK3800 board:: +To run the firmware, first connect LRCLK and BCLK (connects the test clock output to the PLL reference input) +and run the following command where can be *simple_lut* or *simple_sdm* which use the XCORE-AI-EXPLORER board +or *i2s_slave_lut* which uses the XK-VOICE-SQ66 board:: xrun --xscope .xe -For simple.xe, to see the PLL lock, put one scope probe on either LRCLK/BCLK (reference) and the other on PORT_I2S_DAC_DATA to see the -recovered clock which has been hardware divided back down to the same rate as the input clock. +For simple_xxx.xe, to see the PLL lock, put one scope probe on either LRCLK/BCLK (reference input) and the other on PORT_I2S_DAC_DATA to see the +recovered clock which has been hardware divided back down to the same rate as the input reference clock. -For i2s_slave.xe you will need to connect a 48kHz I2S master to the LRCLK, BCLK pins. You may then observe the I2S input being +For i2s_slave_lut.xe you will need to connect a 48kHz I2S master to the LRCLK, BCLK pins. You may then observe the I2S input being looped back to the output and the MCLK being generated. A divided version of MCLK is output on PORT_I2S_DATA2 which allows -direct comparison of the input reference (LRCLK) with the recovered clock at the same frequency. - -***************** -Running the tests -***************** - -A test is available which checks the C implementation and the simulator, to run it:: +direct comparison of the input reference (LRCLK) with the recovered clock at the same, and locked, frequency. - cmake -B build -DCMAKE_TOOLCHAIN_FILE=xmos_cmake_toolchain/xs3a.cmake - cmake --build build --target test_app - pip install -r . - cd tests - pytest ********************************* Generating new PLL configurations ********************************* -Please see `doc/sw_pll.rst` for further details on how to design and build new sw_pll configurations. This covers the tradeoff between lock range, noise and memory usage. \ No newline at end of file +Please see `doc/sw_pll.rst` for further details on how to design and build new sw_pll configurations. This covers the tradeoff between lock range, +oscillator noise and resource usage. \ No newline at end of file diff --git a/doc/diagram_source/PLL_block_diagram.drawio b/doc/diagram_source/PLL_block_diagram.drawio new file mode 100644 index 00000000..90bb4310 --- /dev/null +++ b/doc/diagram_source/PLL_block_diagram.drawio @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/diagram_source/resource_setup_sw_pll_Example.drawio b/doc/diagram_source/resource_setup_sw_pll_Example.drawio new file mode 100644 index 00000000..e3f01a46 --- /dev/null +++ b/doc/diagram_source/resource_setup_sw_pll_Example.drawio @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/diagram_source/sw_pll_model_architecture.drawio b/doc/diagram_source/sw_pll_model_architecture.drawio new file mode 100644 index 00000000..d7b6f38e --- /dev/null +++ b/doc/diagram_source/sw_pll_model_architecture.drawio @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/images/PLL_block_diagram.png b/doc/images/PLL_block_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..ef19ccbb8bd23d6bc334eb7abfb7fb6eec683e03 GIT binary patch literal 27751 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 literal 0 HcmV?d00001 diff --git a/doc/images/modulated_fft_lut.png b/doc/images/modulated_fft_lut.png new file mode 100644 index 0000000000000000000000000000000000000000..b839907d448057fd6957a2587e21727d83fe0da6 GIT binary patch literal 33597 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 ZcREnH!k=-@3%SCY7qu^Bows=MKLA&#ZZrS@ literal 0 HcmV?d00001 diff --git a/doc/images/resource_setup_example.png b/doc/images/resource_setup_example.png new file mode 100644 index 0000000000000000000000000000000000000000..af49b80c575033b4b9cf0b5ea8e4af965ddee531 GIT binary patch literal 64438 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%0Dq3`tWSdb4G(C+Wm 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 literal 0 HcmV?d00001 diff --git a/doc/images/tracking_lut.png b/doc/images/tracking_lut.png new file mode 100644 index 0000000000000000000000000000000000000000..ced9ce56de4238a73a11c117be6c8e5d1c42bdf8 GIT binary patch literal 42954 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 F;Gq$zXUtkwC<}j=|5hE;q@yus8tG1p>LKdyl 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 literal 0 HcmV?d00001 diff --git a/doc/images/tracking_sdm.png b/doc/images/tracking_sdm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a103d6350268c16373c1669930b9b6030e58637 GIT binary patch literal 38225 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 CQc%SJ literal 0 HcmV?d00001 From ef7592357e477b17ff75964585a1205165620a7e Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 16:01:37 +0000 Subject: [PATCH 082/118] WIP docs update + lots of images --- README.rst | 2 +- doc/diagram_source/lut_pll.drawio | 80 ++++++++++++++++ doc/diagram_source/sdm_pll.drawio | 54 +++++++++++ doc/images/sw_pll_range.png | Bin 43411 -> 0 bytes doc/sw_pll.rst | 151 ++++++++++++++++++++++++++++-- 5 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 doc/diagram_source/lut_pll.drawio create mode 100644 doc/diagram_source/sdm_pll.drawio delete mode 100644 doc/images/sw_pll_range.png diff --git a/README.rst b/README.rst index 6352f979..ba4c40c1 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ lib_sw_pll This library contains software that, together with the on-chip application PLL, provides a PLL that will generate a clock phase locked to an input clock. -It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators, a Phase Frequency Detector and +It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators (DCO), a Phase Frequency Detector (PFD) and configurable Proportional Integral (PI) controllers which together form a hybrid Software/Hardware Phase Locked Loop (PLL). ******************************** diff --git a/doc/diagram_source/lut_pll.drawio b/doc/diagram_source/lut_pll.drawio new file mode 100644 index 00000000..d3d7742d --- /dev/null +++ b/doc/diagram_source/lut_pll.drawio @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/diagram_source/sdm_pll.drawio b/doc/diagram_source/sdm_pll.drawio new file mode 100644 index 00000000..d5641a57 --- /dev/null +++ b/doc/diagram_source/sdm_pll.drawio @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/images/sw_pll_range.png b/doc/images/sw_pll_range.png deleted file mode 100644 index 6dd4fbbe9c1733dfddeb908e8cc7da62ef598597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43411 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 G^#1}V1Eb{t diff --git a/doc/sw_pll.rst b/doc/sw_pll.rst index 5e736420..1ad0b8b1 100644 --- a/doc/sw_pll.rst +++ b/doc/sw_pll.rst @@ -1,21 +1,136 @@ How the Software PLL works -------------------------- -The XCORE-AI devices come with a secondary PLL sometimes called the Application (App) PLL. This PLL +A Phase Locked Loop (PLL) is a typically a circuit that allows generation of a clock which is synchronised +to an input reference clock by both phase and frequency. They consist of a number of components: + + - A Phase Frequency Detector (PFD) which measures the difference between a reference clock and the divided generated clock. + - A control loop, typically a Proportional Integral (PI) controller to close the loop and zero the error. + - A Digitally Controlled Oscillator (DCO) which converts a control signal into a clock frequency. + +.. figure:: ./images/PLL_block_diagram.png + :width: 100% + + Basic PLL Block Diagram + + +xcore-ai devices have on-chip a secondary PLL sometimes called the Application (App) PLL. This PLL multiplies the clock from the on-board crystal source and has a fractional register allowing very fine control -over the multiplication and division ratios. +over the multiplication and division ratios under software. However, it does not support an external reference clock input and so cannot natively track and lock -to an external clock reference. This SW-PLL module is a set of scripts and firmware which enables the -provision of an input clock which, along with a control loop, allows tracking of the external reference +to an external clock reference. This software PLL module provides a set of scripts and firmware which enables the +provision of an input reference clock which, along with a control loop, allows tracking of the external reference over a certain range. +There are two types of PLL, or specifically Digitally Controlled Oscillators (DCO), supported in this library. + +LUT based DCO +............. + +The LUT based DCO allows a discrete set of fractional settings resulting in a number of frequency steps. +The LUT is pre-computed table which provides a set of monotonic increasing register settings. The LUT +based DCO requires very low compute allowing it to typically be run in a sample based loop at audio +frequencies such as 48kHz or 44.1kHz. It does require two bytes per LUT entry. It provides reasonable +jitter performance suitable for voice or entry level HiFi. + +.. figure:: ./images/lut_pll.png + :width: 100% + + LUT DCO based PLL + + The range is governed by the look up table (LUT) which has a finite number of entries and consequently -a step size which affects the output jitter performance. The index into the LUT is controlled by a +a step size which affects the output jitter performance when the controller oscillates between two +settings. Note that the actual range and number of steps is highly configurable. + +.. figure:: ./images/lut_dco_range.png + :width: 100% + + LUT discrete output frequencies + + +The index into the LUT is controlled by a PI controller which multiplies the error in put and integral error input by the supplied loop constants. An integrated wind up limiter for the integral term is nominally set at 2x the maximum LUT index deviation to prevent excessive overshoot where the starting input error is high. +A time domain plot of how the controller (typically running at around 100Hz) selects between adjacent +LUT entries, and the consequential frequency modulation effect, can be seen in the following diagrams. + +.. figure:: ./images/tracking_lut.png + :width: 100% + + LUT selection when tracking a constant input frequency + +.. figure:: ./images/modulated_fft_lut.png + :width: 100% + + LUT noise plot when when tracking a constant input frequency + +SDM Based DCO +............. + +The SDM based DCO provides a fixed number (9 in this case) of frequency steps which are jumped between +at a high rate (eg. 1MHz) but requires a dedicated logical core to run the SDM and update the PLL +fractional register. It typically provides better audio quality by pushing the noise floor up into the +inaudible part of the spectrum. A fixed set of SDM coefficients and loop filters are provided which +have been hand tuned to provide either 25.576MHz or 22.5792MHz clocks suitable for HiFi systems + +.. figure:: ./images/sdm_pll.png + :width: 100% + + SDM DCO based PLL + +The steps for the SDM output are quite large which means a wide range is typically available. Note +that the tradeoff between number of steps, step size and range can be made during the LUT generation +stage. + +.. figure:: ./images/sdm_dco_range.png + :width: 100% + + SDM discrete output frequencies + +A time domain plot of how the Sigma Delta Modulator jumps rapidly between multiple frequencies and the consequential +spread of the noise floor can be seen in the following diagrams. + +.. figure:: ./images/tracking_sdm.png + :width: 100% + + SDM frequency selection when tracking a constant input frequency + +.. figure:: ./images/modulated_fft_sdm.png + :width: 100% + + SDM noise plot when when tracking a constant input frequency + + +There are trade-offs between the two types of DCO which are summarised in the following table. + +.. list-table:: LUT vs SDM DCO trade-offs + :widths: 15 30 30 + :header-rows: 1 + + * - Comparison item + - LUT DCO + - SDM DCO + * - Jitter + - Low - ~5ns + - Very Low - ~10-50ps + * - Memory Usage + - Moderate - 3kB + - Low - 1kB + * - MIPS Usage + - Low - <5 + - Fair - ~50 + * - Lock Range PPM + - Moderate - 100-1000 + - Wide - 1500-3000 + + +Controller API Notes +.................... + In addition to the standard API which takes a clock counting input, for applications where the PLL is to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level API allows the Software PLL to track an arbitrary clock source which is calculated by another means. @@ -28,9 +143,14 @@ reach the appropriate compromise of performance and resource usage for your appl Running the PI simulation and LUT generation script --------------------------------------------------- -In the ``python/sw_pll`` directory you will find two files:: +In the ``python/sw_pll`` directory you will find multiple files:: . + ├── analysis_tools.py + ├── app_pll_model.py + ├── controller_model.py + ├── dco_model.py + ├── pfd_model.py ├── pll_calc.py └── sw_pll_sim.py @@ -69,8 +189,6 @@ between two discrete fractional settings in the LUT and is expected. You may adj loop is called to center this noise around different frequencies or decrease the step size (larger LUT) to manage the amplitude of this artifact. -.. image:: ./images/sw_pll_range.png - :width: 500 Here you can see the step response of the control loop below. You can see it track smaller step changes but for the @@ -81,7 +199,7 @@ The step response is quite fast and you can see even a very sharp change in freq a handful of control loop iterations. .. image:: ./images/pll_step_response.png - :width: 500 + :width: 100% Note that each time you run ``sw_pll_sim.py`` and the ``fractions.h`` file is produced, a short report will be produced that indicates the achieved range of settings. Below is a typical report showing what information is summarised:: @@ -155,7 +273,7 @@ Example configurations A number of example configurations, which demonstrate the effect on PPM, step size etc. of changing various parameters, is provided in the ``sw_pll_sim.py`` file. Search for ``profiles`` and ``profile_choice`` in this file. Change profile choice index to select the different example profiles and run the python file again. -.. list-table:: xscope throughput +.. list-table:: Example LUT DCO configurations :widths: 50 50 50 50 50 :header-rows: 1 @@ -203,6 +321,19 @@ Transferring the results to C Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Either consult the ``sw_pll.h`` API file (below) for details or follow one of the examples in the ``/examples`` directory. +Simple Example Resource Setup +----------------------------- + +The xcore-ai has a number of resources on chip. In the `simple` examples both clock blocks and ports are connected together to provide an input to +the PDF and provide a scaled output clock. The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intinsic functions in ``lib_xcore``. +To help visualise how these resources work together, please see the below diagram. + +.. figure:: ./images/resource_setup_example.png + :width: 100% + + Use of Ports and Clock Blocks in the examples + + lib_sw_pll API -------------- From 4004640daabfa48cc578a6b61ce66f76e6ebe0b1 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 16:46:50 +0000 Subject: [PATCH 083/118] rename examples for lut/sdm --- README.rst | 8 +- doc/sw_pll.rst | 24 +- examples/examples.cmake | 4 +- examples/i2s_slave/src/fractions.h | 333 ------------------ .../i2s_slave_lut.cmake} | 14 +- .../src/config.xscope | 0 .../src/i2s_slave_sw_pll.c | 0 .../{i2s_slave => i2s_slave_lut}/src/main.xc | 0 .../src/xvf3800_qf60.xn | 0 examples/simple/src/fractions.h | 333 ------------------ .../simple_lut.cmake} | 14 +- .../{simple => simple_lut}/src/config.xscope | 0 examples/{simple => simple_lut}/src/main.xc | 0 .../src/simple_sw_pll.c | 0 lib_sw_pll/api/sw_pll.h | 31 ++ 15 files changed, 65 insertions(+), 696 deletions(-) delete mode 100644 examples/i2s_slave/src/fractions.h rename examples/{i2s_slave/i2s_slave.cmake => i2s_slave_lut/i2s_slave_lut.cmake} (62%) rename examples/{i2s_slave => i2s_slave_lut}/src/config.xscope (100%) rename examples/{i2s_slave => i2s_slave_lut}/src/i2s_slave_sw_pll.c (100%) rename examples/{i2s_slave => i2s_slave_lut}/src/main.xc (100%) rename examples/{i2s_slave => i2s_slave_lut}/src/xvf3800_qf60.xn (100%) delete mode 100644 examples/simple/src/fractions.h rename examples/{simple/simple.cmake => simple_lut/simple_lut.cmake} (73%) rename examples/{simple => simple_lut}/src/config.xscope (100%) rename examples/{simple => simple_lut}/src/main.xc (100%) rename examples/{simple => simple_lut}/src/simple_sw_pll.c (100%) diff --git a/README.rst b/README.rst index ba4c40c1..ad516bc9 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,11 @@ This library contains software that, together with the on-chip application PLL, It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators (DCO), a Phase Frequency Detector (PFD) and configurable Proportional Integral (PI) controllers which together form a hybrid Software/Hardware Phase Locked Loop (PLL). -******************************** -Building and running the example -******************************** +Examples are provided showing a master clock locking to an low frequency input clock and to an I2S slave interface. + +********************************* +Building and running the examples +********************************* Ensure a correctly configured installation of the XMOS tools and open an XTC command shell. Please check that the XMOS tools are correctly sourced by running the following command:: diff --git a/doc/sw_pll.rst b/doc/sw_pll.rst index 1ad0b8b1..6cb7e076 100644 --- a/doc/sw_pll.rst +++ b/doc/sw_pll.rst @@ -55,7 +55,7 @@ PI controller which multiplies the error in put and integral error input by the An integrated wind up limiter for the integral term is nominally set at 2x the maximum LUT index deviation to prevent excessive overshoot where the starting input error is high. -A time domain plot of how the controller (typically running at around 100Hz) selects between adjacent +A time domain plot of how the controller (typically running at around 100 Hz) selects between adjacent LUT entries, and the consequential frequency modulation effect, can be seen in the following diagrams. .. figure:: ./images/tracking_lut.png @@ -72,10 +72,12 @@ SDM Based DCO ............. The SDM based DCO provides a fixed number (9 in this case) of frequency steps which are jumped between -at a high rate (eg. 1MHz) but requires a dedicated logical core to run the SDM and update the PLL -fractional register. It typically provides better audio quality by pushing the noise floor up into the +at a high rate (eg. 1 MHz) but requires a dedicated logical core to run the SDM and update the PLL +fractional register. The SDM is third order. + +It typically provides better audio quality by pushing the noise floor up into the inaudible part of the spectrum. A fixed set of SDM coefficients and loop filters are provided which -have been hand tuned to provide either 25.576MHz or 22.5792MHz clocks suitable for HiFi systems +have been hand tuned to provide either 24.576 MHz or 22.5792 MHz clocks suitable for HiFi systems .. figure:: ./images/sdm_pll.png :width: 100% @@ -83,7 +85,7 @@ have been hand tuned to provide either 25.576MHz or 22.5792MHz clocks suitable f SDM DCO based PLL The steps for the SDM output are quite large which means a wide range is typically available. Note -that the tradeoff between number of steps, step size and range can be made during the LUT generation +that the trade-off between number of steps, step size and range can be made during the LUT generation stage. .. figure:: ./images/sdm_dco_range.png @@ -115,13 +117,13 @@ There are trade-offs between the two types of DCO which are summarised in the fo - LUT DCO - SDM DCO * - Jitter - - Low - ~5ns - - Very Low - ~10-50ps + - Low - ~1-2 ns + - Very Low - ~10-50 ps * - Memory Usage - - Moderate - 3kB - - Low - 1kB + - Moderate - 3 kB + - Low - 1 kB * - MIPS Usage - - Low - <5 + - Low - < 5 - Fair - ~50 * - Lock Range PPM - Moderate - 100-1000 @@ -294,7 +296,7 @@ Search for ``profiles`` and ``profile_choice`` in this file. Change profile choi - 826 * - 12.288 - 48.0 - - 500 + - 1000 - 31.0 - 1580 * - 24.576 diff --git a/examples/examples.cmake b/examples/examples.cmake index 7f868b09..5aa41fe7 100644 --- a/examples/examples.cmake +++ b/examples/examples.cmake @@ -1,3 +1,3 @@ -include(${CMAKE_CURRENT_LIST_DIR}/simple/simple.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/simple_lut/simple_lut.cmake) include(${CMAKE_CURRENT_LIST_DIR}/simple_sdm/simple_sdm.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/i2s_slave/i2s_slave.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/i2s_slave_lut/i2s_slave_lut.cmake) diff --git a/examples/i2s_slave/src/fractions.h b/examples/i2s_slave/src/fractions.h deleted file mode 100644 index 7c6c4a01..00000000 --- a/examples/i2s_slave/src/fractions.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -// Header file listing all fraction options for a max denominator of 80 -// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. -short frac_values_80[327] = { -0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 -0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 -0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 -0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 -0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 -0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 -0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 -0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 -0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 -0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 -0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 -0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 -0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 -0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 -0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 -0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 -0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 -0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 -0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 -0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 -0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 -0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 -0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 -0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 -0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 -0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 -0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 -0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 -0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 -0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 -0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 -0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 -0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 -0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 -0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 -0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 -0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 -0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 -0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 -0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 -0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 -0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 -0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 -0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 -0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 -0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 -0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 -0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 -0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 -0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 -0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 -0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 -0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 -0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 -0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 -0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 -0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 -0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 -0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 -0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 -0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 -0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 -0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 -0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 -0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 -0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 -0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 -0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 -0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 -0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 -0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 -0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 -0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 -0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 -0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 -0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 -0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 -0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 -0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 -0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 -0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 -0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 -0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 -0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 -0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 -0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 -0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 -0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 -0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 -0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 -0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 -0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 -0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 -0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 -0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 -0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 -0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 -0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 -0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 -0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 -0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 -0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 -0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 -0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 -0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 -0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 -0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 -0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 -0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 -0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 -0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 -0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 -0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 -0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 -0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 -0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 -0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 -0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 -0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 -0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 -0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 -0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 -0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 -0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 -0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 -0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 -0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 -0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 -0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 -0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 -0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 -0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 -0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 -0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 -0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 -0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 -0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 -0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 -0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 -0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 -0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 -0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 -0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 -0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 -0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 -0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 -0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 -0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 -0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 -0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 -0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 -0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 -0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 -0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 -0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 -0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 -0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 -0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 -0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 -0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 -0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 -0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 -0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 -0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 -0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 -0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 -0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 -0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 -0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 -0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 -0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 -0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 -0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 -0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 -0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 -0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 -0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 -0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 -0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 -0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 -0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 -0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 -0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 -0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 -0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 -0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 -0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 -0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 -0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 -0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 -0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 -0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 -0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 -0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 -0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 -0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 -0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 -0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 -0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 -0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 -0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 -0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 -0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 -0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 -0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 -0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 -0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 -0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 -0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 -0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 -0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 -0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 -0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 -0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 -0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 -0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 -0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 -0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 -0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 -0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 -0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 -0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 -0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 -0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 -0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 -0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 -0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 -0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 -0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 -0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 -0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 -0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 -0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 -0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 -0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 -0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 -0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 -0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 -0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 -0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 -0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 -0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 -0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 -0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 -0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 -0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 -0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 -0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 -0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 -0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 -0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 -0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 -0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 -0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 -0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 -0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 -0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 -0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 -0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 -0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 -0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 -0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 -0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 -0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 -0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 -0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 -0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 -0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 -0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 -0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 -0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 -0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 -0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 -0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 -0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 -0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 -0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 -0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 -0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 -0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 -0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 -0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 -0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 -0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 -0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 -0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 -0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 -0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 -0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 -0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 -0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 -0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 -0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 -0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 -0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 -0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 -0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 -0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 -0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 -0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 -0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 -0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 -0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 -0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 -0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 -0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 -0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 -0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 -0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 -0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 -0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 -0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 -0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 -0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 -0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 -0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 -0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 -0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 -0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 -0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 -0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 -0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 -0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 -0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 -0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 -0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 -0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 -}; diff --git a/examples/i2s_slave/i2s_slave.cmake b/examples/i2s_slave_lut/i2s_slave_lut.cmake similarity index 62% rename from examples/i2s_slave/i2s_slave.cmake rename to examples/i2s_slave_lut/i2s_slave_lut.cmake index bdb59d55..dca21145 100644 --- a/examples/i2s_slave/i2s_slave.cmake +++ b/examples/i2s_slave_lut/i2s_slave_lut.cmake @@ -32,10 +32,10 @@ set(APP_LINK_OPTIONS #********************** # Tile Targets #********************** -add_executable(i2s_slave) -target_sources(i2s_slave PUBLIC ${APP_SOURCES}) -target_include_directories(i2s_slave PUBLIC ${APP_INCLUDES}) -target_compile_definitions(i2s_slave PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(i2s_slave PRIVATE ${APP_COMPILER_FLAGS}) -target_link_options(i2s_slave PRIVATE ${APP_LINK_OPTIONS}) -target_link_libraries(i2s_slave PUBLIC lib_sw_pll lib_i2s) +add_executable(i2s_slave_lut) +target_sources(i2s_slave_lut PUBLIC ${APP_SOURCES}) +target_include_directories(i2s_slave_lut PUBLIC ${APP_INCLUDES}) +target_compile_definitions(i2s_slave_lut PRIVATE ${APP_COMPILE_DEFINITIONS}) +target_compile_options(i2s_slave_lut PRIVATE ${APP_COMPILER_FLAGS}) +target_link_options(i2s_slave_lut PRIVATE ${APP_LINK_OPTIONS}) +target_link_libraries(i2s_slave_lut PUBLIC lib_sw_pll lib_i2s) diff --git a/examples/i2s_slave/src/config.xscope b/examples/i2s_slave_lut/src/config.xscope similarity index 100% rename from examples/i2s_slave/src/config.xscope rename to examples/i2s_slave_lut/src/config.xscope diff --git a/examples/i2s_slave/src/i2s_slave_sw_pll.c b/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c similarity index 100% rename from examples/i2s_slave/src/i2s_slave_sw_pll.c rename to examples/i2s_slave_lut/src/i2s_slave_sw_pll.c diff --git a/examples/i2s_slave/src/main.xc b/examples/i2s_slave_lut/src/main.xc similarity index 100% rename from examples/i2s_slave/src/main.xc rename to examples/i2s_slave_lut/src/main.xc diff --git a/examples/i2s_slave/src/xvf3800_qf60.xn b/examples/i2s_slave_lut/src/xvf3800_qf60.xn similarity index 100% rename from examples/i2s_slave/src/xvf3800_qf60.xn rename to examples/i2s_slave_lut/src/xvf3800_qf60.xn diff --git a/examples/simple/src/fractions.h b/examples/simple/src/fractions.h deleted file mode 100644 index 7c6c4a01..00000000 --- a/examples/simple/src/fractions.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -// Header file listing all fraction options for a max denominator of 80 -// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. -short frac_values_80[327] = { -0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 -0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 -0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 -0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 -0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 -0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 -0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 -0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 -0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 -0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 -0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 -0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 -0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 -0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 -0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 -0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 -0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 -0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 -0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 -0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 -0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 -0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 -0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 -0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 -0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 -0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 -0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 -0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 -0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 -0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 -0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 -0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 -0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 -0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 -0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 -0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 -0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 -0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 -0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 -0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 -0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 -0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 -0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 -0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 -0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 -0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 -0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 -0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 -0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 -0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 -0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 -0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 -0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 -0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 -0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 -0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 -0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 -0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 -0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 -0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 -0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 -0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 -0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 -0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 -0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 -0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 -0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 -0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 -0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 -0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 -0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 -0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 -0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 -0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 -0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 -0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 -0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 -0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 -0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 -0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 -0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 -0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 -0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 -0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 -0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 -0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 -0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 -0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 -0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 -0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 -0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 -0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 -0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 -0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 -0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 -0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 -0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 -0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 -0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 -0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 -0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 -0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 -0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 -0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 -0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 -0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 -0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 -0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 -0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 -0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 -0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 -0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 -0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 -0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 -0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 -0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 -0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 -0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 -0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 -0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 -0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 -0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 -0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 -0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 -0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 -0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 -0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 -0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 -0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 -0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 -0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 -0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 -0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 -0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 -0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 -0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 -0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 -0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 -0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 -0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 -0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 -0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 -0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 -0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 -0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 -0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 -0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 -0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 -0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 -0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 -0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 -0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 -0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 -0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 -0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 -0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 -0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 -0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 -0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 -0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 -0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 -0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 -0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 -0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 -0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 -0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 -0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 -0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 -0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 -0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 -0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 -0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 -0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 -0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 -0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 -0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 -0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 -0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 -0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 -0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 -0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 -0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 -0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 -0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 -0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 -0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 -0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 -0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 -0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 -0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 -0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 -0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 -0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 -0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 -0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 -0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 -0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 -0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 -0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 -0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 -0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 -0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 -0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 -0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 -0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 -0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 -0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 -0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 -0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 -0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 -0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 -0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 -0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 -0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 -0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 -0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 -0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 -0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 -0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 -0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 -0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 -0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 -0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 -0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 -0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 -0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 -0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 -0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 -0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 -0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 -0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 -0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 -0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 -0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 -0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 -0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 -0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 -0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 -0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 -0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 -0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 -0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 -0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 -0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 -0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 -0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 -0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 -0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 -0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 -0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 -0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 -0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 -0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 -0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 -0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 -0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 -0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 -0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 -0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 -0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 -0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 -0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 -0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 -0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 -0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 -0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 -0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 -0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 -0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 -0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 -0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 -0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 -0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 -0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 -0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 -0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 -0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 -0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 -0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 -0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 -0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 -0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 -0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 -0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 -0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 -0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 -0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 -0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 -0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 -0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 -0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 -0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 -0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 -0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 -0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 -0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 -0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 -0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 -0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 -0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 -0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 -0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 -0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 -0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 -0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 -0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 -0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 -0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 -0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 -0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 -0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 -0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 -0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 -0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 -0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 -0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 -0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 -0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 -0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 -0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 -0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 -0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 -0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 -0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 -0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 -0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 -0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 -}; diff --git a/examples/simple/simple.cmake b/examples/simple_lut/simple_lut.cmake similarity index 73% rename from examples/simple/simple.cmake rename to examples/simple_lut/simple_lut.cmake index d27fa3d8..7dae8196 100644 --- a/examples/simple/simple.cmake +++ b/examples/simple_lut/simple_lut.cmake @@ -41,10 +41,10 @@ set(APP_LINK_OPTIONS #********************** # Tile Targets #********************** -add_executable(simple) -target_sources(simple PUBLIC ${APP_SOURCES}) -target_include_directories(simple PUBLIC ${APP_INCLUDES}) -target_compile_definitions(simple PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(simple PRIVATE ${APP_COMPILER_FLAGS}) -target_link_options(simple PRIVATE ${APP_LINK_OPTIONS}) -target_link_libraries(simple PUBLIC lib_sw_pll) +add_executable(simple_lut) +target_sources(simple_lut PUBLIC ${APP_SOURCES}) +target_include_directories(simple_lut PUBLIC ${APP_INCLUDES}) +target_compile_definitions(simple_lut PRIVATE ${APP_COMPILE_DEFINITIONS}) +target_compile_options(simple_lut PRIVATE ${APP_COMPILER_FLAGS}) +target_link_options(simple_lut PRIVATE ${APP_LINK_OPTIONS}) +target_link_libraries(simple_lut PUBLIC lib_sw_pll) diff --git a/examples/simple/src/config.xscope b/examples/simple_lut/src/config.xscope similarity index 100% rename from examples/simple/src/config.xscope rename to examples/simple_lut/src/config.xscope diff --git a/examples/simple/src/main.xc b/examples/simple_lut/src/main.xc similarity index 100% rename from examples/simple/src/main.xc rename to examples/simple_lut/src/main.xc diff --git a/examples/simple/src/simple_sw_pll.c b/examples/simple_lut/src/simple_sw_pll.c similarity index 100% rename from examples/simple/src/simple_sw_pll.c rename to examples/simple_lut/src/simple_sw_pll.c diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 714a0179..fcfda5be 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -17,6 +17,13 @@ #include "sw_pll_pfd.h" #include "sw_pll_sdm.h" +/** + * \addtogroup sw_pll_api sw_pll_general + * + * The public API for using the Software PLL. + * @{ + */ + /** * sw_pll initialisation function. @@ -63,6 +70,16 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, const unsigned nominal_lut_idx, const unsigned ppm_range); +/**@}*/ // END: addtogroup sw_pll_general + + +/** + * \addtogroup sw_pll_api sw_pll_lut + * + * The public API for using the Software PLL. + * @{ + */ + /** * sw_pll control function. * @@ -139,6 +156,17 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl } } + +/**@}*/ // END: addtogroup sw_pll_lut + + +/** + * \addtogroup sw_pll_api sw_pll_sdm + * + * The public API for using the Software PLL. + * @{ + */ + /** * sw_pll_sdm initialisation function. * @@ -244,3 +272,6 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro * \param \c sdm_state Pointer to the struct to be initialised. */ void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state); + + +/**@}*/ // END: addtogroup sw_pll_sdm From 600f69213d1f472dcf59b4b1a336327f6fb895ca Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 17:44:11 +0000 Subject: [PATCH 084/118] Working doc build --- .gitignore | 1 + doc/Doxyfile | 2550 ------------------------- doc/images/PLL_block_diagram.png | Bin 27751 -> 0 bytes doc/images/modulated_fft_lut.png | Bin 33597 -> 0 bytes doc/images/modulated_fft_sdm.png | Bin 35419 -> 0 bytes doc/images/pll_step_response.png | Bin 59968 -> 0 bytes doc/images/resource_setup_example.png | Bin 64438 -> 0 bytes doc/images/sdm_dco_range.png | Bin 51636 -> 0 bytes doc/images/tracking_lut.png | Bin 42954 -> 0 bytes doc/images/tracking_sdm.png | Bin 38225 -> 0 bytes doc/index.rst | 2 +- doc/{ => rst}/sw_pll.rst | 45 +- doc/settings.json | 8 - doc/settings.yml | 47 + doc/substitutions.inc | 3 + doc/substitutions.rst-inc | 17 - lib_sw_pll/api/sw_pll.h | 6 +- 17 files changed, 81 insertions(+), 2598 deletions(-) delete mode 100644 doc/Doxyfile delete mode 100644 doc/images/PLL_block_diagram.png delete mode 100644 doc/images/modulated_fft_lut.png delete mode 100644 doc/images/modulated_fft_sdm.png delete mode 100644 doc/images/pll_step_response.png delete mode 100644 doc/images/resource_setup_example.png delete mode 100644 doc/images/sdm_dco_range.png delete mode 100644 doc/images/tracking_lut.png delete mode 100644 doc/images/tracking_sdm.png rename doc/{ => rst}/sw_pll.rst (98%) delete mode 100644 doc/settings.json create mode 100644 doc/settings.yml create mode 100644 doc/substitutions.inc delete mode 100644 doc/substitutions.rst-inc diff --git a/.gitignore b/.gitignore index c763e2f3..4c11f61f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ compile_commands.json __pycache__/ modules/ doc/doc/_build +doc/_out diff --git a/doc/Doxyfile b/doc/Doxyfile deleted file mode 100644 index 0f13efb7..00000000 --- a/doc/Doxyfile +++ /dev/null @@ -1,2550 +0,0 @@ -# Doxyfile 1.8.17 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = $(DOXYGEN_OUTPUT) - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = h=C c=C - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 5 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = YES - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = YES - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.h \ - *.md - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files -# were built. This is equivalent to specifying the "-p" option to a clang tool, -# such as clang-check. These options will then be passed to the parser. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -CLANG_DATABASE_PATH = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -# HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = No - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /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 diff --git a/doc/images/modulated_fft_lut.png b/doc/images/modulated_fft_lut.png deleted file mode 100644 index b839907d448057fd6957a2587e21727d83fe0da6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33597 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 ZcREnH!k=-@3%SCY7qu^Bows=MKLA&#ZZrS@ diff --git a/doc/images/pll_step_response.png b/doc/images/pll_step_response.png deleted file mode 100644 index f00a6bb1357db0140311018ff11c40179066016f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59968 zcmeFZc{rAB`!;&r2!#wqq%tIB7L_4mC`E-*hRRsV5Jj1%A|xUqB{V3KOd*x2GDL%v zA+|A9!Rar*H>HqwIwDV~@8SyOwIylH;7hO|Vidw-%{!g<@uE(i* zGk0oh?)OOS|LX3suVJ2P*!}vT(XQI#pQW^-Ol{eFEzis8Ffd6m-xluJ8@28nom`1H zkM_~!ON3L-F`Gqw&8=y>7+NRSTEESFm)(`Nc7cs2>hC&TIrd}LD@jFUGRbx5wUE=$ z`efJloQuy1281sBRKHU)eM-~*_w&7sy7uqatLW%0g%^HKGqVPS{rjkpEG7Eymm&ZE z2me33JZbwmIXOAPwDCV4RYp$E=#2{x{?5)_yNno>*9p9P_ilJ>Ov}YZw#{$aDN)5U z_fz+q8wz_(Wkf}{51oBgQ6bj1LtC47?gI8r^`nI^=S97?t*)*vOVx`X{_5U-%k{@Q z{4qdVILGZHYo*`~e6V2QgE7aO9<6?KsOYxOq30f)-V^=hcX!5g{Q9+oYRW?yQk}u*`eix^+0&trm3GkeOlJCjOv-bp?FI8 z;>C+sr)zI4diu>TegDOwlIf*ipPtcbcwpQ)I2gG-Vx?8rtFR~TLrHs_T8h0D$myr1 zr(b^kx_Z2?sj519?WwL;i>d4L0@~W&coiA0hlWbN_*^u#H%ebL&rci@CfofbYIReMo;-be+kL2wJh|(e9WK~6JP=Y)Sm8eSrTo46 z{H2>)-Mc0RS{H7iw|?E!-@o>Wdcx5u{_}I8;o@ zbb1llvTeoQ$sjXeVp3X^**gSF$;_Jl^@|sonb|9><8eB+u`B`AxATqXw(Uwu`t8!^bP!PlOsjhI3-#8hTwGiRg(na9%tfx=YK<#mUc6XLetHk0JA9H_#u;r|Z7cVs)L+1%@nJb4>~dn_ zDoO(}zDkm|$r8<{EqiKWuWdTP zg=nvP?&;e9Id6wU%89hxzxO=Hz7LO%ZrihGDWWGm#~({dQ33)2tgDqDf9iJoSx0+& zL!m0!f6jD-5ev54is~1{0}7d)o|IUxT;xBmM*UR%yz|7BD_1y{FW-N%sJdGBp?TrE z4L?MLTfcq(exbmrwJSoQt*kl6ra|=O>E-L}yrp+rSqU{hG(C?)chCG8tA77pCjX(n zsMfJ#_v8c_zm-mNzj*P2^<>ryta^@HuMwr8tINEwQPPc4UsM^7_vgKjd%9PIs~kB_ zNlB@u?8vQ@6l#8EjGgkFf8fWNvO8qyYF<2HCBdp^zg!H)e!Q>3RqsAKgN2U$lR0}? zeJ*rn#zzHT5p|&FG>tzy&|-!7s%f&$;caVc8=jn$QBcfr_w)1XiSr!ppm|LWW*&@+ zyKy69LRm^`_n$Ak%Ob?iwIj+q3)}t97Z$n)UpBoGAAc}m`ex!ccjwqdC6%>nwf>}@ z^wg@Nm9@9Gm+>H~q-?N$%I<@g4rH3$@|~VI-d>_gM^A5PU|{*U`(VZawv8J%noS}n zvhiSN?6E6X^qH~I(Rv5#4Lfc-O>|vp^XO(fI^3FNp^m`dK~C6?N}!^m zaaoEM^C&(CDzHr{&Ur3f%vC;&nW&r&%`c16a0!@ zeTGs{P;mV6WTB`A(CD*~Vo&E4eDCP%V~N&f(Bv2%?tFQ=_Wr$l*KZkXYuh?0vTTYK zM?MVak=k}+i<3dR(YpN)_AkDveC7pi(M|1wAanC`PkE{X@uOh8FD$gbrl&SO_PvGg zc!OMRlA7<+Prk@$`_$BZrd)zs*YSjNE?ahfq`QiqTDn>}sKj?#c z&3j1oW~6I>^G1<%>uw=+{rvi@FwA_nw(XHMYt~SfpUihrX^WgM_S}A~}pdK^6mXK!kr!%D{~?@Sgyc<|t*m78~E6`UDr zTdm;S_9~cVg{(uPB#s^2|N!&E${%{!8RWzdSibLhDLQ%n0^T-`9Kf0NCu<(bqQGFyhf% zjEdq|rR=7guv(!Ik3ksw1^c<;WeEF5rJ!EgoZu(Vrr!O!{ZbC zJiNWNBzXv^B{>5R!e#J_()ku;-@k6!Iy#ynT%CW%`+l|+KYRBwJ>u=_*NaipH4#Zi zJUSQc&pNXD(W6Ic=7rKnJbSoTsd_EPh3tD~XS&&8FTlWVk!xNbG;((#H*VOlq0Yo)rR?Ol)9Nb#N=Wc3RRt$rXPD%u6KNCiAP z{LJjhW@O1dWL>?+e=VYpN(&jTS?UjLs)s$heb=sC>C<_+xx>f>TB0#Dsxor5!=ro1 z;@lk^$C->9IgV?T#C6ThPLI6XI%N6bwocliqTH4_y?ALMB_-27$vRxwb^`+rY!Gai z7v34TM#s6iX=er|CYz43z@H!QvtQrhv_wWmM#XQ2ck$xIQv{&YuEpK6wiY<$xrF1A z6BQX+G+$L#TF=e=sc8a6rULfdR2D0In;_3b&&Wy%zV86WUD56{#V~OsKG;mtk52z{h4kph%HeOh|`l9lgUm7A@*mJ$UiErNMJGMRN zLkMc)Oot91?i?710NfN77hl2^rhO+;Teuw6N%mBi_T%Fp=#r9>hKGmC^4_b3r|-M} zfQ?;R3{{bZM^XbIwzIP{@PTp0ZHrPrGzA~c)By87eE7gCXUqKSQ#w78=Kge}MM6SC zXkWF`m+kw{<#j%^Xa0>`ljHhhPsG6AEhBd>tG##;%p$}U#<6PECA%a~My#cx-;Cmt zB}?iu%@z8;K3jncsZmCzGmxHN#>*S}BguOy03;r~?%usSu+6)=y6W!kT$b;u>ic)O zm6esE_k`rk%uHRX-U{2JM?1g0j)~fpYVz!?ROKbUI|qJX!Cw+sU}tCd*r91D=?(!A zNq4xMo4YeL{>Tfoq(VI5<6iCmmUq8$#*%<|Nz-${+>f2x*PwRnu(Y&H&>O#c4EwkG z&6`)xhDyQ_4Pu4uPfpRRdQZGU6D8~VUEgQovmIdW($z1pSZKoqQnW&r)(l{u@ouzc zm>B#T4y@PxDr}kk53ix($zec>JILP6d3NkT=}RfaQ(X*yfA^k$@nRe4tJ5q>OLE`g zhB}9aE}!bI6sx5#18S(tv)ilUIm(c6Ad}>v|M|l8#GNzEv>ZR@E<(Gr4V@tBz?lbQ z9p&_&9v$YVqOM-O%X*B3g{8BvFZ|1wL#x#NRvmuoW|*OX{)=4xHMyfpNMrl42#Gz& zH1C)Y>JGq3WB1JyOPHB8_UwsliKiA;%9almJup6g1ptfW*X1EcjVGxaS_lDJ^?9?y z!1sCy>BgDv11*~Yw_m({%Z62IOk2Uj!$_HhoZ-*&O@00P^#`xgyrQ0O-vrmMud09R z!rbpLW~qLIeY(Y`x#|1Mk}U!g`JUOyXVypSHXr2q?$SrKKRv?|ptAKr2KKkDt?lto z`+PineS9A8X*ABaH>NbKt%Z|RJkQm{O5C{Xn`&t52PFM|i9m)h&=>=SVqbWC>nhrG zi%RqR{~Z5McGlYvU#B(wc3(F;w|%MK?c%XCo)&rWA{|Al{tsn|gO32&bkMzXE?<84 z%cJTg_fu1aWMuZH|_5UDV!- zsZQ4(Kj4!;X8HQcI_4!yUZAsTIF>H0ccNY7>vPX#G#bsM@Z`-sRU5f=?tQ7Pt-U?q z!tlRYNQGl>cs)ElO?`syt#!S|I*i6`BOoJ~3p7W2?sq64vaxA({QT7J=_lOgnV}2N z6?t)qPoAPsnO>r}j7*K#0ESut6NW}a1n1@XTrG;(_xW(~nZ5&(ho3G-twL4Ldq*-T z`RT0`k}E?Gs)L)2j*boldhRZsY?CP5gI*QD>D1heC#!(kD)YjV4J%(QITwIjY*TTO z+rCvtnDhJlc?4>T;F;lCxbbM^XG;TOVq!i3ZHug5PeAmwt?nm_&fvmH(}U9N)?2@*bY_$V z*||1z1yUm6)SPxit_noc3C1N>>z45>vII7X09uWUM{DDRoLaX81qWMA4R@|QgJv~idUB}yJiA(* zm^`Ux1$)mNiMf4Sn5E!V4&v}-^j}j{ujLj|<2er~#VuQS&>VeB){z2Zs9=r9RS};cpxB9LlcvVP=TD8(A&CZ(OFPqMC1YMk-MkqA3XfFlxw*L>Y2RIOb`7kPQ(T?`@q#PH#HIxF8Jm??Np-*J1%Rg?}ggeE!pZ%KTf^~P8T!t-2`f_Y~dVWjU z99MOXH!XZ5WMuRu$2obRq$$THuri`{4oI9oI-W^CiS^YhiYx4I$?SBX=U70suAul5 z7xzMKmYwX#yIRFwMLuAWn{^b!aW?XtTm}! z{-N6EI=aY#|vpZ(+7|ZcY~aVg!4u6I3SG+}}PKhyr4Tc1Mpc2E?gU z+IjMkU{KK2Sv`#r=Ec$Rg>5H|awG}8O!v`m zJFQ-Ya&F5s%Vz+J6cQBFK#5*)AnS+}^Ets)YCa}Z%D7>vpKs48>~5K>XZao~Rw5o6 z3e-J@HPHQq)fIcY-@-x9g@=cC3=PS!^2up~oqybD6C4!8Oa;Aq_3HGrw&8(REzJ=% zz|5e%YJaqeJ_5E{oYZ$y(e>+hOo6xyLaHpUt0f`=nmPJN*;P*KneQHj8vt@aR$}E!f54RVY<=Y2ilP9YAu0#`EiT#lS&H$-3U)j*maH@2ES{=yw7H}(?+O6oVPyHD^ zO8REfPy?TDzq}>_e-v83o(T|?jzU9|f@5;qkD-iiL|44&L_GtZ_cl9^~*BSo!E6Bmo7IqW#3Jm+PtfP-JK*^ZEA61!qeq zS{#~lkFww?2cc?~H#Mz*tWZI?7vOtAY3Ze)C03p({qvpQ=TA?($K0PEA4>iG3A+N^ zQURgD1?)k-vRncy8m?EM-dVAo3AuwbO9TdM8yl2}P>@a(wQ*aJw)>qJ|+avGIfhT;(2oAfYl=iZ$=Ia~4_)>9lr?D#T=Q{O_1L`PZO ztjSJ6fww|qQ!~c5mi{?-b6Nn$%9Y{(MG}Q8mM>>UIB-)$HanPc=chV~K__~ongJx` zZSm5F8K`Y;Zja4w{u%qhLIoXrdwp@?$*)GO@d*iHM1{40EMoTAV%vCl^DLHvIZ=c!x@2aIe(YoKpSykwkX(X%p%7XDF zs0-kBv*%W7f!#R#te~X;lA<-TF5%EiW=8d%6cve0{+XV(8UNKl1)w1~P~;2hLRU{u z3!mcT<4gY{%@rmKava1=)rNss^skUqvG=oqsUg5^m@Inw)Ebx=g|!S%$PmxOaDH~O zeaDY?H)FF@RaF;MR4M>_Z$lwEtgdjG5{d$%U#h98X8fFK;jwnVyf)Pscu%^R@+n}le+Qn9`EIe${CrzTKyt7m$I-JOL8rz`}5L(k5403 zockmIkwUij$TARvzDK>^RdVih-?;MW%BB*JaP0H|Afigk?&@18x^l^$(zYDqCl-9@ta=~gr- zG%D-)=*8`xD^{!^8uk>FHSsUs0SwoI5kzG>R~pCnT2)1*uE5D0Show8ND_RDpOaQ? zGE_lI10eBqE1qU_LP8kY{X5ujJq2i#Sh9*IR+b>BiFl@DtA^ukJ=?FLp%KtnBHCIB zgyFX3tmN>YA3g2#`URSjjFp#i+$2J)KVWl!C9(dTd$cB?@GcNl&loledO~)Pxx6ZM1fi1BsH`}Kjb?06_a9!1V3jph3`&kG*J!8Acdpe)0z{Zg+6<1xKd)RK-cuWV znod$!u28^#h7F*uckg+J}}srdE*EzCFew+dPM;Qi(4RWk2W6E z|CzeWe0!^jS%G7i^x>x@uPU7UqJhijT)mnN3V=ep&qv!I>uRl_@p^?_Vu+KZJ+g=| zU8B7Gnk-GxEzKjsR43s1$-^TB!)Qf#&E9`G+L*O>+pddD%4dHUCQK@OZy6qYKbm_} znZ~amH?=NziJID|P{oC*8CT2BZL@v8zd6CnT)4R`s<4ReY=1LN!%yZHWTH1eEx9dDCof9& z0!fVoM$VUl1Z1GTl=zzW&D9t5wfbKy*zDA@9NL+3{#y>H28$>pjD1;N#7_VKpN;qf zxyV6!5#pC%WLtUdc7m-H+(^344KBvdM&UH9Q&MEJZibT9bb-#->qe91}*IQ z9K5r{$0AZ}b}*J|AJ7t!oAGN2TNaqAH-3Y;wnFE~duGkMx8&wnrt;YwW_#}^Zz>3@ zumG!vo>+Tw*Hwrz(rN2UK52JQ<6#Yor*IMX5?^oQC7#)2p zKe7m^i^cW(n{p6{Zc78u0(k-+b=*((dqD)`g1zFg)(B#=zTQ@U5N08lFSB=bcN3}r zaHelvKEvB2l*wFSvcu}*qwssk6Q>7ATYLZUz92c&?@hd+r zJiMnx^Xa~uj+u`hEERW;w1pxy8{+Vxlr;#a{U>QJqN9UHHwL4O`{=8l^9XFm#}GL6Q0ub0Xd74a0M6)9U@A1*Fa}T&nolTj!5y5NSH;66xMf>oINFxp-QrSy?}~&R z&)>Hjab+yiJ}V-=#K5#MRbNPGbd+byiFy%mJDoS2OEe*8%AWqF>jo7IwCpvAXpPGF z7gATyt#Yne6A40`I5i+8D%dwY%1nH&YX!c&#=q(k-+6@LGWla~3 z-L&q?>|`asa|*y#>VyFG=GsPj^dQP)j{&*g-gkcwuhMA|h;tY4os^SbpVq9qByJ7O z4a+A>q$I*M6Q&jwa2SN(9dryesRg^d`Z5czE^~`mqiUvfF`%CvhQNA$`LYG4!`7Zo zTK{MIO9B>uWo%ch_%o+IN9{_`c%4X`CW)%vJTfIy!oa^d4o%W3;G%&^#W&NlQn(l$ zA0HI}&U4@fEm7$qs){4!rE@CozP@R(0Fn@??f9DZjvY22OlxL=5*1H>VXG>FhH31iUPlea@O)#TwUlFZ}dTmMH&Hn8Zg zvLL0qJ62u&6rF+TjW(g?E<3M^C6@Iff5zr!1Dj213w9hVXl^RF=4BqQzfHmDovYWX>BN@B80{R`xTKSpTVzaF5I<4NvS?89V@7fq<8eb zMiUhlHk!okTcY5gC$1zO1+2MTh)j%cN2&y8Zv>xZ>z$R*7Fl{n3B|?4>BmX- zb<8gY6N=u_+)$4G4R;4vjp~JO7hiotxiUX?GGGf>MBA_-`;a*}mr%)Ia+F}L6sH(_ ziq(a86Aq#zij;FV{)$#g0KjwRyo-ds>@RF_{On z?3kDt&BnbVMgojCZ#WbcfH~tdT_SUs{81}KY36f*ixe@Mo_7291wPBA{smIx&=1?m zo`x?72pXamq=--TyM=Ql7Cr}vFWnH94YnmZ6@2vPGnOlOd6}bs-DUBbf(<|xtTqWH z0<$5{p}I_WtaHMB(kdKi%-3*mokE1Km|cc zqmY-Nab3NP_VQ>E>XS9hipLusaQpcB5-AM{(HiO$q@+)ZCl4J;9OV@g6Z1fNG`zoO zH3}xl;D&MGmoM*t58`=oF%fH!3%bDohK7X&qQrr>-V`YnyNk#L#5V#}RSq^AqN)+k zrBST<#fw{jL-?srZRSv~ie`nm!dy@kDGdXIs9cO58e!BV4=6(j8kKKxqi=tJtQY}-MT!%lMv-fBK&TppJb)cGEkEOK zZrc#BF=>uSej)PqzWYmwe2&Pw|4A(@EUX-qs4NI~m)&zoGy(bto)3(@hb{{?tTtcI7*<|VAOKHZwzpdh%r&A+h$Zg@a{*AY4cWU6_Bg0A z#Q8;h1%!}DRPp2j#I0BK18>_>=*$iZ0blC`gc+V1YAq$kC7IG0Ia@hCk^#%gG)RMk z(AKgQTYWV?K7>Zk*ny%+%&zbZbilic^3aLatqNr_2V9V%k`h=i3Y(-7taDUM%z1P? z5|{UZAvryL6t@rGlzPlXnr#M&*;nwWjo3Fonb=jdlkz67Zl!~{a2PY-H3;>~7W z;Ha6pE4J>@;jLKSE^L{pZ!vqb?cT2;&GIxoMT{YkpYGnhONfx4pYzrawwGR3X!D?l zTy-+Rsj&&w|NLZ|H#=2V;_FSEXvD*a%ti_wv9El#OKwPW2fOlodhi)I0wJPVf@9%_ zaG`m5jjGG{H++zkw`Kb5-@klrXnu|z>H2tUffQj}e*bnOWDOCM(06vgh==>_u9Ybz z=Bk$AsuFqQ+BM$Hp6~B&hNEdI ztE$>jlnGZKk@nGy6VFi>o>603A=+Zlj|?{yoG-vY0vVEy@)-TL82u(VanvtDM~2!8>C_#R`i7Vo67y4O$1jJHw(`P;?$37!1K|6K)UWsli~( zuGHp2=j73GbLZktkLAfe_Q1>dPu+NYKCcB`G#`9gG5)4q@iwHyfghIA00|vZxp5s=m;u;n zS^yCQdnP`8+FxE#p#wk9qbN8s4Q1eQfi%F4#Fbib_>`np(0_5#Mw7(|9_$@Jh>{RFv1kV`d*V-eOi_|Chw!$hJ!`Nb6(y`XXeZ4^CqfnDHH z&il`wO&#K7(Er2R0rAk7f6GJMc>qGC8@sDozI*5;?!(@3tN)w|FgD3pQx1Ho-Kd^O zlfrPYes;(706hh7-$AL5k4Up~=W$_L8Z-Emp!@gNBPZR-e}fodrwEZ$S%RnM;Fw{S zFAlRvRAS=8FQT!#Vkg)2DLZr!u!f{+zPe%7Z)v}6ZDZHyF ziC0zh`|eR02aNj_wYAL%2TV+AB3^#4&9aWDX0~z%8X@bSc@%&3Duc!v)>zi4U0yR& zIgMql%WLfWP`qmopH~FE1i%U!o1LOyL}@a6^gQgDTWnHN*CNSNkeI?u?@v4&93Xr28id^kBIkz3U-yP+&z@J3l zJxZ5GN4e1QN()|X;IlODYobr~tMNV;CSF#>R9&4mb?RgrgYwx=&zX3sSJoY$#Jbz) z9e0rw0B0C6Tf@Lc{2w@oU=1=EzC)Y$!+uV23&AJ`x+PA8b0@&K&H(KzGBZ&y7o z-l1d8zu7(r$t%vZ6b@w36Y=(2A3J3LR1{5qv}aMgnW(Q|*-%kIwn@2NvhM}-j9s9y zUV%EMQJ{h3KYPB;UC8@)Ln5gt_EKxg(fraHN>4DK||;q(dG*|IaPY9V&6ILwPD6Lf9J z7U0@xx_zhW)!d~boCy}TA}#~h|0YADt8CfMu_U>Ifds8IiCFs!K!mlOf&c9>LcG~F0$Qvb-0Hg4oQ#O_# zge_}%{**@Wn?KdL4)YTuxLQJu%SqW3EKsncJ*W25zegfw0ZF6qGZ9%eBsp1tToh-s zlu0%PSh(^Gh=_=dHkE>6VvC7y&EEdO_5=`a2iK-OGS1+kI*{xb^BXw+XJVxbjPA;# z1jJxfj{JEFphQRw;sSzeSYu$GaPd=rekA87>E+DS!xbM6u2!tjqQu9vuYNxz3+od> zVK7!G8OA<9C|VFeh^xT`KQp`{jf+kU(An z*=%(CX+V6f!qL?!(+sGXIi6$3F0E4Hf&zjP91L{_qHZY6M42y5M7S1%&jXbEV21pM}8K z$vD;SANcf4U|}2G=vaiSzHqKL@hy|(#=2O+PYJDWJ18RRkH~If{Uovs#6bvyov0Mo z)cw`ST+Of&D71^bFmghztAQi7}S)#V^d)mVbge|4>3bLfPSw6wh6l0 zOXOy3ssOZVR@gxHHq-2@6a!%Pr$z0Zo`Z2;Q#e{sNQlD0!<~MhykKCLiJOQJ(>T9EY!t zVp?;7p}u;R;6*}3k_j84TwzCaA~MN{q1o#7>(>+MNAb+BtE2pRWSfG;)`0>_JpSkk zB3$1lDlNmR>3|F%bBEnXvM-UgI99A+0=RuXJJD)p_W>d_@7!eRd?y-W(hX=1#Z+_M zT~pz|9QN&kYa;Xycjau6g>@W&vZT`YIl4x;+4zWWme9AWmEE!p`l_#xbOQ6|C2+Pw z9r2Erjit`p{(Qx_4%e`+ZvqICRArJZ5k%)mvMiEOQ90CC*oGu zr%MN**_!*XM3yl z74HDOu9h-w+tkQ4suxM;4da+5OdDk1Vebl~hQZ1VBXZ{JxSv9OW3b!lC#$W-xM(v5 zR$(--jq}cbJ^E0b-f`*0;5Py)9x1Lj(I|+WH*fHQ_fPBONokNEbwG6yUhp|nDQ8R+ zqBD&SHzP9brmI9}6(#0nvBw%p`uxc!5Itf!xQVbw~ zbFb(XzN2<_gl9(@(89FSFgm{zhrPo4FB=~Xc&oWl$_MeLa&sl4oH>tysZd6N!oTcv?qI-LtcJT~T>tovA2{5#6>#!oth!zdXsQ6fxNU6tv3u#zO_%6LWKEZcA5CJ9Knt6tuFtt~b#h zZ4-|ri2`w8bn>8W@W=Z4sQ7rM%UsJ!(;kVrW4IPv#BJf->vNab)3RB4bC{RI04j`0*}4sPMRp6JaHDjh1p#L;Jizr0b{C|?ugrfDJN9nfM;Km1 z?o7Humj@|@k?NY88@IxXgHZo|g7-v^>?DPjVmUf*%*VWVdZy0+%-88YGMf9d?iMB< ztFDUetHS_a{wzuxpDy9YFz{7{W+WPE0_NPcaAm%GH#)>ybA9Q}>NwrhikGt$*0e|K z&K=$vD}L-GXK&5h-Kxz}8#YW&8ddtcivFyv^CH%}d*3wiYk=KOf z9eb^=A2P4^0Xbk^uv{H;>uOIo=}Wg6$oBA{>Tf`=o$C(rPQq~syuwhRD$k2$YZR96*vd`gO3l|LNieBn^B3!6K zmwx1hk8pAwVe zw``(8-IOC09vDnNNrew|u~cE?e({P>&b3I;AxL>0kZl4pG8~G=&z(OHf$w3#09YKj zTR|~qjG9{ro8RZ8?%(5aPBVPjKOi*g;Xz$AFBluGk!GPnFVbNIJoK_msiSV_a}`c8 zPjByTaC*A4L++{Z@$sX6J1a_Um0`tpdF98$K!bggOuKc@AJCWW3EeBV=2=9I-2W2` z^YLph;JBnH>_mkrUhT&1POB~L(wNHL%$Ta~-K$D($(^^<*X4R*lwSwK#IPNXU>Jf5=r6V6wa#h2vfK6F%mFg78JJSGqSnJ~_PNAm<4GDA=b`!mgq zm2OMIo2b;cC4mPP_Eq`G+>^777~??akodcCyNNO5e>fNfcx{QHt&+vvnGzX_csUPEd%zMdu_!Y z@L4jJ6;!2ACgtq{*EX|+hS5%biE{31Po5vr?%@cpAQ52yDUGJd@lTpSRg(H5H+|pa z$^~C1v&iQ6!qK6V(=8h?kb|u50(Br_;6ZDBeSMAUo=X4PTnx!$#Hr}h=?65I3pGa4 z#E_PcA^IRQ>I(*ufDl%~$x)T0>fQ0}TM!_XB_QiH#Z%VllkDyAVRRQCv$M-^l7JT~ zf3ftw*WfP;}y4!vz}NRy}3iOuHfy*ys4r#KFi>MfUX~6V%+xZ zkiepqMjTcO*Xt$3SiPXQ`UZE2&WQVpkdx?vZ z|M$0x|Nch3du!DGnri;VH?YQX5dQ#~z`)Y+?+-Emb>xH-6*9b3E5jYuKt{!7L}>ghvT&{Atg?_RZ5~tCpvmE6 zn*UD5*HI-NBY-u#VR_X%fcEdkMHe(k80Pu@U1#8eCL6uhh8T$q`RSyV^ORarj?47r z7_R?28YelL-8rh-wj%5Bu!VB-OZv3^ujL|yi?7*oN&Y+PcCwr|(~tSnVyWhK0gkXY z?SjM~fE)W(O|Jjl{-OCrl-b_kr9;wCBS2V_5?lfmL*wZzy@HfbRiK)*$dzCLq+0gEQChoUs@bSqyow z3&|hoECVKKkbR@y5`s_JhH{+V#aOoe8T)@$h_-M~>q0cbN7~u$vIaO^Fp(4}epm)$ zFm?df!hsrCv*kq$qgET9m4Zo7$mAX%@|NB7V7Dv|9WeJEpJ1e1(&KAh{t+%rwBe29*g4Z}YJ>lD$ zj{AE96BC!LxN+}9S7 zyOJOi0yS}0=-`Z}L|hC{Hda#^W~E8xQQZ630wmnnv%^_nA|7#QVe~EUe7> zAVD2S-_e-*g648$jjZ2upWY0;_=|<@PZzyOdb{gmHqqdu7%G=tOhr_!pk-QRdBpo+;{{1O#2lR<<2W6 zBqon8+z1_AKqxbX&#YbR$)3h7uSgv*<B3CRd2!GLq6)++o}F8;bh+4%j%T7ySzfwZ>wR|vZqUZrNi{B zNi@WE$mwd|g*U=&S$9=U(ogM(Cct$#MX{darv^o zM%vr!ruQ^$InLAnj~`iZtBM*YOzujcopsIAx2d*dWGWNav+fQ4&*{j_hw{hkv}2T` zE9=q#Co8Q@GCtlOHVCB=_Ofj-n(|LP9H=wx> z(Xp%m$w#}XobJ!_cid)Jk8HXSlFRki()a&b5~>~Nz`>_IzQ(dF;J->{b0NE~F10;U zvaQfPt9$@uM_7(~Au1z~71eI+A1&RT6_#C7U+6qC_Vz~LyPJV+#ewhD=#BP8xN(tN zeMu7I%@eGF-)Bp=>=ZF#Kgaft%BZQOKRYDtci4nEQDt#|JNug?wojQX&&PS9lAVKM z@US@fF8t@p-cZfQjE{@{yfQlaZF0tM-naMYg8Jhb*SvvKfjrl_hvJkPM&t?W0|MkW zU1SS@&7 zL5eMMcXub;4+iX{M-zmEBr2$8zqB@{L!BOOqqc%-M+$RCM#`>=ajOWCE4E6M`brst z@*po?k?=``A7>Ssx*%BMLBSH9@bTkKWHuIr5Rv7x-G`2NJUB$Ftg5<$_gPT^=tv=@ zvHu%P#@m2o;PX=_c2;6Y+S&w;n+)HBiF<+f0>F2sec%8O2=`F9Nyr+RhJ= z)WQv%3|J^Kcmd|uli14eas@H+S;#~$BFYz;^1+8B@PMyl$yfSBlFWV{w43$n__((x z<~Vs!3DM}?VKlp>48F*Q>V(&mkJs0F61-?wipOO(LEfZ7Qg?UlrXu- zdzQwuY^BF1zpkW)(Ma6vBcd4Jmj{iYrB|MIb?t{+0Fd!Y0mHSm z1DNB+8%WlmTU0#Tf9;)DI3!x#*JdTRJEEcHha>OF^)L)Urf_tK3lJ z(s}Omi-+s<;?f2jKBaNujd4T)z?dQp&S!(xtS9QcRIc%bX)?o4eL8mQKu=Fks^NV} zh-ghuXE8uUCLNGV+>|5JH^}wGodlhF#zNii%TkS_)F`Wq{Q;gix3c+z-c$N^M>o?8 z3Vay-kzDBV)8L9`)hVZXhQoGZny?5}-RaY6IJNS`FL3EFHbVf$GQ+@0{P1?$=Lt)pwC+!)AphLH@!}557!W!KK7ww{iqx46{@*YzHNN0P=%{aTa)SmV zQr8P3C#z>6shJf@$h`#Lt-QKw|MGT5v!|;g%Rc#LdevsJh%l#F2+)>J&D*mlpJ*uh zGc!vM+wt+Ka%FeMzvJiA68Ua%m*?nmJRWqvvdeJm7M}cMwM7&CG!+&7rY28MjZ(j( z@2oR3RVUu8QEMtV_ivipQoS$ovqf#*^kZAlk%V2jbcvNrG2?X#M~cp%yZ_$~a<7m+ zM8>^kPkvE+lcdT|WFzQB#G?M6LGBvmi#-W=>(c%4YZnKIJU=mzIQHX1b@JK6R=_&NFvVPch++D)NFY&)J(aO!#ewlnQqJ64?P@b$S}3 zccqfIKakf}z{CI=UB;!s>ZdvC47iqtg4If=889^lQaw1&_0&OVcqS-k1ExdyOQwGB6I3=UdFSkU2k%Rn=eY=sEZP07Zor zY5S_OCpbvI@r>>N*-ZKWK*$_yRl}Pbeg5{|C$#}`UP=Lvi@m)#xbYkv1C!PQS$>~~Xp{a1xC{A}TN0;4GsU5z)uc6j{V#jM2S=qjpq$UfdlKC}hhfI?+Bf~zZM0Op@DwX!1otG+U-6Uxc zi87>FNfHgBf#xDKN0gLkRtjmjGZj*rO%Y9+q`^2&ky%~-Pg7EzV<%P<2;Vzte*g0;PW_zxf5uIAlRS^gtv{Npj1&` zZ?9b?omTAG!?7=5(g6||oqm;v;@iP;0d&3<3^pQ+L=7;xU4_V&0|LrDE!VGnCGrZe z$bZ3p%u`{gK)8BgLh4~44t_>W-MHywU;MF^5qfgq>=%2S63&(Xis$t>FPu!U z$dVsAf#hjhWja$Q0yL;E4GxrjE8MU=cP@@pibA~u2T0s*BL&L*9gRNYU@w3}>K#)j zU{*U&DT&hz3Z~4cXOUtyn3z=%8X7(f1QYQYG44^Doj3qhtq>|-a}YNKlaLBUc&8_K z&S$cM_8Xp=+0_^LhpW9;sE-`UhP>@FWiV>}(u7a+zr=E35$-*k7>mu*72I)Pv z)T-;>FB3UGL__TM9PZAQzS1^rhLBTtTp3DSH@jx`@}aUhu;r>@JI``EdTdoquT<<7 zY1gY_UZ37g^d9(DK0YFSbKL~T?u4q^WAtMm*F7)EyqXo5(wsf#qu;_+()&1;h(t`^ zF3l2)uS;Op-umOkq)5$5rkyM~2Kr-Wrw5ZmlV7=K%G>GXuzj9?{z{kAyv4OcgDD(& zV;#bUXa4$~Sk;Y{h~`Z>Tn?!@4oldEzd z?eF#El@Bay>SPmK+uqc%~96_3?7NYsoWgC<1c{ zUZDT{g%BopyS{#`8?7Z$jD^2qc)ZYZ6LQT^2G=~P+cOc7VM?WjVG-R$e`Vwan=|Kj zrwd?j)j436rx6vezn!(|p7wCq^7(zewau?Mxng!Kth=h^8um@Fl7eW%X~42`^wmbr zYqgT+_znzr_&$@Bt!khHFKLlGNPj*tGJ*}96z0R#PIZ)c4VU2a>TXjYS7LRL|Y zUcw$F2mrdqPF7DVHAj3a9QNpZEzDe(8N2fE6JC~-c=Ln2&HF<=<6AtIJr(}Bv%D~l zXt-}X#CB?G9au&saMgd-4PC_@pt+TP_bx}^capKwFuVu&tsGsZkDu_>zcrF!e)XOz z_p|8!Vb$Xl+<&H3gxowf1ui($@a#)pu8;N5PTdnHkQ0rZ4K@ro%|zALYm9x<@XT$w zuJi4nEpMzX8)duMeE%YenqXC3o!ZkjVdHYrv)upqt9Vy?)7OM+COkiGY)rqhWae9@ z&Dsi!E14_SS0*X(@JueY{x2Et^FMow=NBL2DL!E`t9|9-#ZP^_=jnn9^cAY~tLuY0 zZJ&gF?h@ItKYaO~obhN$Wy_EC!yYHfwC{Lkn1)0x+?f$OU3bp7Uff)OX^mI5*q=Wo zCoN|GRCmU~)Q&w^BkG5iuJfyrXl;>*=IaV|8(c*acg3?9o)J;r0tle=$FI0Qp>gA0 z3qx5z5d3Cd)3b2 zaW<8sm**s=S6da&pZLD%#Q1vA#Ocoy6O@%zPu#q@P`7HT-rcs{EO6-K$B|#@6Ek<~ zCNU*kOX)%ZJJpSz&%W`w_j;%DP(R0X^WI!RT4kn1ojUQj0dF-p^|@2l4} zO}_AM{}W<%k1dHvCL)%97PGY9EG&GkaUPd~)B2hXK>c!U>>OhVS_=^50KR*i^XkS% zND#v_Z#uDA3Q%RGyHnO_*_+z+btPDsKLz;0&{D|Xq*E(JwpOS_MCgRyD1q0po&ZZ zzzhYst7hUyGEcD2k%*zSCHxXr3YYYWKBR?-M{ z+htTK2cdm!D&IJ7Ljq~!iNAmHj_hIa&x&|_5uQ7bAA7ZW#K01lo#Wyia*~^ry zEWZmEuRxRzo8@ES*W}s%*Vtj&J)w-m|Ixv)am;#FG(wN~nQptG%nL(&I#}O?q6_Md z9qN3bfvq0%gE0;9)V-$U&JAA)s23B#aVPSGAH$zCv%Eo(02!E2%83vO1R>&S5-{1b z_+dx(noiZq!X0DP&swVH%5^$Mg=9vVoX?;grjZ%9zjdQ-t)1HcQwWj8f#@yaX-Tbw zL>5u;X)Td0h_(sY5^`)F;F_AisovESQaCBhgU&=4YDg+6ihz;CnV#Tk@Iz$S1BPvr zctXVIpe-cAGvN#9mhjT{fW09n3^GybuR_i%-Okf=hD>ZBw_H?(mXEd=(~XsyyxnPk zSLi-CG=X(je`UQRH?a%@Qd2B&Jp4AS{h$%ogt(63b6e`Yn!u89!%F~@4gl5?ei_RI z^J-X{5(yL`2|oSHAp{imn6Nk0{C}?$7iR^ACD`{H)8YZU7b15D*O#ykeNcPGH;7~g zPJqN9y78MISfdkw7zl(`7$LR**Bu!rXh&CRojog!K52s>QtX=i3e+K{@|CASrV9PHix(2(7xf zn3*CX@Io-emZ{H9h^id@l2G`ex_S<%aLg1O!H93D(^UAfjdwPumeuCUK@3NRi6YMS zPTs^^n5u-{{0+*@J>wt)NUyH|DAzc{)kXof^>-4mwSZe{t`d$9=q)YHC>L&IJ#5rw z*cbJwK>V>4%KCz)264l^P?GnIf^SH8(-izs(swC>i%1dd8REAu?K;kqJuhuE7K(%(C6kAqTu`?g|bIUMUgxX)* zTynOA|Btm*WZLhm8{qY(E8~>mH{0nZpZmT~;>j!Go%ZtdOlBDzM31M1g^J~QUD;3% zO3Z&cEpjugAdP^K5XLloK!|n*+d;!Byobw)yFKZ!fR~?aQ=9!J=7T#)+KOOVTmb(! zy>S;yhmGG4R<%FVUO?%w1I!^bb)v#i-c(l+=OJApm&g@$*Pb3fIzkBqH@7TEGxdMF zdR^QWk=#uvCWy>lz?Q#Bd;!ZNC&yCwOzXm|MY{DVVco)Kgm@zY2LAy1Ah5>#TC#L&gupB~iCk*u)LmkU;H65p$LR9CMi-vDpn}hk?B4)b!7|x-O z0-E9(wAV5rrg#)K@T;PtqRgOPH%Lvq1j(&3h?y8q67CkjoVSA0_{8EPM~)E07J%s8 zoIsG&z#FfE;p*jEx3UYjy(Q%o^t2#?elXQeq1?d6xr7e#JE5IxeAT?}8UcWgKCK4U zi=w8qS>WJ++ph|hkY!=5s+9{E(c$>>d)apX$Ci))ebEVRqDQ5b` zIgUZti0X|PThVOGtL-V`Fi=fEJ*)V{s01WcIVh@l_JijGO7B2x!8`~cL3Up=DuI43 zQ@}|JTCS6Kh3qV_nYfMpSf$`3T#2Y|&D(=UB<9_?Fn{}-7u2YPt;osG-KKtJyv-!C zIO)(p8wL~g5;jOD`(0jMUh9B55v43SIEcz2LeI2z*Mt0KK&^KR@3yJSrlBMuE2nMYmU4k14aQ_Hqj6XJDH5Umy$u zsV;~mh`gDcoc>8dqz^B*&t6>rdqm5WwKwMnw zAFg|5ta?zegh{R{S7A#hGBq_R)OczP=m9FvIeiCTkfQYcrGP>A$PN^8>_--_e|sm3`* zW`o=S`D2g~%>%+iB4Z`yzNoAd=M*?Q%l-8<_ylqBQwRqjGq?XX9=T?8gh@-}<(l5p zs|l{rrpA7Cr`IQ0&$&{=ef}MjEhAx{vLBXtt>E;olI*;YUl7@R@Je<*~^Q_RfFKS1cGQAE{&RzK{h z{60qI8t1QyE$ZRVoL09mV>K-xd2C;|^|UqHI(HUqUS&xZ7+`}O|0y4$=7QMfDN5(W zNEIT7rO@R(gNz?o%&jiS&q!Yqc1X7HpZBJ&l6#&~?G+kwNmt!0^Bz+#^0`5Za)UQ4Ox2JD!FKaJjEsocU{BNNj~_v2I#x!3 z&!wop+fsz`Ig(@R98$wa3cL3LVJ2)Zc~p?;1KeZ4vHnDY3CI?V9<8M^A>}j_jZkT% zuGFn;;lwK*AI~z@lL@3VB;NrsZOA~V5rfoyt#o-eyRk`-bMplo_jdNmLiIUliiAc88Q%oBs zM+_6(_O2AZeVm{*11%$i?hjeJ`}W+6FqJsG|HrF>yyDPPTK0MQjAPZ_5ieF8y!%jU zf{G|^w*(?l(8Mis?9RRs)b&}pmM6=xUTg6E`?qUT{*^ATT>I+5plG}vhuQi{>!r~( zD~R)yh$o8*YbEi3g9H!~uUE!(WE1!P_U;i9flE!wxy(_LPky}>|H3Cu>q-XC+xT-QA|sd27zDXW$ll$;)`;GaGWxeQv}~og{OM-1u6~NAsxLFdWQfGv%+)Ih%Du4pW*xj@7Oz5o8`T4Vn+eT<{aaNla@3NP?lQ>pEkA!_; zO*-yfzET#W*&>=#tVObUY*E7TOr2ohb|HN0i7i8a12AlNebR}DU-kLs5BaJpfdT>3 zBgXna&gSH;kJG@67)e{O{5vBQHxExW+CvS)&>(hqFY)&xZAsu}ny3)wa^rUYw5Pvq zhNzTG!1pd=-;E-K)+41*K=krB?!Q4($0EMiq+iL1vI?!!-I%aDP;0hexq7vU?wC@C+bUIXm9tJfhR{nVq(yjLz5~MQc`gC9$^yUy?<2dw9RS> zv*(l-j!u6DNs}B5!`Rru5cz6IL=mS3L<_^c#tKPm|JouY-erdY)$yNtv(vAIZENNm z7F{+Fj#xD3Az7D_WXKrjp890>Lion+hh-ud*dj1%PHWR1uYW%GOXIxvrDn6f9cpZq z=G)R$SX$%N|6shwc9ikeGls=SKh?AzmUHKU0uUE<&Vc8~eSgUYl<#MLU6SY5=6H*i zmWGYy@MXS5f6_gCJ`YhMC!hX3;PEm*_ISi@wfwyYn=v9|&5}EqrhYN!IWl|qGySuv zTuKrnH!Qot_f@>EmZF9z^%v>8j6)GtTt{GaVp4Q=nKpx|djEviKMXkdADlRmyGjbL zt4}xsOC_5JxJr+pu8{-$P(QqFk^b;=i%K^7Nv#^-y-x8aEo- zp~Bd49=`o*-~%=~Vc)UPKqYvLT+Luj9d)IaZ+TXrIaH5-3nX_u_05=U9gmT3hX82> zH1XvIO+uc7FrD}oS5xm#iEaIn%|Y4gv06NvX@ab-s`*Y{N+r$C>LyX{H zQ<4|k1aOQ)pcj!G8YO$;i9>i*o`FErh-v|c-_@ks_L9%SSgn6@CRh_Ltcbxr=1nJ2 zj}X?fH~x>!Yl3JUpyP-DbAwplsG}Dn%a)z1M?ph-3=emR@Rjs6pBpjcH@_R_aw}>Z z>gfs!*rGInAtXfYjZE>+q)F)?uIohM2k0fMAQbx@;b$THeRP*gY!lxi*6Gpk@9VeT z+Q#aX;Xpn-yxvmJbEr=dywcH#?uDvwhgcCWw9MVCe!r#Q=T_O0 zSK3%t&&ItXTHT-(byFpt<1^lq{X-Sa59lV*>&z85uI0J1Ss>*6x|tFgtws^7gatv=U)1hNvNoxmvM*tu4A1)jvLXn^iT*4!jn4s}N8q%; z(C!nuA#fYg5E0UUqBAFk3&%d5m?JALVHc?l6>(cb6|d_suX0a#G$(6vAcxnc;E8tuUr;J zsNj9myL3Ada!HHaP}yd&b2n|;at7_AR|x2ceVM-(ff}@H71eM?nNQlNK-jevW-CoI zi(QD*4lHz@kk&L1%`JI%1yS4qkemeD#<=L}zrTpWG<-f3+DPh28;#4cvF9M9N=!(g z5a~Pt`hiARX_;QwB7ef_lbZiOw%$ojO-;m^3EBi36B82_=NzgM%$5kqff0I$Kv|8^ zwPXj}8P_Ros4{9KB+9M6kKy3o`jF^orQ}bH_hoQ!P|G0NZSgA^E@>aJ$o4)&3 zMtyD5Vp$fo-!(7R7{4i3g<7>WP}tFd_G^e)N@}FaW#7G38npJF^5l&B@^Qx;N{T$43I!wZI|hWOoL{QqJ}zB{d%(K^QZAxBd3;fmPTo@=6h3=7}j z;OHFvdH8Gd8d+I~@V5;y-xT!BV?Q;!@|VUEGxlJKFP$(oIaO9xMjV%kqZ=>UyRb&+ ziJ`v%N4StWKOD7K5wP9{Y)|&-qZ^aVfq3&HIT8i>Vu&<{ANpUG<=5}_qASxv0>ytK zXms13$v&am0I^}NjSo9bAxB?D^x2OI?pEk{@F0i(@1MLBJG5v`%FS3XaOq~_Ubd>c zrw6R2o}RgA7>s{*fAHhn?sym?(CEbI3+jH8lD*O~9I#e{?+{$xnx`-?*p6?bWxyge zBkkq?m@$pVKlFtUQ*%bo3R&4xatA}FKXutnj$5{T=D4=n##eZcf1qB-%I@B4W25$5 z|BkVwMDLjq-8O-oQnd!N2kiLL;(n0VFlsd1XBZ%tpefdLZ}b5M@a zb}C6!@Ib^F4Hl8PsvgvwHzt$5GIwYiS)qllrSBNt|ADUUCbNP3j~6MuX1{b>Ub6D{ z*vRu2$A!2ewwpjjLSM*o-qeA%K_vGKe&)-enEsTUk-;9lp38V5ptq2k0sJqGL4U^&yn zfEnKc6Yl5P%XG| zka86M3~L2mif9eHVFd_?8m@+CcE=NeKYVrO6uN!505pX-`=b;@8Uq@en2>ga2+~sR z+}}NRYp0%FS{=yEL^GPdq40u1G_P~?o5JmJzJ-?0-uA7v(qVH9eIj`DNlushj?Z0m ztGo?P3nB)0z63&>xih8Bm{-gGX!2fNsXGNDg)vDFL%sJ7T^YR=(kvhL^7d?@|9=Y6 zPoa~|7t=lSApZ+`gU?6ws?Y)N|N1r= zxUSjvljuS~JP$+67Te1N+bNbr5_FeNr+PR;dh-u%*Q~K0dl|4#o=4zD4)=lk>?7)Y zR=>V~y1}aE7>m7(nc|;p@!Wv-Lu%E|TM-KD*Dnps?bv<_Z%0mJd9Qc$SF3Nk(OiQ_ z&n47(V(Rtdw%X-b_66tVc|UsxCzL9*HiJ#(eYX!PAkk;EtH2v!3Dnhu#p_VZN4&}s zfjS-|=L__!b5OE-O1k8sBtj4q;@WuV&?-<&)6FGCMTyZ|B>|bSqoL0v4_@?Vi0Uo? zW&JtSA%yB);+v0U;z>!$AKxm6Nz4tZ?URoYuL`MAjCnyV*00l-;*EGdtWvufOZ>{w z3+AW*q9>`Bbm*NsA}FzFjC82%OOMjDiu&Ix*imt`IkZN}_vaz&hrWw|CXj}kF>M`i z03v=}fO&5 z42=&)g^LMKEea9q@v6A3x!mcJ_Py1I$wt*u&>zz3%wxebUUq)i%T=wgwfRAqJddGu zo!pf32^9x=RAeM{9(R8f10)<786nkkhW!tMa8$`0zA5&LuUp3pl?rij0d5e%@lp2b<-{D)C{+4jM@gfI zYrmj3j>BNQUx?y5zWp{90zBj!+9k166kNUgMzfErl8^hM+-N1f9soUp#n{LFs~SER zgnkB$B0EYM84;^W;>igh(dpriCSxqYH~&YiwMZRhNBQPZh|!x)`c&DigLA@4b|$!7 z(7>Ek(}F03BCd}8M;5iAnO3c9c7ap-63e`lX`7ECya_#JtYh+*AI;76EV}(Pdd|4< z-}jhPs;TQ`m%gZoXo(9IvHbNF7W5#y9Xo&5;pSbf_pTo;iSPdsmNtx85k^>JFkI~-k2nk3pfu1H_78wUOoj84(cLCG1Ql1KkMUbL_>|teU zF9z3z1k2*5jf;wX&1S8~O7&Bn(?k!@7glF|+x_^m{DWBGM7iE;1}Ho5wSV|<$>+gH z6Kx+rI^uz~A6cLiDN3I~nvX^wuq3cTCr6cdclfa|QMVyZe~h=^JG}v&!x6Gz>I)S7 zF<)n3Z-qubU`Ci_)uXP*N3~gis7f#}h$fW)2exf`a#`R1(iV)P+gZjw*5b za)zNQ!aH;>NlA2M+NZ9%cQ(VMtwcn$r$gkR1MSV55doRpT>8|npy=q4#X9$D zCJR95WJKOU+{?S$>Tj~<=JnW!mzB`Yp4Hew>m20DceV()`g-T2zx*<3Eqe=6G*C;A zTch&gV!9L~Z&VKaIII(*rBUEC!j_aG7_i19mQGC;TqtyoytK5ZdDm|bPbQ}(7N=&m z_75l4XgVR2gc{89TRBq3E+h_*Ff>GHLmKw7DM_Fgx@)05YojY-s-6|PM~2YH{Rwt7 z!!@abRK`d5G8m5h+hh$z?5C*7vy*;#<-?Se;&M75WtVLF6Siv?qv|B<{VUf`S z{U2l1cXw%MopI~qW{^|^29w~9R9eB~+U%+&9>Q{a+7!EIRFlJHKEB}NpD!`05E*;t zy??Xn9}ap>tvLJ0NbRd-t8+_|XSNn@HE(J537K&rr_9EqU*`M%;y$}g;@0-QQp0(h zO^!D{&dS{}ZGAmF>(|()CnHlQXbA!Sld2C@ma6Yli;BH%ZA~vb=FK5iFrK$AyI|l; z>C4^sUM;_B6!8rW#H~@>{PyhvGDku^IP^LpXxCC*d->n?Vdq1Zoq4anGtA8%kl6_5 z3a0o-rn{j>H|S|Q>L~UW@MpKxgkT-?y}a(&b#T5_NJT~OP6rW@Imy04_qWZbX>0dn zXK&S#I50f_%a=n>`c!G5o7U&z29AHw(W$I(LLG)BL5t!%QYD*k{_bPWdTOa}N4B1r zkjmaB)eFPq=5Z(Iec6TnP7NuS{O+!cSl;keH%;W%IC@2b zwbRh~8&|YnVYd#)SJTr3$ke10o}^3dHp_CdR25lz$Ar3()EwvA4n!WTqFKSL1Sq&=p4|DEvbMOLHDRcGr~L_B$Om^~l%YhlYao&qzbIBw(# zk=T(9wYj+&C>r@@C+@FFJ5~7gIc|TqckIeOp-*l;|MVrAwgNqgv9^DZu3_=KDk!4j z(9kt7;DD~jZa@+986lvYX1xfd0s9jw+fO-sxmMAbzHUN1ILUoeyZ_<$4AQg20;x71a>j%=+ zooB^niXv5AKqhAq%RCp<$1S8(!>2y;qLF3-kUq4wtGH!BuCT2S5^?~S;B-BZY6VK9 z#s3tJr1+pfCm~%ky>^r`W_)mn!i`jurvv&*TJ51JX%*ifnV`zm# z)JtEozf=f({^;vb9ytm7ffO1n7!=53 z6DJ=0U0i!ZuR|{(a{+C%tro`zYh={EkO`RWx>(7hz9dgyOL0dk=P=0}U^=HGFddr$ zQ_NyG(yeRN#xNdCQJEXdSC7iO%(iUK`;Q0Ps$TUsu6H^aGC)ctz%JjwRLisOyp{-> zq9pyh(8+SGO5nWjL|Ipq?`qSu=fPV6DrIPQceiI9u?YiaknNF(8>^XStOrwN!gtU+ zjT!{ut-(_rz$I#Ou*fVv{zIhHXV5AC83ra|(t|O+$0(U7+OO}6D!^h$+LB65K+&lQ zOq1x@Feirx{vg#L#{HV}S+IG$`ErT8NDYJ3?o<@Y1zF#|l+2CPHUfDdlv!lZCV z`l%rFLP>%6HtsHRy9D1}ej+P{Aa3~2;D=+!P0hNV{WvE7_4b(*z3nV4ac;t=?C8m& zEIVx-%ogyzE}(N*TAtY(&3a~$JpfQR4)To`AiFoO7 zZd;^&&5I;I^;c2B-5y8xTh_dP7lXq!t> z)-r9Xm7x^#3WurHU*7Q@Vv@TonabpWfD>(P^0A2ZjUkk^92B7F3(lRS2i@r@Owm|K zrv=}*#g>0vMq@1 z0>2JD=y9JZ6@W83AK3L8tK6rJgEWd-@mR!C<4t5X<+6muwsNk~2j*q-=O>_9x)EU! z9tc-Wfsz%@Zo5Z*{W@kj`$ku2%{v_?ifaD6!ZzX1^Jcy;d5+1>&29nPDYj*v(p1sm z1|9y3+UNFvflX->IsimDPyYP*bL-({6y+h*CiVL8hyw#J*n!Wt;;=X(x}Z~kStbWrl{k)3(XP)Xl$F) zD@7}}1+sZ|d+=rgwm*yjpL_MPO+MS;WPR*#B1O@s)-iGNt(oMgtPcAyeS_lcuGvq8 z!7u=Pv99~b`tPEF@~5{t7>Hj7cx4g}m1If1rj~hrTc=mZ`Q-BcB_|fMP*fGiD;kC{ zMT7b5+78+hii+OW@||{&V?*aI%;4E@iW9yt>sSR>Io()6EBCX?-J2MUp9*38eGWVS z&gi66!@^_Bdj;vPx`FjSg+|n!&v*0$i(}>ODEP z$UKj37Y_3ziY_*FlHSmbRj_$gwbbiWBmL3do*`SWKF=L;!|E)y6t(iaSwheqv1;x6 zYBL-DxEm#>9Bg9Npx7X{w8Yq{xCG9~@IQB&=~!HE)f^yi@B+*Q2nl@z5B`Es5M zCi3_-SE_Ii)&Dm6)bjfYV|X$Ca{vp6)LYij%?e!}aa+Fj)tGCN#59bRtAO@$W8XjQ z0sH52m0d)Y-Sb~ZaH4T&olm7#`g) zT+H|Qv{hSIHv`_uK7A@D$1_2$Oh%E^3@&g$f-Z{!HP1V${j!}GBRC}(2p~B&} zTCm1WSm8Ik1sC`Nm$*qaBha{Y!j`2Gs|v7_<%_DHJxhn>>~C21jv>Qwnx6R4s|MI% z437Ia9!q!UAKLF$0RcL`o{H0JwY0PW;GUdK%?KzKEnS*{y!;TXSg`}ei6ti`-9^R) z%=IeiK=JaHdPn9c7!#ct<8pl z_bxEc)>7{b0EJFU*QU_N?P}i(j*7_cp`mP$vYi3Ew4yVO$za%}OO2Q^^7l|KD4Wfp z=#TAA{^Am?q&xO3!ST|%;p_1JKJzo&Jg;PUG>m!6=Dc5ycu-nj1t)!jH!#c;)VY*1 z`}+Fg9B05g*yw$ebsoQ7BpRRG1N&st2Q`Y~U`FF4ZQU@F1aYaA8oLV&1UJ7AnDp6$ z37YydD)ju|Wwup=279Sl&}GE+5pH{z8lit>WMt&BrvCRiiDKlGGtk#xSCpERbO@_3 zGAYRDqgM`Akp5ef9jZM?+bC+4OiPMJYO?;h2tzLFUSvP}{b+$xj`h(W1ON0E<~k&E zFHidQF)B>xRr`(!)&agWsn_4-1_Mnr5E0zCH;U6j3i&hGY3A5!Ka_(!vl{fxy12C# z`11)KAdqb_@YV-mYc;GJUwOHFC^Z9l8uqA>px1Rxv(l)YdbyV1K)MVBj2B3j_hod$XWUg;RMse$$J8@+VEgnxqq)filZ z?qd4_S;6F9AN)ibEB?&5>u1h#Qp3yIcsuj|Jk)j5-|@@Ti*CGT&0ufcQTL%6?%MbF zl^qPYxnln10~@KU2373CC?k%0s5Z6%K-y5G4$mZMla$aEfzwCGCJCOys~^^g8{VQ+gNl@@7l(HMFp<}v#xo=K z>t^gJGifRdy6L8SSi`1l&dh+?&w?q^e16LorJ+SH1ZmvvjRxtKZ%kfFM@*FX-0Es? zPBr>4{b1`^&y6;xUk*HR`qiH3^v_s6`;y%kGr`+AF>%~`;QcNX$^9PXDjV#u{5$a929i7H6tz=cHuzu{pJ|o}oZu_;YI4wrb z)sVsLefKgn{f8M+KsR z_&=dPU>0mGzzHac<>BHoi;}pSG0KGw>M%=@`vnb*GZ?#(N#LT_ZVIljwraR#HO)bF z9xgmKH6KB^^lL@{oGMbl-h{3#3P8(7Nhe^v$Q8a{guU9p7i!Gfqon^9-ccBKVMOEa zmpdY9B-?SOc?MdWDWDBla32oKo0F5+Ar-)y(lhX$Q|>CxOFi?KkeX#S2TpK zlg3F*9(Hzjo-{_f7Kuwp^q#l@3bMX2+TtL^i9WEgjk;l`8;KvdejpwKh%#y!KYt3& zzr#RccEisT#287OerL~}!}x9yM4J!k-uu42Xdn;`k0zoq+Ip7b#J|ezm8xVXar#?&lQ~bn|q7Bt4drO(@b=sk*PiQ5DsZhXwkr`B1uta zFY($Ye6d}0s8Zp=m?{FPgyHvUuj#zDm3cRAGQGsb%Q5;0-OQ08EJ)R%bDc)itwvTO z!a1qe0=c!9uP|{P4>1v~RC0U2Sd3ymAwnSl*B_@@M#^_r;m(AOE>Ue^vj$|@FrqxE zlax`E5{|lHzExBnJO5=#COL|_9IZ;F*3~k;usx`dt8#XBe02-W<8O@Y5IDFEfn#+V zudO+qZR8Rotm~0udck+7QglR>3IBLwD~S{%aXWnCljU=+P@KwV|1uddCb_@QEk_a(!I5RO!uVMkOsOPe zTL1ox7t4D^YLASu2EDEgM=0Ymb}UQq@kR90r{p!|=$jdze(sYlPJ)&t=b~>0jP`F% z)mT3x;2}s-petlye9T(7c3Bu@Q{p-5h2Pyk23W3zt#iW(l91B(bR4Vo;@Ci2^3PY6 zAY~hB`V~(maRuYE_!W|Yd+fYy*v1?!_V-3G52N^~HF7A5+J+In+4qiJ4#UODSOXGd zvdR>w^~jt4UWII7HwUMT%roqBlvim{Y#e78U&RoOx&#g`GYX7luhgLZ9RMUieJ!)C z8*j|YXcRY9p}{wh@$U3xr6rOYW-WyZ^3I+Pf0 z`YBk*-mJ7_&GwBi9)_NeILGC+;|blk#)kz-oyI5fV(x5>S4l22&yE!ok97Y0pL`mrXNP__0Znx+Ub79eqB%!G`ueWd;3l zR5ms6S#4WVW`sA%h$X~su=|BIukM9s(Z;nt*j>h4|E8GIEWmD}bmo5sjM7vE2 z(hmM!e|ar7I!JZ*5O+8H*DO(o%SwjdN+#A%ND@8J(2e*lq`PV8Tn7L+9Xd0PdGi|b z4>v?ROQphPR z0CbF@@!U5sFG>o>{-Dv|J0wJAXkdwMy6IMVDTw=%L0wksXy&3cf;gAm4gJHle&cdF zGWj6py&vQgOlzA(ELm(<+IafrUeOI*s$_%^w_@-!T+*$O(A@;8fqXIr2?`DjCx{1I za}ET@%2?eY*^Zpt>&%4<7bc>pryq&Faqr+{l$?^{g!1k+0OsCQnAPz-mAiJmjXzd;`-sNZC)nOpFdIb>)WVGN z4dRZw;1TRZlg(1MsnGS3xbR_|G`^_V6;WC8;N^^FY-F8uyAM2ZVv zWf>hfwqWyh@MW@ zAAk^-i1^nTv!LSp^X`X3-;Z_pJKkKsdfFheqM`zAXtHo`hY>mWRBt}gb!K+Fs3Hw8 z8JPx983)EK?(W>ONZPdK&sn^e8Ug_UzdyMLM*X_gkfeIGEtl0+=+=vfj>8RN*Z+)C ze6swHn5%&>0UUbqA_`0c+|X}jh@~McHEPy} zjXNU4C%#xW@8I3I0SL)Vzs;ZA>rASMyY$~y?%|)?a)1A6u{2Dpfu7!LKpkTpe`YOE z`LO_uj#wj=Y0WHtdN8+@^Y=Acv5Dl@!6Ma$l?i;c&FpftB{gxzi|MH|LpPJrw>Gt( zv;Z?OG&F%Wagm^)Q;|EE$QRC^e}ga$e@oPe1)S1i*wD$$b)oVb2I^dA5K*^s94 zf07Ueuiof*NupxB)!C^fHyJhH6kNgy+(AK@U4mkX@NuO-+VvR~7oA_LkR zl9y^d*&%dz$A3!@bAPL-shJzA>MQBeU(Ni^2C4o#9Pea9sq?Gb6wwi zymt#c{Uq3IFz}Yp#*^k1BfGQT!zu265<>Xd?J!!qay50bEliQ-QS$TW^`ggCSvrL?P0`WU z9jtb+VV>795qSve8DCK5x~Arj(BxZ;V?6FC4YdLl&+ZV?KM}5$$}&o}mDF-&4-Iq{ zH9^_?AM_re!_~gY(`$kj-jHI{$4L<8Ax1dDLK<-NVoC0Z9@O&R;@f13<){ zH@%TGy#uvvuq98Iyf8?1x_e?CX262)9(HB$nbNShTE;={t3g{uxE7dRc)7{jDts5m z+_^`N&t?uHLqF2RL8De}=3e|L*D8aF-rpj7s!AG1w)7jCvas+L;Bq_RMS%ipI=ZTL zTS)>k!I@-o^k_ao@4&;TMZI!=3;48pwSJP-z=PY|3wAH!;}d%#7wwWq*Tac|4l#=- z%z=ByMQ=3br0APAa+2_&IA&19lG`HaAaJ}&0~g8&>{j#-=3Bft74$a$;YV{$G_B** z+_h^LSpejaLL;1BmF>d)5qh|og`b~a;S3NN1AGs-9+XoqZbR6o#iS%8B=*GPm~+Nl zM!K;fO=_>&PnG*N&Mw+W#dU40r1$gnQ_~1q(oquj#hyD*X!Fq53^+{#_FR%%y}YT~ zme%);d#U2KdHgav!HmyG50lM8nta$6kc{a?l3MF8<;3o8dy-5>zC*9H6KxOtn{fbq zz_H$%>u`}2=e$lL``UMMP$6DDa38c-p2ep&ko7A^8E!kx)*jNw*#G$-5f}*T)G@#x z1|s@zHF;F+-Il8Q=HpG5&?jgqF>OF*ym_e*pE@C5hPwCGQCLcTE9tx0aO=;s zqt|U2Ozm@7YR^p4&p-Z;{442g-GWFJd2Krt`O>Im0s?7Bb5rZW@y;MMeOc%a<&e*( zrbT^)W}u$nnyWjYsnb*loSkNhUr13F_v78Jx{W;)pkHO;D&Bt2Bq7@-?rj4jI2}p0 z?0_v5#Su5de*dM(e0K6{DoieAO!1ErEt_xq$8g@t9gHA9cPVW=SHKsEj>pneX1T-)jq<-)4xY_r-wEUs z#oK|V?zk0uu>Qg1SWn&Ci62+nXn*re{OA=V&IOI?M8=kgq2^{qZgCzGilUsQG@}Yx@2?_NPrI#!70j?i&}13iV@E&QQryT~ z@H{>=OGO(w{QRX4?8B++J$Wh$?d+2FKe2u*k&whlKuy9JzwSD~G~DVwYH{Mb_4m1K zo_`ZhBwi=RO%g^=*tk2T|NNSIwryns3nPz-+Dj?A?30jV74%qI#%r70Yb_`F=FQnA z{)8mPzvHSCk6E9~wRhm?&RtHE-AR=nO&bA3xA$^XONb{S#z7(_9{@9IW;>U`~jAe_&Kb&ylVY4OATIbF2vn5YSU6Nw{FIh0=tkBYJGTxqJgGbwcTTxQoHK>8 z=0Dd0a8jA)8PD{RUGAU10amj5biT%Q-cG}~q@PEBVO|_Om(dhCrwAIdb0y>&1FIDdGNNqXE*xAXVc%L(`a(2u3CM))N9#UEADp%cI~ia~ybG;(^Xe_k-y=V>%dJM)_7vGS2Jpe-VJ0RCRy4s?awM<6Pm-LC zKz6JzGCqKv22hjr1z-fujIANwT9>g&MFRoD#~7(AHSoUt3n~8YBj67z?iA$%7Qpbb zRQ^ZEi&+_?{r7JG8~*bF7iN#$*T`C|`NZyFaN+3W$jjyU`ad7WKWwi41R}yr#RAdF zN>|1^zp0%WxBCR!*wB-gq1gi+8zmLhrj!u3+?8ND)YONyrYBHP72P6_BbI+_zG_L^}j137yFap zw$KeOSL#}sqb2aqReXm!3vtFgIQh$MJ6hO-Pi~Im9R*VSZ^Zuw;sS)cdOOr6lW@TF z^!I1vI@C$a$ZP|#+$8<~4BRB~b%p*Udu3YM+tgbI?iGWj=V-|qg#dR2P?}Lvl4s&N= zt}i6oCaFH`i1wnva7XoAv;{@!XACt_^se6BJC15{fqTb3bnblk5!_!S?;qPC21|xh z$2XK3EfB%H7zFN*e)co@Ft0(;;xJ-ls>MC{!CbM+(pe$5fShRWSudnC$LE%B1!}QuH{?wTAO{Ox<-uBQ2Ji3>if=Z z!!HyH*T1^DC_wx6(+qiX@9FC9~L>Ee*fF3x9LwHE?k_`*N zZdqI@UHMy?a+CI^O+a)KBZf+S<5v~<*dgThw9H0C6`6#(^s1`fjUl8IsN{A->@d0l za)&Pk3RLvb*APtXCYTRo5okxC(8L;?AgVuJX=vRG3(Bn_Wq2ubv^l?%^W%8sO4PtG^Sv`_>XlnoD762I*jsCAN zw2&fEX`u29fW}~GK|1QZfSL{v{{>!wpl8yv%4f82HS22hz|*>?PYh_PTl;)+p-hh4 zaB{EO;x~rV1Bj!e@qgTur>P(q#Y{var~yF|j>|g;k3x7KjEy6$8vdA4dUMKM*8Vk^ zMKEMU=YV9K3j+$7n4!LjHtMddwREOUwa;eiURFmYc!%guM z_weBB>E-iScItX!UgdY(ur56i(Re1lN82nVMQbf@^HZ-+H+c7VpSC=MmWxkK?fLMe zg!CB|*M)d&`&DQ960k~N!5lq-aRj|az0edANi(w1 z6QF$PGhdvEDlL7~DNPu6$Wn+zV&F|~az{Cu+5rR+kfv>oV4u3TtKX3aD zZASfo=nvu_SAG7h2{<$F9KHT4O_g6OT3(9p%3>VK-B7Vo{~tR2)|BCBASpy;f?jq2 z(g@^HzLT00`kzQ*eaA-+e%wi=D(DG8-C>>2H?BR@a6rk&#B)eB#Z+0}bGgZGJ++=* zHK*a~6MuK|2^cRA=iQ-1!M8rdsv-Cq&>cC5u|ZZRC8Yox4aA&afV_DaCj*dMX}1?7 zz_i;_T*eu1lCK$M=mU7Ig(7k1PUV)K^p*$3@@P23+_)4>0o`JU-_w#(l?(LmP;T-5 zM&omeTfT4w-oN;5c<}r3cR)9Yb_hK^VUU67hS_YZl0u?J5}A>TjucJunauBH|62ec z#-T#Q+fu#?lZ4~?V~_q026(gi zIuN2%^xF?0a7}~1<&LUX!R{j}vX@_HTb#s@XrOKJau`IWprF7UMOdKiE9#g~FhWE6 zI6&Y(AOT*Tm*vj``d0FpE9%JMt!e<~HGu={+3B3Vt6R*THsy%;@wD%Kn3SnN!j3<+|6YKQbYU+2XW3 zbaeXc*`}r@NZjrV)`5&mSEA?rJE*`&WYPe#dRz&i52URbP;6pzvu=kg)6{byC3*b0 zcowM=UX1Z`k@v-Q%gdLm@ELRC*MKX1*qzMv?}mq8bkZo(oPN~J ziMNuA3)}OncfTDDSZ?yJmGbezvez{`$1V-4FW#7&?*OmRC5iI-KCe2@FFc;UqQ1MoyPXBcI z4YRzAN(vhxM_R2x`;5{=%*Um}fmw-8LUwD8eFOFvRrSSPl%=9B=L%oAPg+0Z5q05| z{oqyIH3s(BnO#g%J@%RN(jkKnzd{orOejzeo2g7_R}1AO_W4?yulKes3D^DWxB5F! zz|{T8Y1izdbSmxm%8&hep?oj6VGhU6X{-21cXW``mH@=D!K#IN`3rfj3}RLW3@3qq zmUC_;dcN4-&~8p}-pv}Wt;4*85&6=Lpygql0%)VkY1z=}sQg0VEfV_gzVUCLp+E${ zPo+1yt3O|fG=R&|WwSZW-8LX6eE5PdKQsi|(!2D(ROW-7{`18l8W`sN+sl!6{kPZp z&)*f%PDraXt%=Xe*~W#6_KSeP&2e>Q(2}K}aM6kELGE)4WYFX<&zJ3R9iV2IOKwyA z=IRyAr7NsI&5k!kvwRk3|1hZjsy9xxB;?55QRW@F9gCc`q-(w<@9^pShgk)*oSfr` zcQ|%!x4*^KSjS~137`w;i6|I7^R}T(3Zo*T>t({A=;}VZsra~#fO0osR(Y(NDZi5J z)ad3zJqI0|60Yy(I~vz{_l%0S&!xzb@#$43ds+>;_0wd%3N~(va4x&=_rH!-88na- z$_e}Q@ise;D4C$rq16&={`!3#M%7Kdl4oxlCnX>HW%-!}vn8T}oJ9rl`O`FaAbs5B z+)Kfug-b*gIjg{>>sqog<(>YV*^;YYqyXEu$2xNP8IO4V&wcsu9O=l^wmibx? zHFm!~WycD#DsG`9HF4dSwaz|A)6AnZQ1pL)6Y0|WY2ysvQBi8V)QBL_X6)Rh<=J)P z#nnrXVqvCp67W4bh+&o(t zToS(pK6kEm-xo9cHwFS?HvC~fN7+_-e{`l)){+N+G{7QVPHRu0XmOLp{IC6m=y5K0 zsMPLAS^P}0fcWKXalql9u2}`N2~f}8h`l^YgPsU9fdNQKJwwA=guiNTZnj5W&;KJO zek9)m+R&Z}3B&BKJDGq{D3oV+3zG#-+5P_(E-Tke3Msifv(K{FTIfAb-k9IVz~+?~ z&`ip~GM{0X^8;#aX#B(*E(r%*Az42n5V^5pVL~#)yuu8jss>@B?%8BM7dp!&rjq1MetrIc?Xv%=XFW><(&hh3+uA=gbl!1l zYKjSgTt0F_1~D3`reI+#Zm}6`bu!eBXF+ODm+Nybjx#P!wlW#XO?N>GQZgI9Z*6c6 z7Y!M=bq0vaxZQ`0#uijOj?X(ZQKP=l*gr|Wl`@*u%>52UfgYU*W0TpK-YqUBR%=lP zVor-rXmm94&R%GDK(Fki@he=2uli6v`dl3DqC09xZ0qHGbjq=>57_eLDZ>hjkpK1T z-@8D`JEI9_t!;Zz4j`PtNg^Hvw?VEb{=8}P=5J|j<=!Qr;bc8Vtqy2tSS$B{)J*_8 zcieZn&K%m(vY*Y@kvCZ>wmD&?r1HlvJ<680=g$Zcyh^e({@uG%By$FoXI<_~1P|ah z;t-Pep=;KIXY5f#G2SNh!7+m)2cQRR>0FqUm)U4jj6sM#Qze~Z@Gg_s2gP6|OtC~% zKNGP|naQ!9&->Z72w|xjB${PptN<|jFrH)b9vvcGddtpV>>B?{O8&!^0OylFq^?Tt z!|}<{$896-h&B8p&71sW2<2Z($))pnoA3);tf?A8CDn|WLM&IUIPb~ZuwW;^YH$`# z!x@ll%`hB*r64{JSKot$p1c>cCctj=xeGvnan?Lgj>8L=RAItJq?;)ECl>PUe{ zEGJRhV_^`TJqP>X<0IuWG|-j?3wXU7R5;hXW_r~ZQ&4rY>*YIlzlInCzdnZdQDPA8 z1G-NX1T6ZnmRox*N7HXcMm_-eC#Ixy5|{ZEx>;a~?Cg&Sour;AziF9&GIy}>WCjlp z4}L5i8s0(uu(u#GLmWP1`003^WQ8cA{uz@!JgKmYHV1-Aac>sp(RWK-a${qB6%*w z8z79#vM{Rm!gQ}R?|$%>;*}AdfGl*WKEFX`l!R@JD9@L84+@Rv{&`e=xktERFOl;4 zK6uGm@m7&aCnc0EZRgBDCU1{V>J%`s(Y(>C%-l|Fq3k%4i;=Vl+^APfIJ2aLEOgRq!132Tv z^&%P%Pd^RxD8SS?wDBi5S4r_6m0z+$py_n`mqp_J*A)Zf0dTC1J9oIP-%Y-nVo%Cg z{7PB^1p3QwL@LRD{f_@c_zXzAH<426|MnLbZbPfVky)uuwTnQ01(k_5Lv1$QXLeBj zsC%7zvPBz49q;N+&O$T@6o%iD zbCD?LpqLRyx;c?t6H_$0b!XKr-lo}ccE+IF<=J$hy_?f-VO0QB_xsC@JwNE(>%0ERZEoY80`M7ZuZ2rm3dn5`o?-TL(w3)%(q&$Pc^(6I9Egvto>TOCyvK7Y`_{o7vaK0m=U zy>RBhQJ)#a=(o0;qr^bDBt#~l20?=-Ko0^Y&yN$*$uO#1;x!|m+;D#Si$xijzCUFP zdI#V4qwq#;m9q`sUW-#JiDX+k&%zGJOVe<7FY4ttcme-514-oBBn117gF1XxjinyW z`{^THH;=Xr8Y89}w* z2mF`XF9vL{#h{IQ9p#wPUHi~5=l=Fu9P_^vtFpBVp`qf#J(Mn#giANfsyV}$sj02) z%97hC2WmqK+CztQ!<;9WPc1b!H|MmPhkIJ!;-_}S1WZD}?SlzXx-d@3^*JKaGx4%T z7cDK!Pu{p0SH3XhyJ&}@Aio(__>>|1$HMU_{9l;Zu$rcU0}u3-6J_gGu;U9l2Q&6^I z0dglry&fT;F##M&TBZn`RPG$#M`^Vm1CUOh;m+vds7op41ht?vx3j1(46@oNPxYrD@f$C!9guQFuV*5O5KO#rQ+ieDX zOJ0z(znDIDp+Spl0D~;p!6d}nY5UGJ#mNF=wwFAHY#3Ow_R^d_KR5{?kRBcw&~Cbj z+IOjw+qeZyN+DK20t&EmI6d8FeHNx|_84Y=2jT27JpI&;x!Dc`69t@K7?!!K&OI-1 zgSD7|%8#hKQG&VPVcnAVxT_X&XE-o9F2{VpqOf zDH%}HH56!xS_F-Q0XDpa&o$};0Ct(e3&Sh07asu@tOHg%4+)23t7R8_wq*W17mR3z z`w}KaVRowJYp2J+WcC5vPQ{sllkbdUa@KhU;Y*Qaau(*Z(TR!r{M}fnS13V0L`@jR zg2)b-%{8msu+ou%=>loMaADk4Bjr=EYSu#3XQ8exvdfd>6lYpz%!WGxGmlTf8%m{# z>!&yPE{x#;Y&=L5q=-+s591~qsy2S9Q+}wDcK+&9Zn{=|E*tfa3xWiR1d!;S_c^wU z!r4TOapcY#MOJiPP5WBCq#(55!|a7wqM5`wM58avFe}JE zqiUYx5{=kn-0A=yFDaZ{KeZddi|9F+&y+<7Ny)s&M8)q@l{bhl8i7*{L3}bXnfp-* z=~vL4idO2k7QohBGCJ#={txIDN%jOLkEPTIc^xJg2FOP0 z$W%%8rY;-i)PSNJ(CG2sa>13KHMo5~gF+DE}J4L}6$F+1h zVpsHRp;s(ln6cwtw#-T#kNOL+=6kL`y$|UCYu@+V?4l+5SW+_1qw%}(qj|#Fkx7@O z$40HRYdYWpt(rGtMa!Q11H0ZbP|Nx_p2krUq+A+$%b}t+A1ZLd(GrF83&|Z5Xr@ze z9J|f-FUEjzi%tkBB$nx^GphH0DwnvG9ubpeTJivtsEwB{18-qRhu~h+Lf8uPk^i}O zu%pBvRAuy<=``ucaayLDmJ|RT==B#DReNN1%(`s2RIhb1efClAf{!r7(MYa*qyKY{ zjgl|VjQo8N^)qm5=GicW=5a{ovZC(XNkt9*th2N8a^BjHWeDpfW_1^g`#6gpjToSF zc6*yj`CHsNAEWrlfwhnJs3hrHxrPp0gghN^38#ax5|@y8Y4Uq@D{`!eoue4hL1{y$7Gb<}kBbL3eNBG3ywsf`p1M0oI**7GiOLZ#pgG{d(Q3ULx-K)R~OA3~I!_8c|u zHFt4w30btU4t`5`T7Qez+^1&v5BPHxRpQJ;-3JCW2#Km6c0jFIit-Sjw`v}z zHxjTL!mMN++g~f7jx6?u6kh}5t57Es*R}I3u&L9A8TwTW2IDmhs>SozW;Ed5;6>b) z2rAqR<}tyKZ_8L=R+`d8zFguBsj(a&N)$?XclBP9 zrqFc14uSz}x+a}czxEZu>17ixZtyb9ii|w$nM8*xfLc|K%7qG{(kLycIx2 ztmEmNy{-egW8C(nF2~cTFsFI=a9)}`Hn;tB%vn53ItbJQdtt|2oz`Nz+qmF`%$5_p z-J75mE|++SZ$X&|EG_}1E57ZN=-4 zHAHM&-FbN|c>U(h);^rxsH5O~`E+&W5ws$^QCts?j!L5+X@orC!>>DIrEvV?F4|?4 z_4tcVl+KM$04%JZhs*uN$l`NoG|nim<#c)nomN%)e~-Q8Yf9CbOtUiEAR*N@vOeCk z*>;eCv|I}cdNC6Eji7*xbg4#81XEefMO!lCRwAY#YOGVOXY z)D)aD{a_txj#GIKtX4y?0#%~`R;%$1LC~P^qoO5iFlcA2`x6BKkI}|v;|Rr-XH?Xn zJ$E&ftnd{nUtI7?z@JE8MsBFeaOKkC1)p$PRK);YlqP(W5{7~;8kpSk#pQZtT(712 z?A&9a4XH{genoHw%0^2l|HyCtHROSO<-ZA5{Owzk3?UT3-NQe~W9;qksSJKpK7_U= z){%!oapXuMh2%gz%Y^k#;Bv>Xb0o}4AL5k7e7FNdG>F_{A!y=nPhc+RAG*=VE!(th zTLvo0Gzg1GR1?}Oz-EcC8&32$9mGTq9Z%1h!AroJ9ZFR@$eVw%K}aYQU)|BVmz)TFfyqI}}0ln+$n6d!;0%i5lzPTTyR zlbuq$V`JE%(;$DF0i=kSSFvQ)1vFAJa}e8gpjU~)MwkQ+NRy6TGTC?TgRs(tcYfHS zlQ4YN7CaFt8=88NlEk4_H}Vk?1te2HvDIfk=8_6h$o(A`sZ$X)K@ejD%F9$4@Fmy| zBv=dZu!{oZ#wTjQ`yj=88FDaZQ2noJccl1v5&h|Rzm7lz&_1~rB}pdMMbF7{&yPZaw+1>&3P~x3%(W%vmbSL3JumO3K|e$izKEj~ zco@p9ugYcy(!e4~4(D7i!7fU0DS9FbEH-dOA;~_yYsvAE#&L1;tfUi_%TT}&&kGllaY{dp+N5aO`apTIgo>S}5W1)(-+_^>@W7-M4oux2@BqZ*-xohGnsZcXjehqaay{8Q+- zC#@wFH=Jyy~70J*6R?&Z{V%GHD zWG$)%S&S8>5m#rd_oNXi+hNOPVqKH=(xJUb2L(ZH+7w_!HVUY4DiTm-m|hwP3r*%^ zRKTP}cUZ*IC%)9Ny(-AkBS9&^rN!~-Q25yafyYwF;&BPaJeZq8KA$Z}+l(C<60G9@ zsNnh~9|}kdiCrPYBVnzi&phu-NsissP;5^$vIG&@lKbdn2C0B!JqN^A^UG?YFDUd5 zdE$hu7=m-s6LG|o{D?2hEB44cs8ed~mMJJHwY8%XKOF;b()c!1R0-H&tc$@AT8Yab zR@fPntUJ#`3%%X=Hr}xSsSWC|JBS$*VK}WC3gB&KOw?iak>^l2eWm)o6d1>?noOMp ze*&p>8%|<(d!xZka2w|h zzRX#zpVop;E>CoHWaFfOx?JuU{8$@Zf0zKJ%h+4!3{}W%Tfc>lDgMB0CV&I(n1| zynP1^;EWD*ZBDyc$UC|U=L~)FQ%4#`XghZT^&7`3*Q5g)NdQC96Q^h^({jlLffAB>9PY4$lj>QYSeI@Fd!ivB02* zNw1CZb%eYDoIxaQNzj;T(y;{?i;AmuhM-a;ibBMpgJ@_Z6fNLO&pd4krI(yqYe1N> zai-A|31V(k4w9p%m(aRZ42QZdD%%98)C|zf*!hp*WZm4i z6}Xe<QO8V8=t#!8-|Wl z%uhz64Y5(9+u{`ESG!!qV!`*IfRq?YWj#qKfOOd#;dex@3G6o!ry>pUYPmaTP;Tnt zsa1JZjt8E0`vhwdmVHH2MvJ&C0L9L^_H7K00#}F+@=>FfF1q8fkVj>a8C`{`M;H|!fn>0N_^ z1?U1+j0GmQjFOa*7hTmp= zApMugy1E#?7Y%q{rR3W}q!U4H){K{vO%0tfyJTPnsQzmIsDg z=oQty0A0JtE0D>S~dTfo21N+<>;VM<2JjpTitPW1(ERo z{_9t!K4ug@HhCNsiN&U#fxvZ-fD?|A2pL06Znep+{(Poc-G~Bq8X_&gW|w4t27{kc z@I1eLU%YJ^pAS!(XuvFLm3wG(X>`&SEgya&g&NkmR8n?F87jDPos z4yLUt;PMro}%Jth2VV>cb+cn{Y4m9If41U!zB`PdA1Q zPMctX8Q?>%IF>KpJ^0+jq1`R-?G2Q=si^XM81;ZC&&Y4<*hFd!>=Faea?=xmmJ+yF zLUkvla-wL(R!Ud`LZ8@2eP?cxb|3pt8g$kq`H7SQL{E&e-4mCtUWWyN1`d05)!T112g+1bq=5Xp@5up$ zC2t+V-#PG7d5EZO#z7*nOqx@$0egfPl9&syBF!Wz7T^R4tRwXgF881H5A4Th5u!1J zGdF_-Ap=}=o#8JlE6azz9Y0J9$RdE-g+!ES{U~>ilQJDW7mm^a;3mZ4O{7&Qo5lC- z+s9}rKJPl-r-SD~pU@7ta4q2Rh&UNSuO=|1n-Hafn6PMZ4`Kzv0L7Oh?%-6JV>ZG!O#T7jJ zsYH_T;SiEP=7MlN6+0$lGSv%1(GUt?^ES?`iAIs$RCCh`pKG?DEj(^Wj>5@QqI5mC z&F6GaMB{_Aeiz~xG4nwe{&~2&QZXRekP!a_LS|nC`81AR#1?NBew0{3fMGq=+U67b zop{&*jGvdN438q1B3_8dtq@~I%4SnQ#^f+SXdKbq;jGP&dR%!A^nTx3-_O60XBQ>r zE(wVv#3_d3ha4+VH=$lT1ZL~ygaV3B9f%}|7=Cc@?uS1NLQpb%548+O&;t}l1LGAM zaPO@0PwJrx!B0DkvR)6Z&?=W;yiOCInsF!z>~Oz{Ux+-SXref6rCwSjIJ;C5d=Yr< z+A+i~moqNn0VEm@lFdVk3oIR+zs@kG1bT#^DA0ssfte7hb~2t#p?rOvpdL-ZKmz!1 z2U^S*Mz^433-P~l#l7JBtM3TZ3;cj2wP3*7IqYJwjvFYH9h(tjg-B);2u(!AQtP-L zGSDv~Z1-m z5sAzNP4z)+F{KS5hy`-0-qiqTMubXDd`48d?|jI=k;R)*iT{CL{}ybsAYA{<&8;YM SMJxGM+JPfKX8v&M=l=pNvMWXa diff --git a/doc/images/resource_setup_example.png b/doc/images/resource_setup_example.png deleted file mode 100644 index af49b80c575033b4b9cf0b5ea8e4af965ddee531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64438 zcmeEu2UrwYy0#!{0}9=Mpn^z7BuNIz8AJg=f|6`WA~|Q2+~h1G!Gww+IcLct2#6p# zgD8@bBhXBcZf! z-+s$|`!K>_2f>ks^mF6j4~DgpwAjA1CbCca_Q|o?NUGbIIv5z4=|6mYsu{jg6U|_wrdjS`JYzF6cjQ z7EUg_$E#-+o<0N#pPSeWRm>0g&KvVlh7;9=q528U0}$y~m8ftFnyyf-m2 z)(3y2^>vNSp-c3vkoGJ_=HPU8HWqdkUT{eKhLyP~I7y0)jh*Ez3l}#V7YiFV7x?D; zqe`)Jg0rDnDkBY$Rz`bn2T4KS25Gn>0Yhm+_RCIE*DV!!ZYmlmp1q{TWw-O>cKTM< zMnG)aLPXyjy4c3iLVxF|p1z%tE|>{BEr%rSSyAvE^x_cLGeR0#Ax*&tGbEVl_6UkV zlFV#7LmTaiU&_eN=!$}p8i%~<1!iUrIaNz8#~(*Sk7?>4ZH2VBanW3FTgZA2JHxWE zodq|Arlse&bA$&h;`RYUEA)MKZwnnzHgejX5Zmr`hPFm}`qsOn?ObVNZf;^@Wbw;` zy5?qPK$CuPBGSsr-2RuR8JL^wKHctJEx?2S;v(pPvM$o(=c8&ydNx3rAgyI%& z5iMMOJs|7dkLFf3H_Q#q&5$ON-;at}ncJG_ZTkbDxnkeXxnOQ?0Yu3L4&Kzav2om0 z9Hgy{IXHB~#?)l@TYU#38}&W!uYmW!D#1z6HxiJZLL+Z~fK+t*gPDz$BXrTX4|}eH zzWaXJ_9ygpXpQ(T!(Fjjo7-9eOWanSe<+&W^K6h-hWfjT^bgPD+_?oLx1G_pFZ^vy z`K~Y0`oJ@R=>wgy(lj$*%kdFQNx}t})zOk*>(D2X)MWl_5z7^!q!J3{0J{trrNCXzh|2cJH z12=%~@gLNu-`0;EUHz`Hism4Mg9+}26HeY;rvzCXm^v49CiDPk&D}Ay-K(H4{<-p^ zZ?>(%-z?;}r^x|wfj?K_cO8Ho(!_SxCM|%oqh;qd0q#KTx)pe52)(n=G1WCO-g)Rf z#$kWM$VOk;0=eyE>_H?3r|xlrNNbCo*l1wn04d$Cs}@KrZ1i`f{YQNZda*cvxmfJz z&-UNrD0)3_?2Vv5tm%K>8*%K&d(Yzhv;hCXNOqQmmj@lc_Qa(>J(@v2Pf_2>2!srM zD`0O>Fx+|WKjM-&K`IRys>&uwGZi2j4d@q0q(pC1l) z1+vHE>YAAAg1C3~`p(}U<01%(f1k+6#r=cN_}fNC$fW-&R{eYPbQTCk5(8zfmE%tM zeqfPvO z)LrnP-NUXqvq5)-BGaGbE`AyxeZN0xd=8FX*cd(kKQ{i~%pL6-)1EX>&(_ow#FQV? zyI%uCtn>|Zem)yY26X{&yjvpeDLMXCoaFa8jXx1I;pX|J8KTwTZ-<%es`H+u{W()K z(*r(XyCtG)g0!|a(gl8f4{G&G?0?(gtMB3vd$PBGmc0F{tN#yHTYFXy4HxW1ywJyf zHz@ymQVq1*{mIj#i;Z1R!;V%lw4?vS80xRykq!NTzjn(%99#bD`P432wa3N$2T_eb zmQSIRrz?;x?R3KaM?ej92lt=a+5Z+$11*p}KJj;;8usQ^dOF*fKR418!u8FrTSI?- za;Ja!*v9pP8$dgrJ>C(0Xs>rfA4B`6|9(G!eYf}j|F6~mBJcUryOX;`=s$G~Y`cw@ z{~-SHdkTi#ptIe_{_RNod)wXUKJ35d8Laem%|Yr38hStF%Q{eE{AV-@{~bm66Vu`! z5mQbyt>|y(JJ@!i?Z3Y7_;;56pA%mG^QQgVrn-~7{%Q)#`$N{uiT;d^N`KoF7VTvB z_?};@wtx0B|BGA@H#!g5En@!0E@+pCw`avA0M!$s*|DEBx3#gb1;EMK-PH6)B*wtp z3i{IA6o98j)(~&^$207W0Pzv963uqT`L##{XeJP$Wlz12G%+%S_)of^x`!ACVo(4v z0#r=V-7idy^zHzX*cAsvZA4-2Lvh!7lvwA53}wt+@Ct4u12BJ3>P1=pPP?=t%rO+BIND zH#vR;rv4;U|HtwFU{~UAhU%mH+k4{6uVoaw!9mApcmKy8NAj=28NX_rf1>ODwti@R zL1%$`TsJyR*z38`$Iw>s-}l^XyVQa`Yh-P&V*%|afVyUT)&cA>0DCQT^q*J|cJ5s);WrlK+oJuoSA_j%mZ{=41sEh-pp^v@BKETe`ucj;kq{5+ zhei5dn@q9&luYf`*Ly6I4Q-`+6#(t||2ifKSPt9Ey2m8{A=mqV?^>f1;9X;9`i&X&ZO;dk2twWt?cvd}bkCxr_24hQ=s!Dn1q&On zhZ6~H;=5@CZT|sp0G?~B3myKsvbME0GBX6H{9ix-LfGC;{Ik0J$jAQ&>hi4}y;on* zG2*Wi4>)!s*&ZjcM_=^Nh|Q1ii{b^aSNSIx=l}8rfcpSP!i{di?z$5WbWGXfPQa$+ z9hdVL*N>m`g`H{dN#1R&jF4uA+nCv3AY1>D-1JS*dxifGWRDI3e<9oanYs2Cm;JXY z2pvb!nbO}*_MAWa#lN`he`+0|^S{5J?0*lf@B8+BiSHZsCBGm2^EUPWJ-0%lv6p|! ze?ae<{JVg`9NfPoG5-gsg+JoYKi%@*2B5$5C(+^OZ@K;QZx^tBT3IyG^Y?R_KVz-` zhwOng5B>Vbb^niSLAHNX$)T&Zy_q^XxT4MVe*$dZ?#TSguk@ff$=`nuX>W&Z`&;xc zFztM?dja$#Svx!DzOKR)_wA$DCo3U(*+F|U9=lUTd2MSZT3h)8yWVLAi5SASaxryY zO6*?j4WVK(+G0fE47xtA1oU2MK0SC`EQa0Zbhbq8W`|+v*2cPg9bdC`Q~jg-Nc-=F+BfYsE9peV=u3iJ8=|ouQ(@+kQiLdS;9@)pu zje)Jrjtw2*^my^Lr76?i0!!LZR)wQoJiRVUePv@Yk`uIem|Nkffc+`mL>LG-c)3qd z%%|h5`{yP>4QdhcYJ0GS}2= zPm_rI@a+nbdaN&@SJ}_0ysyU)N`^5Iw2UwRFVZJ~^HS!>@qmQ;Indg+Kjl#i3K`%hP?PuOk__mWwu)dV1dQ z444?F6&j>2Z+&`pzq^68+(fI$CR4|4wPvCt-JHXqG2_FVSn1bO9=I$S_Wh1^X2nx^ z-rZw?>CM7hy=DbN2aP+@;uj~{8}J0bC^`?iA2N9y#GPgE=3ZB#Y{X=G`DT`BUKd>} z+tF_Ijx;UP9@G5XkzoF`mNQ%(Q32*}_sd;5Fd!-O+QNxPv#WzY;)V#-AWWv(hzgRn z$OWvj^?5lB^U_6=nbPIF0G6Uxsv4$TYbzrmQ+IWPGdpx{^$D%difcH1da|&zIa`I_ zBTFAYnzpt!Q$AHgqLcGJRij5qj``>(6qv4y^V(GY)KZVRPLdYZNAvOx17?jpn-MMB zc9qmikEQbFYDs&p7{-rP>kpKlpH$Lj!l8 zjdJmFzjOTAo?BZRrkxqcypN9px{cFI4IKKk9fl6{Tn}KL%y}oDy|q3roMy&?upbHH zIiWjtPCyIXw|JrC$O+C@@6~mWbdcyiiF5oAGks1VFj~tlW@CM!*{((OP}gmY{V%jj zozmO_h?ysWqU4YZI(1U|k||Z`j=l<$?18&1Eb7Dx~I}c)7-f_(4pUm*Yl@%C^RMu3deRT6xDCN>awQdZD@pMU?Xjpj5|0wEjst4SGCB7oTHESj z7%U?~L8z-Xh}3YZC*NLMbW>;;MLpTXKVvf`AItp)yI7M758F! zu9@IGUD%|-WAjmwUUju^cBsm9W3eN@3Xi{W(RQiJkaT%%GP}7uOq%>tiT&pn+44v5 zBxn*!wd}fTuq##<#>Y($VMy_##Ca(=(@W;h(;ti0adV`1isd%Fi4v?P9jP06pROZJ zD(IBs3YOUpSY^+bPw%E4`jY!cnoBBtxN0Vk|9psPW-WmOk`w9l^ksfoOae>URvLN1IdkH*Cb%Uby?jn;UiWL@O9z)qhT^}@)txe~WbhQU zMDa?HdK`KyC8{^~zU+dG0r1WvK_b7YeR|GhzuE9CH#D*_2UaqEWocC6? zo9uk6s_m%HLyn^@Qz>DnYh)`5O zz>Dsq7>Fb%Oq6d|wjsa!ohvYRO_#ZvcvzLE530)e{R7ec_Q%Bn&eo}>UL7N{M2QQS zPUb!qSZL&6;e=z{jk)bLKuNsZZOls2gfhg>R}7A? z0sj_<`usjMcA;^Mz%qu4U5B5>w}RTQQ@ruO3s_Jiv8Cv%GYPyCgPFScItKNs#ujTjQzdddkml-Oe z3-{uu$^$yYNBjs)@;SPfKd{IK&%r zO^b>}haYG_8gubv*3v{udbdHW>4$snec)lWsfRr#<7uuBosb|K4r^&9R1kREUbwZn z7Ow_pJ4O>+Ph|OImYJ5{po?Db!pR9rF1m~aP7S=Z`9gzq7UOV#;MkBLK;euera%Gi z!41p-*X4LZgGp-L0phihPEVhGBjXM&GL7!=PXp!UJvvgasFA^%%k}j4JBNHvO?$!m zi?1p4>V^n@Ac(hMri^hIk_q&nsHY$9w9W?)Sd=%r5=Q2gQSG?!p-_UR#!H#%HT<0P zN!v(5MOaJmbWy{Ua`D=9anRh5`^bs#)C`hT;D$B$y)F@m)!#)_03!_1b)L+uJ2w|_ z<3m5(#9^R7r+iBqO)_OOFha{@!>Ccq4GY$K1UQ6ctf7YTPChJ1JP1+ML< zx`s*9@^Zmj1_x)nR?EK6RzC6o6<#``iy9$Jv&y^JrfmBD#TAx!G}!#JOb5oREtxQ! zhx=U?E_TphP5}>Kx%PJ6k5>W@8Kq~ONU`$D#%~`Dp+TDUa28=K)_65#(!S)mMM^Mvauo28U~9H9x0jkS(+0HE_33##0z~o6{S%m5`wH zIL>i^^1>^l&^fF8KD(5mfS0;2F3m@w7+0tt;%37ltKN-~+Gs1y@#$?;DK{C~PqaOs zrfnFk@HB0_%VzdUf_b*m$JXmbZi&O{*U#x%S#_=$nZUEOGUscdi1(IGmv$(`JD9O}tGk=>i{*#LnCXnUTU%=bxGLV1-kJI&>eTDk zh)D*-OcaeeK2mbUNj7ATGd03SGFb0xy%*D-&~#fJlYc=u?2zek=)SFgrQ9<@dEScn zsCwKJ)#S3VD3QT)Ld|WrCZ0dj%1taw~o7*w&@ zE+#=ng=;RLAElydiEKmhFV*Ln^>us4Thr-Lb_{vz3bVZ>h-0nqiYkfoKUZQ?M2bcAe_IDs?MzAg3U-(TjJ457wQERh^1*< z_oMA4zdk|uw7zWEG`}w|E9iM~%4zVNNA>0V0>5X?D{{9(r7KU%-jJ|U$0E9Z7~$Iy zqI>X_Kyk%E(gJF^&V15ITSkevAwR~^a}s1jkzRUP`N10wlXEHv_Z)aAN-p3Dr#z&U zNtl>%%=wi#j1pGQXuPlJg-Q0F>i=p;FY1>_$VG)oi7hep35PE(>vCO{R3*V{?jDm7 z_NlCnBSTU!9>4Q4oMPdK5|dR4GGvVNOsRbkJzj+`FYy0`Y19b9%Af^azB32rNTi=j zt<_dsGTn^0EfHtn4Lyc~7zMAzIo^!!2xL27plk$Pp)0dMVANH(Ihp&*DzYw?$EUT5 z?QDlG35;egdvCCpxZ-Pc=NOH!k6Pw<_{WedRlV^@T8{H3eIsmKr?0b=7}nyaE0&er z8D1aFRAf0rxCye;c(u^SxUcRUm!9h@v+T|8X2J(K{Je!KD^wdP(z};$ZT6G~(KTHC zz=mb3dSS(*%ysRYyG^@_T9iQe`2Ab`-tv@s93kqb3q?5s2Z zG2XOzxwIc&;98Y0zWwveF7;buQEElj@9nyc=N>TICU7W5R?@Kt6p@xPqcEc!NBr#(favy7#6^7Mf~fY`oa#2F0Ac2u@m7Bc>np zL22au^t|qy(~~rnF-gt)71HJ?&N+YiI8-#M$@i7Qne5OPrTd-*hYlYbBsgY4VF!{` z&9yf71Ky!@)3N*CA{RS#Lkc+-3#6Oeu7@0UHt-O4T*z&9wY)atg-sY(<-%9?(0aUy zD}lGfkXkCxVtg2JE*C_LqRrLiq)U$jXXWs4$=UT#J){vKI!{rL$q(fZxUEe!^{53& zf}HFHdGs^J-lWZRyC>bzjnNMK`8WmE$eNNm-v-Yt1t2%<%5*3~A1nPt&DolY% zYny#yK9yUD3ozR1cYXE)Q&^o0&*Fw9-az3VZ-_4EkmXCCTjcvX={@!h0ahdw-;&K)6S1TKPeF?{v{;mJaUWPf=>xme&sqy)SjPcOLem zH)&wbx4|5L93qtU^kVF}%(@l0dEE0zV*ylog#5YslhcAAp^Z+96=a6BEq3S(@EQgi zALH?eXtW_~Z4ks7lAF|h=sJC<;x(^FW}MT+^XW!{3v0i z80uh8i}PMgVuT1kxYv=o(-g^w>%GR&ELw3b)TWL?ZeGg6e!BPjfY`>DW?H0ji3Jl? z^TzZCUgxDxvy=jF+iJ`oq=_Z*(i#((Am$TbMl7z}{paxR(vl$*Ms-8=!@e)R1!6fvQIE+S2HmFv zX}GsK=!WNm=sDuDbnAjy-f4s$T!%_CZO$eyv1%@}-cG4tvQ_+sw|4nveVRAt1_!kz zh@aPCw`+hhu+P!Sf!aEEYr1%*$awJOu>zf?wn2Sd2~CPTn8ujU9Px0bIN5bV0ecj0 zi6v@AbA9&9TOn;xiDB2}KDCjz3ohqWH)bPypH4ixy$>ZK3G$7U)7BFPAklE43S}O? znrHG!fERY4L>F}m=Mjde=$)Jk2p;PXjVCHR;r9d_vli?qL2fNdEM5H;;}BU!D81R& zFQ48gt1ymBQggDOu4u-lzS;7?eQoZg`~E4ghW6y87Z7z^4u|(EXr|4<4PqFEd5f{l zbqpvkV_&N0Mcj1wkR|sNRaGp5QnsT!$s1>tE3E&Th$MM+EL!Irn{)*=tVg68(T}Zz zgGoGdymz+Aaz<3VRL5_7M1 zQ5G-oFg0_n?b{yaEC%_~T<_pUWvoQ9v)Jh_L!V_|-}6i6l#f<;v=c8NqGRaz;g$|F zP~p1#vgoZ8X<;Ts+}Fj=`JpKB42tu!Hvza{;IoXaK>WzZ#3E)9aUn3uLY`d4bMh{X zf8rn&ONT?&b^@*CK~PE0&`ZD8b3-_~^YAqTe_pXO_FeiUYOHY8$>=I)KN2RjHFPG; zwJcYBc5FxpZV6SKy3$ubjMsEvSiAlz&Cf7m3F+P`^=fR`x4?4<6v~q&(J&<}Ow5w2 zazyygfdekEB)pP<51IN>M5+e<9!jS@GR zD=v~C(>^11SazRBhPYS83{yt}FBX16jvQ-g8?6#D{TQV`4AFXX8i$3b-sH);7)&l$bPYk`u&*my8k4w^LUvr(w40MY zZ$7tqW1b?&KCsH8tK&`~eej0uwDoUMGFC8*fMS);5o@HbUhb$n>b|lQ02#=y*1tkIh+bBj?6g*Ti7Pa)#MZ zd^pB1A#?gHJ?hxYTFd+V(VrJ)id|DI-y}ym-$A7i$xR3W!ziJ7nQ++a(2#SNw*+m; z6zfvx6>`P73xixEroxMkun$w#b`=YvIJd~paNxH|K5$tX@-^F1&){K6l0zNyUczPe zdNkM4cuGvVkY_sH_xYr!3$AHIV~oQJOy%&>)}{QsB|gL8QfCK#YezAMYtNX)C_O8A zkzT$LnQj0Enm!`*i2GEz*v!KdX^bBzqZ$)n4=H-@_}U0mI8?zcQRlSJp1tp(cva%~ zFh2gVM|?6%o))$5gHCW+vBER>?sIuLEp0v%sUACTRz-G1f^1~u;r(a?5y2OWTY9XL zgBNUK2*o1TYC&Y&|#>Pw7M$AAGRXD zFHDM;>0u~QWiQN;+Jr!XjMCb{1(sV)=-+WPvFKpdHGUq(k-)2+NHNbl zF$}_>oi1w*(MUa0&YRQe4TqzdpM{E7SMc+ud6i=u)9P^n%~W`7PSpgeMLxB*z4_gC zf5D$hu~Gbt?0Fep_kC2HVY+0z>>hetC|&{&cz;6z$C5n3ChBz6<4Pe(FQNnxtEdnJL0QpFl%Mqg@4#!^n z__k^)mYNN(!lDu(jYPOg=0vqE8P>>7zX zp=8P;ou=)YNf)*|+}DdE;sR~5QXoA?vJgiPLwlJeE}sM$Hyt)!y$`JT4O8x$FcW#N zaRWMOe^1>q?ua~dz8MwQsOZ}gnvRYsJiD@!If@~65L>O9(B~Te>SzQBfwhy&#VEcl zw$7qSjaZow8!oQjV!m#@~X!*qGVqRWyZgXSm8I( zH!{)0e(cPA%-^W>#0FFEeF;F<&YM#_z31*KE2bQa!Y!MD;K+R8OZrI6m?h@ruOtGV zujry=MG^{L=cG(0H)0XVUp5$=kOwf|3#?a-ue3SO=g|kZeRWc2>LYtH$-q>a-^*p3 zZLXA1kTk>gd?`H3Txo-@rtrjC6e>k3<#Pfx{ev2ItHhg%Ag?FHoQ#hA4qD;EWMSaO!sHSg};1qZ!-2viGR1x&6)eaad9$AP+($SWum^F zM7O=RiZ_=kCu1o$_z$@@CkPfx5orTdSi5%tpvsc~WVG=OAVY-((a|EJBI|`CT@78m`n-Qt{K*58?65D^8o0z zC|bigx>&Z>7Zga(*(8zJlS);uy6AYnOl>GD)K+>b`Emom=q&5e*2AFN@mb8`;|YWe zWI}Cs&+iAg^|lwVfm3wVgV<#hH`bnOtX4I`t^YbQjw;wQckT|wLB1q@wV{1eppbl{ zwE$WV>LKKUldl=mbGS{r3&aZyo8ms<9XZ8e5V$aJN!$w%fSI%_x!7b{3i?Ca4K=8>giA1Qp|}FzPyIKs zJPP}*f-Yg=^*<<`69%}LBrUX zKF-2}aEt7)8^8olRE8E-U<`mJV4i9A8}2mFnC||3>QG%>1!M7>#L3vpci1UfVA2BnbW4B{>N{=DQF^a=@+0=BTS?$@9U zFav$nr>>yVnih41%DALU)bvHfIxd#Z%YF7*_uQ0v72Qy6J>c~)!Iv=H2dY0fdEBxzQU)ACn)+6GMRjfQBwuqrJ+gx953U*K=b&<_EBFp(#yUk=K>hg@oL9aW9B+501M@&q?dIs^;#>~zb6~M1xh-3EM29g zi^&5L6lzA#2rN}@I`rA)VCASwH7KV77&OsWWa+E#X${A*s40KOH1&^kxUC@%(OiJ6 zwHgza>1*T(&f5^O>`Xfcae2g-$y4N!*&3}Yc~QCB}c zD5++I9!$&TtzTd+y_=E> zKj*7X%5V1qH=>%bqs-OW7}VN?ww?!%WIb1Y_MS{go?EQDw!u@Tfyi>IJ2#s}tEfOx z0q0>9fL|}BQn941%?pO_UsD1Ll;ek@jwCKt!kjTOO~@9h&g99G3?ODnIgQNHab1kU z%}l&zTxpN^EbBbH?Mk2OwkHQ(I$y2gbJCUso3Q12Oa?|Hl`2i7vFHMpKOOKE zoa`!)H{5=`lD-{UkRa&X4G0F4EEQFDKaw;w2t8+7l8R{2G5y}YVgHSbdPkTN;Z%IOwSjTb^A-v#dX)O0 z5yWjjtvS9r!t35$y$|V}0=lhYNC%qsS1evc;R2pYldkPtA8rPZf(7>t6STPRGVe!t zl_r5fpXC})cx(o%mG@EAl|yz|&RTh?4XRJpg&5Z8+RYgPFF)f1rRNh%WoCpr>Nc_5 zMHt{~+@yuKe_aLu!{`yi(UsdSek_khgO}kUiY$n_{#0K78Rz83S>`Nu?kH#AWJtc+ zl37z?_gE>*cJVxS8(9fVdeaLt4*v;b3-S^Us_%kd2)w$wY`?< zxf^49uuy-rL|lmK+q+8AA_4jn7oK8(=DdUibWNzGQ&DBm7I&6io39*O zvwBK;$Xmzy4cGe&q&WYFySkHrOOnp#BB)$V`Qg>Rub{nJ$Z>FMW9HWQfd)r6!vx2t z{QeIB$7Q-iv}twZnthWm)co(gV(sviH^C$O0AI-Li|i&V(|PM)Qr@|0!l1@yPz#fP z;u`SKX>RG-r7y&N@wnaA9eQp&4w?jgS-V62(MsD^f2UH$_B_;yIlDCdU4QouDZUab0pC9N1eV0YE46qouQzxbk=3)*a#L9qbka!MpE@ zhq%w-On|QS6n#!JR@1cf=8xR0B{k2{xwX-raBI`CM?YGtH51CJm9*!yA1D7og-zt z?q!-$aZp}e-@gFh*4tIKX)u&+0*_Z;Rs)OiOvyrUYRUX-oM%iX{vVUQz?5jQwEI+vF$T)L z7z6UAr9gg455((|CG|xYTUQ4Qk&p`fI1^{aq@ZO5>$3|n*Hbm}(-!Gq_a&|k@ij25 zyDYY=*(1Y>7)9W-2Kx+%_yZt`*8t}hik3-KNl{(e zT$?E_HH;aG)O)rLs^Yli0GiWYY9AP3Lg9A;LDS)C&J860OiA|QbYBm4-qKW04`}Qg zfG}viHVC();irt750_)Hd>B3XrU!H*r+_CEGQD@g<$}`TVVGyGqCmrlX zz1rkQD&UZ@*5`Z5?Jf`LC?Nls3OX;@9A{zx@`@((4qz=SfC2{8FX-q1^D%G*9BS+|Q5T29exYms#+y0}{AS%t@0|%qRUfoteAuqw$WR86?niSuR_6^+-dWrt*{NZ>26_tLWB6g7z9GQK0HDCQCA z1lVxvICINDibWg)H=eb4jN^r9KBvWTd-5gza`}@$mESOWp}=R1n$6zr@|9%{U9-c0 zs6uun^l5mcYs3W4#XQ+Hb=u)pGtr;{Twr3QXFU%B(S_~<{uWz=RW~}z{5Ud&5pf!N z21JI<^Y074!(F7g2-t*1xJkEmLmk{bnHJ(>#qPu*eT{eN#Sy552J#aZ&tu=nyzqR8 z(*y*f7HDb2?^Y*dUzcK9BNqRBzk?2{u8v5{G!GV8yQ z2X~l+2#7&g@OCed2b}?HAorvThdgIePDo^|xbU(=n2E2_C3rYkFN~QN*x(I}x7!JiAgN43#v};e_m6bK8 zSX@@oNZ@^gfk1&k-+&THJ`N<@hIpm9wX)1N9yc=WUVaE7yQbdnXsR%L+)y?&p546E z-q|V#iuGT{(d~emBnE`4a>J%CYtL$nj1ee$DUNk>7jXC$yw1;@&NvL_bO$4==v#tz z8g+Y#o!E<^okX_!Nw>?15>{}=*X!6p;@%`?uEv z*u&TN_XBo>xo#iwI2ev;zn(2n@SPJB;TTixtiO4~1i3S}@W9ArLAJj7pk&KB9NNC! zw};eUL==mN@-qDCWgQws^vwmM*)s4*p8{w(@zB-)BCiV&CUpCU-ydT8+5=E4k3x4s z^%u_}f)M*{33aFF{N@b>qc3lgfstKEKjSq(+J2lO1s6RXcMsTUu_THM&&y<}SlU0l z8+4_7P}52xcZ2M^$P`zh>=i7KIQpucEX#end>5^4O9j&>lHWJ;3DT`Iv&*1y*jK)2 zZQ0_|GA{P{_Ivm9R}bNA8MH+ifqFlh&e-ThKY=3ell-TvbuaZ&zI2|a-o-dD5U+su zkmnGGis5QHgb0%|VU|jaND3+X&hDLHRcm}ykEju1V0(U1*T_2`lFF%s{u;w^nDdYX z1ve(n+khjcqemK4!>Cm}zdedR9fg8sjC%_IxAS-3z{G*mIxN`!Z63QS?9NUIQcn&G zTeTQ0qDRr>#jjOAWkyMzLF-FsEJWMzfxJjPJJoIVr*wKL?Cz>O-|mQq>WMTu zOoD@-GV@HL$Tf@-sT40_L1c8{&6UT@K+&B9^wm7TY#_3I7eRN?ib_A$ zG>;)!JCa0r_T~OOP<>tj72SC=yZO<2sF!d2?rBgEXvdA(u`_~VEo-R~m$egOQbMJ? z`~E{dW<2m1h>Dk*QHk@gM|nSZq9>3n;^S}n14Z9{5y=jMR5LH$uDgDlLJcS~CjBLj z+&5Y%oJYf?CqbmN=zlCGw}427oCw<^7`gE1~VI&JA+0Kk&> zs+yKHr+uDt13NemsPk%6yehktXuuUq5TO{JeQ>~I3>1Bw)h(pvB}gdigb2-PrAt@W zb~(*C3(dj51ta=$Tz z@=O8R47W+=S^n`j>$rVd0AK4>&+D_(;0Nja6hKEUkzpHO7qcPw$s7q7mwgUCcqb=n z%bU)9E^w<2K56Vr?vzCM*n!%QxM1}|Y+nIXweo*_;A!&mHimp}CAHdG4hLwN@`D;+ z3Ot~_D;Ttp+jipexSzNU;4G_6Z#f7S@(38MuLEjB(sBHeL2ZV@mDHTp^V4NXB)zAm z*!v;=TaCiOcA(;?V?YqE|9JTl)@I0Uu`jJ0q&w!AdI8fN70Yj*MraAr@>~cRu>;D` z_2NqQB;Yw@Da-TKU>Aa-4y$w5voDdjpfM13p3n zkW}J68!k zQcXDuw*~cstqH&qbc#RRGoJJ!l+WnUc7AgZpWFm0wokguRy2|NM>CU@%dr$z`|%v3 zcySz;yc*O-+3fTw)90}S08lZWX~~)M1+JKNxa*7{7VuCqcYxxuOIIYYOI)oBKG|26 z0dcuE?t3H+k2L_{eUQ)u$-1$C#cCzPoEfFpY$9`Ro09x7h%X9ssHY3az(9E8O9bI% zqDs8EuR{l3T&#u&PVzFBj!}ydq^rQ?3jzFFI7$f95qmL3 zhS0_r)NoR}ptqog{#J;&HL3Z72xY{qR z2ZHtdM2}IRhWubvRWc`+n5Y5)ZG(K-`paAC9#CjZf5pxwIolPJ)GtZV2LeT_sc=v~ zAqa5^#~$5AzEV9P(n?DOr*;>qesh zeqQzmse(frK~24JY7|x~F$ur>|NpN_U2gyz1rWI(5X|wuX*PP`_&3 zuh!WFO&Cn$2&OCxJMV2(15d%(pyzd03vO156A zSRAp3{2bQd!Cv0sk)vQw4Xb*!J)585T z?xot&LsWPPldj@2S#@wqT!l(*>>(PWFYh(XZP#Y5iFw@7T+g@|+UX^D%L_@Bg(!fw zdW}IDymtS@#ogpE`JM=zJSHZdoe-u?UyV=c7Rj8~S^1=2+G5ajSbz)b*cjl<*?Nou zz$TwDt>8Lk8jKrDvE?9n(h=^?mwxPN(iC9@SP0MAidab*8cJ`JbolMieNO;U)`KWK zgQ5iHO+!d$st7i4oT5KFXF~a4R@3sLov6)Q;k7|%Qwx1^XF73U{ENG&DvEaoc569r zHX@R|DqSt6K(PEMK^()2?V6ae1#hO7y>g6a#J2`K-4wwaMc)mp!j+*IiE2O(4}%{V z0(^s0Ct?^?K!|(|;OI1$7wyUi2o+J*(Wza%5^8bkZD=3&#;u(G{x~v4@A1G zpD(0u2k8sy4u~*00_|51W35!;^k18&i(<9swphCy$y7ye+$KZ}yklOg%(ptq3gj5? z8fT#(-w6*!jcmo$j3|elovlAXVEHN2=X>U*hy%1%De1ZVB`ny`wFu7G=k+MbNx>`m zul7kb3Lno{;Umo-aQe&)!EZN*H)Z|eV%iy%s}VEJQdX5CGU;)8mr&Z_UjjaBkF##FJQNgsO1M9Mq3WPTD`|vxAcs4u-7w-@Ox`Q< zTB#)jwN#tlBH%FFc1`w!hE(*b9Mo3*{z~=+V#rlLSFv_jg3Ktko~f2HEsm?oz^26N zB){Euj}AyKo5Q?cApdRL#i>M>T2hj@bWhYsMq7OZeQ{0;S$8rOI_uo7RoX$@HtpVv z_?FUxRA#k-$KRJYS{H*58z&9%`OGB1?zHk;lde`(En5|PhmU}RoZljoyu6ISw4Z{fZj)E(WJiix z^T|jjkPQ?HTE!&rHg#3-*InU3@m6cLO8Q+4&l4HiXi!Ddd$(um26iznykj}al`JNy z3D`xc$SNP7XQ|R5IKFy%)F&fd*93lOb z)`I7EzST;?AjYg`O4bA?!Vw*SwHTc_4|X2pgSJjPG0)pz&m8MulXzi zdQ&Rr<#CrNk_hk3K^>wdM_r`;3@F5TP8}2)M8&YfZ2om;&HMe<-Ttz zJoF|MDod=t)urf_RRU|kB3aw2xIy~>E_ll{LXiC*>NDIO20ML43Jc!Q=4GNn*D?VR z!ksZdsX}0x`B0?_EN3N!;QKqNt-6dsm(gE~>{RhVAUV4(PU={dr7|kh{s1itkr?b( zglao2b@Y^W#;pX1Ij*>Z?KivHz1@RGAidXzHTzWPKp1;cmzJgCWb!2JTSh+$GM8sD zl$Z#RxCFu|?mWltO1|)*J?PsVO~Ccn#Z!Dz{BC=RI=x`t_BG!zAOXwFM)amyT_cU4 zZv#jAitKKu9s6{lbi3S@!JM!5KCqI0tIOj6u2|lqiEk2@B?yfclKncN0jhX)AfFn7 zh9d0%H<{bXEW0{N4sTg5JS5cu2hW8AwS~?m_mbRvi;tZlmdrSUbUMtiJF&=!+hxrS zvKp?S<>&=s0ZI4+;*HRKjEO}bM1OJCdmpP-RUI$|Bp_FN()4arap5_=zE{?AG z#%LboK8(w0j|SNQG%ZSO^c^Kw1$`oSSMIf}$iVnmVrm(fS|!kYTv^56H+9AK-PL-z zAM>lw@=MqW4{)OjAaEQhS_BD!ilmwv@b8T~{{1-&Jeq_ipHT`cz;-ggSz-ijk4_O> z^S<06AQM8Vzf)JcTM@K$E6R6I$bqgh6L6WKSh*j4vD7Ksx$s+cWp=goK^#^NcVtA~ zR{{(KOb+NYjVSY1N(@yQTS!Slca=nh5BuX<$xj0Kt$9bSi9rJ;7wsyLg9s1~`7u#( zsQ9=G=3kKnCjKb-;{?uIxe&+ilLnP0z3Cz?z_XzMBI9xOi#Xcxjz|rlu#HzNry7m zN;d1k#_oYx`)9;J3;eg?%T%(CTFPOUO#(bW#-)i%J&#*37$wpbmFjiqHE7K}U8rRy z7Cy6v4NHCC7apinBb%DnVRjocOe2?kX$UD>D&gXz6SmHj=B&2u5yR1!%b#8= zRp6{x0_7|2A(Ij4I+9?w_1qd(x7rC&tX9Z7CFVZTzWmsXb#&@Unv>Fc_ymvb!TD=1 zv!V`30c4^ER@Q6^t_rwBWpa6)%Io=4i3gYfVe#g?-WG7PZOXf(v`t09|2|Y3s^~G^ zGL32*@6e&D32L-=D+@{I?H((?y8gOZQ_%l*Go*r7=_e$)CYHqdwI`ATbhcZ2B1bS> z-Ks7g{03z*yUlT^;!}kJLR3DViz}g zgVHg%(VhVzo4H ze%)W{DGrt5Tj=@j@qB;2ZN&MV!2_`Iwu{{L%O${)h`X2|&~=Z?*Z`19f>SeVPe2Vo z`d+xjtjO+UQQOL0sSS z5PlxCn-Wt4s;2F}etOpPK-6H$)Szrpyl1f1am z2jnM`DbVc7ts&NE1WX3j9?&~Y1=JqC*n0pE;JVTBa-u!OP>wk_a$#j?Xbs|DsE=+n zFlAq0t7^$*L$r*aCR6|QwFVoNl#AzIGeT)Tj4x~A1$nL@&XXi~X>TG+*~;~i=CtDJ zo25>RV3+&p29TW^;0t~U?E}J+Z#n7o@u6KcLYg=8&XuprGv)vnc)kHTN?gJpzJk1= z)0;&5*jQ+rDhyNB)lK|xS4#5sCM50S9Cg>Ko9%IpT&NVaA)RxSE=_7B9|IdJ!$KJ= zxPd8pQ6~Av2m*2)#3srQnQG){&I#h_=e1*T_+VD_iH;QTrnY-hoB|d^C6>^x`=O(U zUxS^tW)MD7Vv(uwbQbT+Ds!Al8lx+;^|+>Fe)DQXM~t0to$LzISh`i^@05(pRh*3Uf-eG&`&`TBx#cd z3`ydyZn%|^%T&KE-V-^}^M|kthl9ZII49jJ8MdS+0O5&Uj7&P{gnkey4-1hFQ%5PN z>+Arty{qlHvev6npcy(NK`9Nm(5B`!P*|9Nt}+YaHE3;pT6#MtO)CURbfoz4eBR`? zZ_+kCIuN_Ey}R{6I20~(%z(Era3-*-A*H|ROmxlzu;Of7#tMQjgur+O>K%$)7$#%* z!9G_l_M-%MHSN0eSx9Ls@xq{vbu6b*cq!<$N(~dSBME#80i4rAAjgCRzt&+Pt0;53 za#631=0YU=61)E9xbPOkIne-C6BklnA?YEe;a_)o**y`Vj7=htpY4yB$ZwgeaXsn$ z7WRD;Zq|u~z_N%oKD< z^B~ZSZ*8#C6ZHC8X6J{7#MePj+&T~Z%tXw?DE=p66eh=wFbyNvNCNI zd=Aj*^=dDJB?dqq%lT+=l9fR_(7YI)7_qb^no%4crOc(%C1c&`FFshjRuEdPj_;`L zG6x&E^)Jil8Xd|6yVVt-{p3@Clg8qxm{~`rhaafNfT{rb{eV;mxoKaqeLm=tzIP_h z+=th7L+SoNXw}lwqM3DfI?!zJ>TB%Hb4NLN5>gwn8a!pKnEP!{omw+f65F(OZcz3v~xw-U|YWbP|xzc^i6aL*WxV+54 z*D)N1IG2ermU@pIJNxqBkrSgr3ukNzTgLOvq;j=}er5RcS1!xmn+1?TVS?xVZu4YZQPGW6T!K+*yv_JK^h0F?N4nn(9}| zzRGP%In|*c+22SSeSZ-V3@$w~BMv^N66OSl_y7;?8z8)0nD%qS&$3Dde6s3i;bPU= zdmfDee4Wpy6~y6-rJ`MM*4OXM)3ez{y}#pk3QRS&_?~B~dE>jzZrg#|!Fz|pS85LU zvD*?nxrIl^M#ufvy1V2bpstM|yupXyGIn3#3W%xtSo==+Ys zPhXB8as^iA8#ti(Y}p#0ec1)PuBNj-s5SoA9*;jo6&$tWYp1SB|9;W=G=KdXe(bu0 zT1Umv!KgY?*yG&{8Ay%Ri2~>{7$+=$O0D{HuqR0x0Q>?{nSB20cY)XI;UoBHTM^zS z0h8-1gaiQlmwHCqF7_MY8OFm5N-_HD30u3=hH(gsWL3<8>(7Y1k}q1Q=P?3}P9j0!Z5MnegA9 zb(R$`yMjZhtLkS@FL;1YMQtV@G{om32n=lUvriYjxuDMZkx4GX!WC{!`+iL^-s%*! zH+kKT;;(nRy0_N+Gnt>*uZ5~DpFTl2(l?hoEg8#Arkdvp;DLT=+kztkp9o>Wp3`am19EayA#GK zRevy7bxoaBe;Y}$3X?4SftQBxXZcDZ)OLuRu!KbB-ur9H{zdnuuJteE?U&`OFdap~ z56yMrf-GJuB(@!*u*^N;zm^43+bSC(Od5AP_#BdZt(NDLB;ENwD z2yi+-@9!|U`PrqXZzDhTqyVC^o*8DIRJ*mk)1oLKQ<`sjuuEWEM9jHF6B zt3~s_i?qFUhU2=IxXE1I0JR0pt}5!+0H$9rxm`;w8YGTpZPPMR3-&cVe^3wswF*KNmnu{5 z&(RE*_vu{rVY8hdtA1b)?gye6oC1&*$H%J87=C;*y4JXA;`Lk4wl~9HvY3W-6`%-u z(>yr7vk~fxmtZ)cMxvwEY;kUp(lT>=yD^=b3`sx(w&i&Lp0D!k>uiK$+GTliPB?R2 z24Q4u>i2!CQcR0FW}3;{cg- zzj^Tm{h6A?)PS%J=tA8Qmw#|ePWw?T0q^nA_a3n^Cwa-4FySR&9|@3LJVcUmXp@z} z(hUyYetdh&|4S9-?qk_YfwUXVu?l@*S=&2ITGazLjcCnOAe7%*3D>&^ZXinQZu%#$ z_akL>qX};vb_gF2HB3V(dlp4K4ZbU((gs}!<`IOgrZA={HvVF(_CA6@N`_KLBRP1A zD=t{{&c|ca=Y8)a37Glry~S~GFhP!My3dSu2<5GCLX%ySkXr=BQw4Co-?UIX25wj_ z+buHZU;aUozk1|vGiERilQ$d&o}o3rP*hhcNlm`C39U!*lE>ZBTU8jc8bj;z0jBBp zKQ2QZ{tJP+K0Q+9?UTZ;a{LB3NfHf2xLl7W*U|gA5=9KtY>+C4?J=5q?mL8w3_2Y_ zcPpLm5}Sf!N{v|i;{;Nb!s)8RUhw=Oo3bQKWAsbzI0SdC%@ei<7rM_}yq97Jom&by zS1fm(#TZl!$ztGekk`32?nkf$ZbYRJHxmOKLKfh4;lPMZ8hT?WypY~Roa%Mu-c+y$ zkkaj186UqxuH<0;3|dz+TEFW;SNvE^L_46Jcc<jTtzWxy4I$4Ngfz=~*o9KDg0DguEDqT*^L<_ixj+o%m7Fuan-Ur;M zeYE@rwD1+s&E$V4Ba3$Mu`2uiq{)mz#!SB4etye&it-9KIXkc&uOJ3ddfO>f-X5x! z3j@3QRpE`shM%_R`H59qzGSP%`PZCjJn!Zcl*>Exn8FBu)%!Noa>N6c*BJ0a8-Kb5 z%gH2gkYT{7tZbmjbn?Ikbi|K*BYt9Ku{x2nJP7_-8}LA^`c#SA_w2YDmDo?@OTon6 zO1}Bfk0*+6Dm z4C0qAx20ivba^oOCWk!(z5ug1c8sW-f2JzY9PJPm9T?v7>3LSPdP@L^6J8MK;58629 zy)!U7KcB}Drm&Ct0mS~g>aO87$(j0%%MZUzncQOn0>BF0=880gDxbT5u}l56MX1jZ z_BK(rYjglt#BZADEui2bu4_x97vG(0D(Vq+NdaUDF`aX}SDQt3pUIqX?T)-?(wnWx zCr=PpqwyqdCeh%qk18AadW7+NNMhn(Bg@AC&%o&Ke1L+RApBv z>Qx#qo*+vPF1%G<3ST_AQorW_Fmvw9pS8g!ZWErDa%7)rd_GwF*paKnm+cgpamr;E zXWs}5L6+cOsN&ygWWRah6UD|e)qhRK zg|u0Ry6{Oa8EM-?8HO?(QvZMpK&A_QRsTc zXLcdsBtN|)_ga^wu$GWvFtVD!4Em>V{TNO{%PzIv+Esdvu^`@ZkAi{T6@>ul!?gk5H3tsB zqasn76Hdq16dvXNOmpfK-SiJl6&P5}35Qv1O)fr_cDVFum)goYd+>ubrzXvwlCW=w zL-W?pgpQn|S_-^F00B7eP~f<}9KSHOYx*o1tu%@3=iI`7y^))Hb)8{Wk}Idd{G=5j+wBdpZk(S)?E{yp# zdMldw3p;(-vdDe{RG=#x?eRcQXshh{zba_6ZG7_tLnHwardckq8SZc{c0`x1-iqgW z9WWL4*~zMd*v^Gie&=aMO|h0i_3JRHOOAe9)w}EAa!;Ii!wJejhvI|{p|@!SUOVco zV|XiB1DyT09@#8AMOOmKgGb96l_%%$T~#4Qxg;Cpsk+l- zCf~nOxtuKYz~SX{7rI@Zdj~Tcp=9@-Jnv{c;hA!`d01cc^EK$OlmRc0<+m{2YvpZ)oBKfE{m)s5d!qhgRx z9NDc6gf;tRMk;+~lZ{@C^c5VrAng-dQ^ik}n)tG0#dC$uZRt?NvY& zsw2ZR3^RKy2}*v?dOk+0Px#%2$81oBsH(Au(POs))Iqyz!ETKEH8o zqOb}V;u2D2+-lLIJTHM6X*E}Hxz_xyiAi>a*m34tZ>D|q+{+6&?lwidE(f8Kl9pd& zg}XWTA?LH{;tZ`xTiT2 zC_teR74r3B$AgDBn#+b8AT5%NV@lwkHSgS6Nc%}*&hd*^eE8klz@M*3Pt3wku`;qR z*DHbAqKLn`uV;+`@Ru*|%Dp>r<{S zVtp5TaNCuYh_>*TZpW^E!~-lfbj^|{qw2GnhL%cH0*P$r*l8i^>XeaRS zTTfnMQF)51r(CRc!_Y#Ubc5Z{=7YPR43XQWtkg-ziK7VU8qFo%)-mwYU7Sm;py$yOr!a3n?shPSI3mF9T8^>D& zL!vy9{`2il2rrGA_Nf@nPk}U2sg~j3ZX%fl#UaGlYfvVGTBWbaFu?CT!JYu@8brTAF&i>*cK@^ zCiRS;7iBe1=&XjicjZe%!7z(l3$0hIy8jhf+vKJ+nX1b?NF`KCj-%Gm=;>4 zZAN=ltfKJ!ktZ!p+z;^g^4Znz0QB{UG>tIAhPPptI`%2Z3|)=Lv@D3g>bL|%CfzS@ z+2kkZJqvUn%wl{r+`ut(^%$CLwFC3(q%8{yf2rimXyf9eahD+pN8_9zHZ`B$q}?cU z0n-m=%O*ox%EOyYgOTQ*OOI!Uqo-=|=+|xsyn?UrBt<$SZa>I8%VXaSOP=;Nv(MXw zGwu(vty-xZ*#G|g#>|+X#}+=l$N%L+5P@YIIqCQb9CB|*1Q_nxuUrNQ3^sF#+*yE#CY;U zvFHsi6dB$V!W-{`W_p%@eb~GFTva{aHd+ZgcbWKgMOefLI-)p+E7i5}Ju0V%iIvAM zWi_CT>>9&#f(1Ttqc}>xZaimP;~K->m}{FG6l3F8>OAG1V=O#y!xWF3&WcqDs$mr+ zpAnI=C7+pog(Jn2%3M;mB)uj=H7KKY13#jI4CB{-GO)Rfs$^lKiB#qxzo2vMRC+2I zX47b%0`unCQsmvUh5ALQNH;2_q3v?Y_h7a!UKC|y;OR|FR3Bq6w%i|X*YqfMe*!AG;9KQ3mLSuv9|sobVVhPvHM_^O zim#ljWjz{2eQ*Apeq5kIV|`y8!P_cHt=0iQ)OsVDkW#%hFO#0bH<;UpTgs^+&sQ45 zZiC+7V||@`waq49pFkxyHeoZO1~jm3!70sa^6H_t<{? z39;u7Sz9wFoBqUPP=Ocq<^-u|H}QxEj;s>|*cdc}~y^U}%mPHMIZiI^8B91Ax@E?qnHB&AREXz!#r=4wCB4DZWD<=0=GV2$AvP#1GaXZ6O z=1YX+ih+F4VHrls^_d!{7?pFK9Q%E7U`HFDDu8;hqr98G7cpYC6#t}eFePy#UTn$} z@3~0||A;aAZO@GzGmTqA=x{E+AwvVH_!(5^Z|xV3{B(PhgiY)v&Cqfx(H^MjnmsL5 z>{r5qPYtbEUx(Tk7^*L;had9zyqn+Y7Zdb0cYOEsMTV`M>_jy4)dtotJOKp zHpP11C(^?SHJUh8*q`ts)Eah_^nXvmmZe{g*Qr)EC^B*h>HBDc>vF_qpu#-$y zUw~uAGtouuHct-R#BJ~64wOHlQgps^ka+6|forACge`-8uqnfhfgHlDI9UrcpGxzY z(^PbF+-r>`(c&s*SMK+*+kUMmq)=yPX#a(cew*O>8+Mv$TZ5sQ>(*+bCDo=CYu9NM zCM)!w>GA1=5s!}2rt{CPoK}rxzIWL=nnb>6g^jMU+=R9~;b)RvX|SZ0T(b;5d$`;k zig{yusDZ^p_x^XEB1i*!(l7;_=IS4AyW&<|*B-rC6_c{{GpAz1k@;EB6O$z0d-7G^ z)N8KJ6OPNxUe;X%nnvi{V@lS#MbD$x!Ry@^7-Jd*`vMfQp$}=?!HBc(E3DwSRTkZQ zX@4O#+~v|A>oYqkh?w?m~iY? zR#VUOpbIaU%syK{t+UMdO=lQW&dbOh9xM8<`7U4~y3UfY0;Z3Ki!` zl5e8#;p14O&Z5|`^>(n zVkzY#yVZ*i%i`T@M49lj3amcQ4nrRGkB<5uA!4ZMe%Qb>!mMCfRXBC%KfyEQT|1@V zH_Y?;&3Ys!%7vYg)!)h9Ut2?g!#X z<~R%Ow%%X=dzHyv2VK9skIVI?`i@Jm93=aI+rcOhSP`MoKxOF0(QAjt5EfT%fe=AB z<`dEnT>f0RnbGZ7MLYGs8@DVsLH|WoAEp=jP+UF8^!`3VoLK~z5wT{~GRVWqKfSA| z^6C=!Yd?OY3c4YC$bKSo%g$94K=&?l=YQ{tqwXEV5v{_$+5#en9Yrp0b`EqkS*u;% zIprz9L7Y`%hWk4)TuVc97dt`F-mj8@txgCMjS##KG<8h4e#5}-et>W((zrAg-(4G` z`mjy=ngFFjan$F?R=k{i70b^cWAng*VA?B)tP3+MPDb*I?;Xpj5!=Vjl={Q)y$(PE z;C*MqQ8DWP&3~$mh8O&nH|hlr~V4wugJgcfz{J-0W()9^V1h+Q-%1r`W(jV~GQ=g&KeRwWSvGCb>EgfT@cfTg z(roAfdLpE=gxCu(I(@{$JfbxEiez7@l?Yt|yy~coWCliykq7(vpB`*w)V0KYYh}4q z0vr%_5C(EpZCKX9+Dxk?o*I}-?vxN{bGYL}kly|AA3m$dxSU9yLDVi@GBTpOaD_P^=h|2+X3*VJo7 z2|{O1B7`mb(ss@Y$b*IfTCzpJqgmECXy}%K9S{(#Qr&-?M_L_)0C{RD?BY-wqfB!v zrtlN6|LNuL+}Hy!`4V}$Q``1z`}0B&7j|6_{3z#yF#al_MKg?W7?3_6ZwZ$CXod!Xt11-yaie?ubP7bhENEg zPb>f7rFRdF5|)8|5Zy!>(n&%1g}pZDh$XOxh|I>j6|EQJ9*twl!rOg*~n*yIJb<6J#{r~#9a#0iUM$Qr9N;q0;y`0utvbz&u zTa+dIf1KUT=tA;{V|%$b3PO*Wh?C6y0eR@ScNOpj+vwvO&x07huGiD#&;pF39k*+* z6GM&iFSi+y_e5XVSHopM-?&`9dullZStXz!bxPRxGpjuSQr!1R&!ZNk#k5 z;({*7Jos%O>7&7K?37)HNfd!k`}<$428cCc3hA5Pt74mv>1n>yIL6PQeA4O47z_@w zM0lZlhtNYaM|h%MFhFROUSH_8-Y1YNC(na30iw3$2f);`L_w{E6%|J=uh%ZnuH-go zXf*<}7?!aV*WM6qApqnuSbSApeRitaE|v#Pb`lOsaRb(*c(o*C6vW`@ zySb}3MP#m9Upi~}FR=j(-yp{*vb|n8?XL3HVQ1F!hC^egyA@HLXiB z7kUe7xQ-qLc^W(TvP23+RFAx~PSrz90+F@%OIp`h7p`l@v9!jO0LHW6Gl3dtV_5tO zi6&|Lm0OB;PZL^u+wftbLCdFAV*~O40UrdHrRz#{Qpg$`gVI(Yv==T=_b1 zw)ljej27}4eR^NlvG+;-3Z}PVRl2Cvvn&|gRkVWB3m(QNrs>&i3yoI*`|4jRcc07f zzyrZ5)E4AD@}=|7x=&n^@v4Yu;ru6-&}5YGTJuzHs!GCpBA3toB?>Rnrs~BRqtvPw zG-^%ASTY|T7U=7pREk)q`W?lI7@5dvLt9=q8tl(pt&O`=NOlDSoMF!`q9a<5c z@;Joxa^vbzG<>9npI2{y=9q!FQahuU-SlPujc(Io#Xs7LR~<gR9fD+Uz|&@+4LO#-u~w^{N$LCgWgomwbcO8PaJuH) zXRjlVTksTUca$^|9Y$Z|onKrUxcLR%btY%QrVxa{{VV^spm81lm*vh4T3B2jVb|fW z1MxL$f~G(%Fn`A|JAl96UL6}t^sWY^ zhY+55h(snO=;2jPJZW~@E87~f8~s*`)^>lRAO7kf_6#8wvF|biXL$r4sySZY5Y7%z zZ!ew;Ru4|Uq;d2ih@*_&omp;29nyB4z8gk5@UFuW^F})%1af!&uR-JT^`EcOwnT#W z)0CN`K54rdQGs_dylEJER52Ia&+vaeUo30vgRB1AQ^Exq2}lvm*T()T(Bd;e8ienz z9b-7?TaV!*u7Aj=wF(e77(#llOEd=!zITdLhS&q5>uUEG05ui;lNEcL#-!i^KC$$3 zeuLsWK2brk=`aPxnEU+gxDyaM?0Ndv($=whk%co={Uvu>M?ueQ19o@$_x}JJt2|vk za1@x~hPH7~SCk@l72u9fk2oGI0r*yu9P$h6;h zAMA?WZ8>8LMiE_?%frR27a-rcux+9gMq>v2=d5wakWLQu`lGdjPs|3tR(RdwdJRGe z^)``26HcE?aw6Ku2GxwzC6fBHJ#%D9*zgDuqa&>G=Umq+z<+cYrXbu0TfO}9@6ezg zg&NQXI!&=&`)jQTPtp)3dZ;mKO}Gak677$`9o35pIx=c&{_lByv1MBJGqFLTV}3Tp zA;_r_U6NU)C(W*WRmUEy2#@l2WFpHMpGV81hOih2ebO{90T|Px$jFi;aMPO3M_d#T zWD_P_d{dZ&F>lL=ZwG+PRqluf1(5%H{4LghB1Ap};qw6YwS9K%%~D{7j7LDnDoN%^ z{l*JMkW4>9XiJA%KPNvZCEs?P_D6=(+b2N6JaL7sEdPN;GG3s@jHN}mwQ3o zh-l}Bm}WlA#vBe!B*@x}ygfYRY<;MkBcG!ZbE=O!frmXkMn2D?OP8HhzFmm1ck)~| ztxitV_cp0}Jn4Ap@~jC$E$z51(=^+Im95Ij-1n1P326sQTONf2JU?r<-v2zP-rA`8 zu$TBJtqF`1e%zqQ9!6+Ch`2;QS1Uzr<1BsPgX&y_enM^H+E8xY+lY`O5pk*QH>B7|KRY|w%m_Y-YzzFT;~QcJemYW~whrCkUM^|aWC zeHnbet1%x$AebJ*fUcWz8vrI&L25mDf{4}E@AL%VT5mBkEu0_8tP!3u5~82>0=OJO zgEAkE7}(xUyW%wl39_c&0q{1dPD?^f2 zmoa4cS;UoH>)I9I@zb8ityUpQ00(RkinEj^)NR+PgG2o#VoO;Ap_gc2!B=iO^a%E0 z-ND*MI9*_IAHw6Yulb&{0@bU)v^z4c5zNsf>f#}CYGyVn?|r7DDEOo|{bKjUz(ZIC zkprT()j#E`U1aej{5*KlX^PFaXZSc#`ZF)3_{GA=M!*Vf9MLXJl)qwC6CJ*DoZ=qh z&>GCBK>P?Pq@a#WB0L>%J%T}^FG5~npwMVY!3bulEHNlB;=Y5~t^g`<6}YX>-L_YQ z5XXcFUBXX9y?)APQGsF+m?kL&Fw}T0(Z|YUZ7G#4iHenH4v_MC;Et zha56y+=m&JloA*d>{=O8z#xJz#1mNb;s<2g5PkgO%jPLVUJ!hY?Ht8)RXb{3mNJ-ft%(;>_m%H^>QGrj95vi26 ztY>}!IiMn0lpn|4;VUL!$IvA`8zp~{zl*rDIh6YAK4A*hNTr@Z$(o~456w4{Mmv7{ zH*z&uWwGI(c&9ozLuQ5Ver2Za@|=tqOoTFR(jCV$3-{wedy=sQ9i46KZHWC1h3#yp z&bCE^@5vR%Cwoc3uaQxa&XeMi+D!=6QtsT^gRa~jq6tatO(?o~_e;S6CK?@K%evGS zD{qa6&zx6Q$(1NYS;NRlnPB|rS=7yZ2@Ij2cfeD2YEn|Bc|`_fyYR&NX~cXDJm&Wq zVfs<=8CzHTo->=CXeVBRd8g&KfFm=||7NiT5!f5w%+Q!m*K|&}c;jqJPHHz@4U(O6 zAIC>TWO@k-c5ZBAQBGIuYdw2OL;UWzzDLkUVs%fr6*Rw`f!U?lFJQ!Sk4a?xTU*)2 z-M-0ismoZccrKD6#tA;=#2_d1CGs`@xQLJ#GcTMq+eeIXV2?7ELs3I_4;y`4`M1hS zsM9rm+`eaKOZsIFG_S$-9JmKnpwIhZbc0TIGbqt;@zNn|9U%i3O>nkZ9!tl~B9rBpyhihMqITA) zjdq0vm^_={RurqsoQC7t z9t@IX%xq-WbQa*secQDbuFk) zf2!DGp9WO{*f2?FbJ%^jA1TssW$NkiG1&7AUgKn#Oy$T&fD98;VuB=gOXvFQ5C3@B zDz8HNVKDN?E{y5X7{!KqYh92Bz(wQW{PVKwd~C$kUqYweSisVBsbasM|L5l-#U4IN z7Tf!Z?ph2CT22ZWs1B(@@`)6$&5;$@HIFX+R9Z@glq4e6_8i;H0%18ac^P5c&;>F- z_#}8U%MOwVC%D8S5uqi(fD*`M@Ttf5 zzU-B}%!pgB*ERv&dgHc(aJ06(0Sz~}c3BF}WI;}im;9y<8yVB$CTf18fPdeAFame7 zJwswLfg;0gWF4W%pTdN0zjZ$ry=G zLGHGUWJMHeL50SO#{as%4OqtzeS{d?-<4q1WVqqvcM}}78O$S3jk-?4q45nt61Nb| z5!}`NQeFs6T@Zl)#qNEgfm?pE8j`V=6z|>dobLoqu=t{)A3+DI8qCFdd+E~QcBXtS zx3LB?gI)y|v&=9=rce!D*oAV~-d}a#tTx}ymPHZWx^LpWV<~3Vc;YUSl~mdc@N8&1 zlg_k8iun)-&vys{`HH(N_i8_-&ps3$oy_Xtf1U56?m*ZRnT+92+_KSPh-<7x2JI!} zbtqyHzx%g9bm1JWQ$DSuU;D_ulQEPW^DA=$W(5@>1RX?7@)o&i*H!r@K+vgqE&%$h z+?mivbs4X5DSOafx!3*V&)vo|>GRSUM2t}WY&wy~%^6@n%MD*fiLF`$H2LN1Ik#82$?-3{txqd32WH<%p!nBY7Et|ibfa`a_Pp|7xI$#cavM44r2wEQHdR2%O6z_+#Y?Fm> z&8~n=ngD7G;Ozvfvu4V}cV7fG-Bp^PgV}u^uIn%-h243sBUV(@?4_UfP4HtXj-J^( zN;5(6g4pgOIncbC2^ zA}|%2i1{t}WSc2XzX0BIryny+*QJZQXlVXmYvBCaDKL^dm|yO!Q1oT$*(YORh?8ls zVo8ZM?9}L1wHix&7zV%bmi_qaqu+?^X&s_0He5YG|7s5!=@ng+6pRZF7EKRJ)xsz8 zXxzS~2PQskvNn=cxeIxX#tT$U5bGzxl3(^GJ>N31hS`t46YN#~Mz2u*FD^hYrJQsi zDf0?n)f7I3rr%BQH|yVgAZ6$J8K$`%6@U}Pk=gvl$8V1A$b3=uQvRy)~PcuAg{KFBOoi7&1L2_!$dn^j&{DSJkN3h^Tq95g8}m z?#?YfJ&g=hzjfCbT>5U_kL|vZvk9W3(S?AU%yk;=gY39cj@*sEH;BzSu(7oH;v6^d z+5M*hB+ER`Gy7+Y&9JLg*>vWFj?}5*1@g`p-5vxLyh1$x9AJw+hArcroyNAlT6*%q z{yv`FKCL4-Kn7a`lPP$XVzhpDc)|(#QYPO0bLp*k8VxFpqH6(Zu;S_zrQ)TT18kF8 z9&fkSms_lX}{R)>x;pgMN4!|;>$E1ikrUtv2I-QDs40dA)Q}SaPX;;SGNpQ4tldkgT zwVTSCTnMwX3%JwYx+Yp5ze-FLwrX5gAmzA!hF{PvIv+Nz(K$SGzDxdGbyTGE_~CIR zguG$(zI9JOzE&URdxTgkbRIJ~BOp3(ZS99{`wJ`jYizNaJG4j}YnZvw|GTD2np{C? z-|=3f8soZ8G=oU*%Z_3A4Rm;}$+8 zsSXahAZIDT+^ZD72vz8Pbi@<3skT5YMQ9aT=J~#IT_MymrHegxqXedIptg{%g5?M%YiinOn+i?FMGE%3avXMw*Q%ae z`6BRC^~#$lAnq4G^&CrUA@h+VkK%e8^-u)BPDm9MkSW-1ajswMf|}I*4KkC`vSmSs)iw{yUzM1%c$_@)7aWs;<>!7tRef+(i_7@v`f&y4MvM%%d0k4toN> zl{#Kk(eV+{z)q(Vuk7Xds%7i7G`7{wdSWf$66mBUcbrjlH<*-L-yy>Yg#8#$=}j+A z4JK5s(!ToANy9ZODU{k%33?uD!M*Xc6szb60U3S+s8OZ&wAUKuLjBl{uLRZYOcxS3 z@&|q8I&&;;515#CdT*Tk?^X>2o~6HepU=#}eqz_+N4=GC%v06V^P!=nLY67#-)qFU z^ze2k#TT#Quf#f?s&{hr!0x-L`}ba?tdtR)s1q^Y2ueyR_C?aTdKP5>&diRF`0=m4 zJl*TRm&&hGm+G1R{7z@L+wu8@CaEg@H&JGIv@(}2Up*0m^r6~5x=29Mm`nuAiKWd5 zYFRYFqAs^H?-H(i_)&Y{|M?1RMmfa(FlUKokJ?1MERf}$(7-T#Z&0f5rTSwed(4~s z`RE8~ru0Zcb;nQPwk4)SA>#9aTf7?B=*k5}&R)Z%a>7uBsFNT^;vFf|$suh{{R`wq@FlW%yNB8dSSc6 zq|OBw>d${_;#U+6!^6&vlhjluA9t`3i? z#Wk21L=gyU$%5OK*Kmg%GK|-;-!)TV7*DU^ZS_3e78sM>QWUOBXP*oJQiOr*Ri=w) z|78a(5-%GcG;>l=8V|*?B<+hPA${q1*5??`(=;UpsOro?Op~pV@2NdlHst^lst`pj zbr#Qc#w9&=7%8>f6rPk^1sP=7Vs1zk;(5AhJzI}S{b|^iOcN&^{X{1+j<}bG6IT^M zT084#^5X#?{<$I+580M`AKajUsCORcieetDjMBblGIc#zq(Gl7L-37N#+J4&?MrE< z15zT}&+oK+6pYXQxzqZ6@O=<*S1^sG!mX{9@#W=q)<6dH!|=8A*+OwsGEc!C!Y@yX zA%k3V@UJfa?(sYvqDp##k2OnRzAcW{32keB7O%ODkKe_0AoO^hmF$_H0QiThW=NW9zewSx+&!N4mR>n88%=%Vuf2 z`YleanIAq?4yRsS`Wi%M9w|@5VY&02x9v}Wv_?VD#?U;6Wc7pD85B+xW=B(S#y<`} zK%+^oD*d5DM;s9I*CtT(YWi`LOJJFZuT3HJ6ei_A;mQ%x=d|!QZJ~f+@(_3a=1VnD z8MWc5WqhMZDVYQ=1B~c)BWJPYhi3JMjTeLkA#|<35BtK?M=-G?n&aw`wO!Yyl+Q*? zlV=8~<)$@Hqa)OmU2}?otR8o|@qBrjX2r_hmwFx`43!rvG)le?nhE9Qse8&#TYqPp z{A2A)o2w{gXFkV|z5xW0{Bw4V)MOaX5?37!U%{=WIC{)Pof%tlteWPH7+*14iWYuT zGrW@?RhNnhXM-WHOqI2vf!&>fB3z^h{Pt0@c>-U3ynCu}eSn!r^UP*&)1ty}rA z)@Oc3=L6Cmp}KM^>uCDvK(WXT znxywcItoGT5xKI&Oj2EUnVUMTKUm=uFAa#~7pTfe`f(smMihZrAEpek7gXQHQM-6+ z>39)8^MV3-fM!rQ+RLc;8bkE8Fy%wV^|Sr3@gJ&wuks})>vNRbYz}m@xPR zymWGX6o!3#x2}*IrJQ#+-iW>-_HP^}Zl9O{#BzZL)=V3}et0z}x<3xqto19eRKUby zs}VP@NrFFV^(0)`ZIiQK*Y2HG`V`9V`nEgn3~TCaE9+ZgS}N@x0*z-dcjiU%F*Hpk z&)YvxWW?~BQ&RLK-mA>H>VPqsoEjrjBP8+Dv5A!RRu)m+DxVjqo`~+!l~v3c-^;`k*E2mKI_(?Bb-i5kmv}8@M{?VIp>o8TUKNeimPrplK)F!ay>SS)vX`fiv0F(c*ry-`LJNLbwzpt+zzrEJQnM{rf z)aU)qiHiG0$@hG3V>-Iw$BTyi8JEVB5qpdX-8B|WQdw}{a+UTkQ9XP;Q%`y;-l!Io zNAnTgU%X6XgNSS9i1Z1G`KNfv@k63inES|*FTnI#g0^Qfg{X`WP(&R1@vR1&hZiH2 zfNi{vB4SI%E8)5al`-@AQ2_qXQe z?);<2&5}vG1b&b2joz(t75+3C%Z^)b)n%iMKZYNP)k}zA_p&#$wrsf)@KItmTNF?hPSb>INw;`6r6 zpat9-!K$L3iso1IhHP^rr45Q1E{3{h;zZBWD~NIQ`s#^2^YZVUGvUB>SY}_k_78pF zFPy`Tk4NWtosu5fF6HcW1AOf}j!>0n^Tf|N7m;J;N_7)+|JIDEL~Z?1^xl6T!g(-G zX&h+S#lL+K&w@fB(d8)Xx0*VaM(KM`B-Pu4t8lsdNmI`?|KHo$TMj+eHJZ&Trhegn z-nqWdXqoYJuj^%cTA_UDTMqPMmIZbPbT3AInPvV4QsykhKw0yk5b ze9mb{TV~n$a*&Pm=BM8<^XH7cPSn?alsB*+So3U-nyq9$SX2+ENF@j52aApVT}~^n z^X>uG>F-fzOHn+8{E4<~5eToz`anTOyx=A&^OZ54AX0kKH@bQ%E$L2lykTUz@0>FV z#3>sxTV@{LSjg$7=gqdx$4VGSK2qkdTlM{&C-2$I7o0PDW19)UE)fc581{0YNzCCl zj12n$VATr!9Fuoi>$ko8X<<>lYE<|yrkrs~{BWCbz~piV`g+pkHQO+ULEAbEbiGJ=q?g&&lPASPpOYvWuqUJ})G53A@%>L>A{_PQKQxLK!MM!TNR6;j^Ld3PE zEqvqi`!!eAUjX#mFbO0&^~Z2qzu5Z;c29`08m}=bavVBN7d3=!q(=FAAB6}ghjk3Q z9#f8BxNjmW$GjHA`h_O4+RU{lo^&IJk%Biq&mto54Zu~RoCgjl3|OI?U*RG_1ri+1 zafc*15bFJeVBs$LC{}HhJ&k>ut67QXjxz-YlvRQa!H|`7`LQz9n8zy^3vw^g{e67P zYqF^UV=uM$V?JM7OnUSCY=}4(C1>B8p-3qf{B#FQx$fhiO@9cmO3}%u8-!2xL1KR4 z;BCPgcb=SNHd1zXHFO3jFC&6Hzk7W{n}GTV7g0Ax6|0@WQ_5rKjYD9wjW$$cu-H$q ziYa2p#UrFuo?!7tHdJ9yCDK}G$$cVVPI=7;8EbjDzDRI6?#|OY@?_@7B}(=QCKk+Z z3S_xKBqhFj82@*Rd_Fop7XLXu26s;>GAL>h1}oedDA4QNZH2ziP~9B{Y7X4?ZG?mP z^Wu*4?C9rYiGJu%qMfDWM2NhwGjob#K*ux&@MVY`6Sf#R7NA3->#TVs-F1Z;AGaw;~cb9rrO z7DkD2H3H#h+=a9J^#B#v`alg^#18PUqr zB1~qX-$TUfp13X$MrY*_GF@sEY4<|*vcd z>uq+9fwe9w8Okv9cW6000=YqXFj|hJ=}d!CD$QVpy%F(qI{^A-U1#gFL#p8P7;L14 zy#J&!na*r|baz1XhfNR**B~}9AyL{-{i`2+>uGTQ-Bu$15RGfAWz!HI=8D+E+J1ZR zhs@t~tA6*#h0b8MOZ}5g$S5c|=njvix~2S!prpu(+z|22{3H7vZeB#$O<(1-&bi&p zE_R;Uf@;gao0h3aL!9*`;YzqZ+HLICgrZVG=ag9YHvm7W zc%pB4&LKU~5~isa&OH?yJ)^B)2~HN3=I0@=(>l)-UH&#dxys<;GtU1S7=!SVPUG8c z6QwmB_v2EgdV+6)opyPh58c`8`X*a{pEC;OOX>pjWi*$ZLB8@*8GAI;j;3_dG?L1Tq>W_-gAiA2_eeUx!{kO&m zYX^Ikd(FecmQvM1GI%KJ!Cwhi?&+M(HU)yQ8ahIE%dUzVnh@X&dUIakw6Ir5!xym8 z9zF36s1Rr24rBEC)q6j*YHDXE)y(P2NQ4*2!D}QQ%!|8!w4BjXz#?N_T27#lMulNR zvRoqEm)~t*Y8KM_!IE*u=CYnYw$Tt7;u}iMU!m!d^YZo4xW)_i`@+@r)R=0!rS%5> z=Mq-#cKP#xL7_Ig3%Zx)3L|70PS9;2L+URP=Z@PFZ*nRGN}faWFO|6G4PU zHd4ViDf5X5GPB~>_x^FX?ZZc4x2%v!%~EOsv*k9}!EyfBHeM%iJl8r{p!d3u?UK`! ztR!=Z{1x0&yyIhVmHo6H^8y34gt(#twM^Gz9$PE+ZV|&oT*-HyAoG^hHY9*u!Sl9r zzb!7y1q1H;3!`Kx6X%= zGJmQ>__?-9)#LacndYNO1eK>5XdaAvdTevxgbrTzt^GZ1MPg+0MGaMbQix>M?S{D{ z=*7AaM}xuP$33sE-lUoct;$>)*#B+D>LXiR0V_q^fET6 zx9~FPA<9o>tH?f!lO634v#R`ib^}nMdvl1#XX*L+V5RJZHgV>l0$(S6nZ{3PD$cKs z(wO{nG$(T37Mg9Q?i(i|0s<8gm`=#RTpuA2&5u5P7~Tz1^$A0T8J3NgylsfC!?Do0 zFY8dyn&_8W?NYu{l#9BifVaxf{oAt1>g}-{)397sZVnAF3aeZrO38YR4M&V4iHJM* z@qF$$r*)REPU1&;pSVM2)7qE7$+$iN9?c{1m}*ok*Dp&waC53bq8yQwoU7bz&E2;u z$6(jSsA{}C=nyqQY<&fGUh{Wh9H1dCJk_xE-Fm}Njca-`9M^CXBmO~4ChH&I-;j0~ zwY)QZj72s9W}e<~i)7-dFdpD4cY9_U7-X)`08f5*{Fg5;HPKxu$H#48*yvHoL(`&L zGvD1HywMdeKVHdC15-_h>*2kH?rY04tXVYx?WeWV@U=fh;p#d`Ty-4a)Kjx1$2kqk z*)G|DsArCDpS@`8ZwNj7=)D-TQ?pLXIn(E^pTHw9wIA>&p&k9fBWkn{?LePBa9O|3 zX#9F)jUyK8YL1t$D+0G6siqF++bL3zC2V@Jz}W6@z{{H1kinX9z`j!8j%Wyw<(K_3 zz`*om+|q61yyFUI3~fNCvIsEFF?Eg$EY0e~^;-TjX#C~r0wW_r|5R8Mb+c|Kw`c^2 z$-*AAZSQ!xT%jfnhfmSGG*~sZq2i0s*)zQm09Ahv=Zz@M0klPHp-a+9*+=Jw=`laP zgiLby0uk&pN!rt{+U?%V@rO`E7N z-w_Gf$gzWb7wbO$g#UDRBOFxK#7Y^8bG|TGO`h^QJh++o`fIJ(rmw|ZeET;)+%iOX z+?%*G^0**f`FeGv&eYZeeZpFpP;BJ=7VkKn@*`-{JEhAn73WK*!AF*RBjcv|0c}z8 zJ*a>>Yz6LADWci-G`79CIV5^9Aoz=N;;ks_Cl_xcoWF}5?k@5LjcqVgm_f{d6XNr= zY}6}VjJ9{Pe6;Y)ArS`AW0N;|r{m-~pWI)&tgrFZx23nRi!f5VOwe z*?#!!rc@eDBsz&{1w@_lLk>{^2wjiBLdnif8>0&4?Jweocu-=z>uJPBkcM#PvKF>DZgK zxN?$MJpP6pdDrgilZxZaI_&J6a!c;K*E}Ix`0lkgzy}oZ>pQ-Ru(ggk_dYQa!Q$ZT znsG-<8^vKliemb9D`ZBnPy4lPEZE`zk z#nks_9bkz;L6@c=z%+~Y{=F~!0c7xje%x~)2OlAU-r2fy4_YVd zjM!h`L$s_c6Qvs1UV`vE+D)o;EL`07ySd}~dWaRGm!m5kxrplAs@EWCU6OvQ~w zwV9bMs5qWf+RL)h8x+CllXC>G4Rj?RH)jHLZHMJ(U4BZ72I+crftZfU7k2qln zzI;h8AA5IIDAO6RGkWre$y|&*RqT_>H`-Cw${&@&eboBXTJ2bHAxWQbLEoSqP_^w5 zJWC{1mSjXfa4Qe`*FWfLPa?JKWG3HM+FHV<-i?bok zF(MU74xTL&P3HT-;N2<`ZMiE?N;v1)<5lJ;&4PY7=ZBl0wZuwz)`j2zd#x#rnB6YM zQLZ#I-wUxG60Jv{vEr3fmu~;ffBFa?sl|aIDX)G9pD#;|`YY0lelL%Qwckf@_8$U1 zcn%{1s0O zG>ah@mCSc8#Gh_~D$9M+ANlnOf`O4f!T&tR7^Aa5j?j7oLtZ;6K9V-q$-%<`;r$kl zja%Qn*=(;EhcaCBMGGSyzf;R^A^iiXrMvEgjNc(uU!F=)QH`$pa#NOWe5$uEMt;^B zE<~HFHX1yIOtt6n&{kFR3XZ?A@Bb7ZyRmK&yh|~67{;v8CvAqd$x*^5XhzS^RJ(or zLKze)q30-_O?{W#z2ug5BdL#7v@&6DqsjOsBSNx%haZHO$0d@Vgv__jIbB$_ecdkX z)b)oe{F~%Jf2K1Mc);dOfSmbDy0w01{PFrbp+yR^1+BZl78*7;o?0lyKJ=%@Wm#7l z1W-}!ThY@}H*+;M-%0Ar_9QJm9kP2XoH5%3QF4XJ^*;fMD68N+N$5pzqP#e7ab1R@y{CIN{2OiW8~lN4wAO~PAMdvO&u&TZI|&Ft08`HS`X@6*eD<} zT7>~CC7J5E_~GqNEeJZb2mU>XtNgXeNVYTLY@F*r-!|LttsWF55HSK6{20{GqC|I~ zMjdiQs5o^(w`f?SiV?%_9>^NRmqbS=yaN{8NdjVpy0>p{eS6T>H%laeF@3@x?2+FCh2&x`ItTxZgk)Sx zFVAK{GG)T|0Q!xV7zTa$ z-T&i)d1>?nLQ>}8kAG&J$8a?@bsE$FPpfv{KeL|i|Ez5lta`>53$PkY@RlTq{qdJ4HxnQOdMzmbr}{I=XEl=llWLO%*fLdQ3%McOY`vLw6rY zX>pw_Bv(85P6#I?y7+d09|p=la8?F_D~7RYCQw-Zz^Nr)7i zVh7RQ6u}?T?MutP0p#7EHU!sSJbkc~0&d>CK{010IG`3~ue`zbOEH5AP16Y3lqE3T z{e+Ci4W$bnZ$(xES zkT}$q5soQ;uyzHgiL_-T^^1Zz`M558xQ_We=dM#W3c<%0Dq3`tWSdb4G(C+Wm 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 diff --git a/doc/images/tracking_lut.png b/doc/images/tracking_lut.png deleted file mode 100644 index ced9ce56de4238a73a11c117be6c8e5d1c42bdf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42954 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 F;Gq$zXUtkwC<}j=|5hE;q@yus8tG1p>LKdyl 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 diff --git a/doc/images/tracking_sdm.png b/doc/images/tracking_sdm.png deleted file mode 100644 index 1a103d6350268c16373c1669930b9b6030e58637..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38225 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 CQc%SJ diff --git a/doc/index.rst b/doc/index.rst index 0687b70f..81e6516e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -5,4 +5,4 @@ SW PLL DOCUMENTATION .. toctree:: :maxdepth: 2 - sw_pll + rst/sw_pll diff --git a/doc/sw_pll.rst b/doc/rst/sw_pll.rst similarity index 98% rename from doc/sw_pll.rst rename to doc/rst/sw_pll.rst index 6cb7e076..f4ca3056 100644 --- a/doc/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -141,6 +141,20 @@ This document provides a guide to generating the LUT and configuring the availab reach the appropriate compromise of performance and resource usage for your application. +Steps to tune the PI loop +------------------------- + +Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp* and *Ki* can be found in the function `run_sim()`. + +Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1.0) *Ki* term. + + - Decreasing the ref_to_loop_call_rate parameter will cause the control loop to execute more frequently and larger constants will be needed. + - Try tuning *Ki* value until the desired response curve (settling time, overshoot etc.) is achieved in the ``pll_step_response.png`` output. + - *Kp* can normally remain zero, but you may wish to add a small value to improve step response + +.. note:: + After changing the configuration, ensure you delete `fractions.h` otherwise the script will re-use the last calculated values. This is done to speed execution time of the script by avoiding the generation step. + Running the PI simulation and LUT generation script --------------------------------------------------- @@ -222,8 +236,8 @@ Below is a typical report showing what information is summarised:: The following section provides guidance for adjusting the LUT. -How to configure the fractions table ------------------------------------- +How to configure the LUT fractions table +---------------------------------------- The fractions lookup table is a trade-off between PPM range and frequency step size. Frequency step size will affect jitter amplitude as it is the amount that the PLL will change frequency when it needs @@ -238,7 +252,7 @@ discontinuities. If not, try moving the range towards 0.0 or 1.0 where fewer dis be observed. Steps to vary PPM range and frequency step size ------------------------------------------------ +............................................... 1. Ascertain your target PPM range, step size and maximum tolerable table size. Each lookup value is 16b so the total size in bytes is 2 x n. @@ -255,22 +269,9 @@ Steps to vary PPM range and frequency step size Note when the process has completed, please inspect the ``sw_pll_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. -Steps to tune the PI loop -------------------------- - -Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp* and *Ki* can be found in the function `run_sim()`. - -Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1.0) *Ki* term. - - - Decreasing the ref_to_loop_call_rate parameter will cause the control loop to execute more frequently and larger constants will be needed. - - Try tuning *Ki* value until the desired response curve (settling time, overshoot etc.) is achieved in the ``pll_step_response.png`` output. - - *Kp* can normally remain zero, but you may wish to add a small value to improve step response - -.. note:: - After changing the configuration, ensure you delete `fractions.h` otherwise the script will re-use the last calculated values. This is done to speed execution time of the script by avoiding the generation step. Example configurations ----------------------- +...................... A number of example configurations, which demonstrate the effect on PPM, step size etc. of changing various parameters, is provided in the ``sw_pll_sim.py`` file. Search for ``profiles`` and ``profile_choice`` in this file. Change profile choice index to select the different example profiles and run the python file again. @@ -319,7 +320,7 @@ Note that the PLL actually multiplies the input crystal, not the reference input and its associated constants such as how often the PI loop is called. Transferring the results to C ------------------------------ +............................. Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Either consult the ``sw_pll.h`` API file (below) for details or follow one of the examples in the ``/examples`` directory. @@ -339,6 +340,12 @@ To help visualise how these resources work together, please see the below diagra lib_sw_pll API -------------- -.. doxygengroup:: sw_pll_api +.. doxygengroup:: sw_pll_general + :content-only: + +.. doxygengroup:: sw_pll_lut + :content-only: + +.. doxygengroup:: sw_pll_sdm :content-only: diff --git a/doc/settings.json b/doc/settings.json deleted file mode 100644 index 08890775..00000000 --- a/doc/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "title": "XMOS SW PLL", - "project": "SW PLL", - "version": "2.0.0", - "architectures": { - "xcore.ai": ["15.2.x"] - } -} diff --git a/doc/settings.yml b/doc/settings.yml new file mode 100644 index 00000000..dd9ffaba --- /dev/null +++ b/doc/settings.yml @@ -0,0 +1,47 @@ +--- +######################## +# Maximal Setting File # +######################## + +## Quick settings feature reference +# elements ending in _path are resolved from the directory of this settings file +# string elements can be templated into children using {{ELEMENT}} +# any element defined at the top level will be available to all "product/section" unless overridden + + +project: SW_PLL +version: 2.0.0 + +products: + SW_PLL: + documentation: + build_path: ./_build + output_path: ./_out + +documentation: + title: Maximal Demo Project + root_doc: index.rst + exclude_patterns_path: ./exclude-patterns.inc + substitutions_path: ./substitutions.inc + linkcheck_ignore_regex: [".*xmos-broken-link.*"] + doc_dirname: rst + build_path: ./_build + output_path: ./_out + doxygen_dirname: _doxygen + // doctrees_dirname: _doctrees + doctrees_dirname: _doctrees + autosection_label_depth: 3 + latex_toc_depth: 3 + enable_latex_index: false + doxygen_projects: + SW_PLL: + doxyfile_path: ./Doxyfile.inc + doxy_overrides: | + GENERATE_HTML = yes + GENERATE_LATEX = yes + + pdfs: + index: + pdf_title: '{{product}} Programming Guide \newline' + pdf_filename: '{{product}}_progamming_guide_v{{version}}' + diff --git a/doc/substitutions.inc b/doc/substitutions.inc new file mode 100644 index 00000000..879f87b5 --- /dev/null +++ b/doc/substitutions.inc @@ -0,0 +1,3 @@ +.. |trademark| replace:: :sup:`TM` +.. |processor| replace:: xcore.ai + :trim: diff --git a/doc/substitutions.rst-inc b/doc/substitutions.rst-inc deleted file mode 100644 index 565f54db..00000000 --- a/doc/substitutions.rst-inc +++ /dev/null @@ -1,17 +0,0 @@ -.. |full_version_str| replace:: v2.0.0 -.. |tools_version| replace:: 15.2.1 -.. |I2S| replace:: I\ :sup:`2`\ S -.. |I2C| replace:: I\ :sup:`2`\ C -.. |trademark| replace:: :sup:`TM` -.. |xflash manual| replace:: `*xflash Manual*` -.. _xflash manual: https://www.xmos.ai/view/Tools-15-Documentation -.. |XTC tools| replace:: `*XTC Tools*` -.. _XTC tools: https://www.xmos.ai/view/Tools-15-Documentation -.. |XMOS xCORE Programming Guide| replace:: `*XMOS xCORE Programming Guide*` -.. _XMOS xCORE Programming Guide: https://www.xmos.ai/file/tools-user-guide -.. |processor| replace:: xcore.ai -.. |---| unicode:: U+02014 .. em dash -.. |deg| unicode:: U+000B0 .. DEGREE SIGN -.. |mgr| unicode:: U+003BC .. GREEK SMALL LETTER MU -.. |reg| unicode:: U+000AE .. REGISTERED SIGN - :trim: diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index fcfda5be..86d49db8 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -18,7 +18,7 @@ #include "sw_pll_sdm.h" /** - * \addtogroup sw_pll_api sw_pll_general + * \addtogroup sw_pll_general sw_pll_general * * The public API for using the Software PLL. * @{ @@ -74,7 +74,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, /** - * \addtogroup sw_pll_api sw_pll_lut + * \addtogroup sw_pll_lut sw_pll_lut * * The public API for using the Software PLL. * @{ @@ -161,7 +161,7 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl /** - * \addtogroup sw_pll_api sw_pll_sdm + * \addtogroup sw_pll_sdm sw_pll_sdm * * The public API for using the Software PLL. * @{ From 7e7bdca575fa4fd3012c6e53d25d8e9378778693 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 28 Nov 2023 17:53:11 +0000 Subject: [PATCH 085/118] Fix doxy formatting --- lib_sw_pll/api/sw_pll.h | 152 ++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 86d49db8..34d1f69c 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -31,29 +31,29 @@ * This must be called before use of sw_pll_do_control. * Call this passing a pointer to the sw_pll_state_t stuct declared locally. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c Kii Double integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c loop_rate_count How many counts of the call to sw_pll_do_control before control is done. - * Note this is only used by \c sw_pll_do_control. \c sw_pll_do_control_from_error - * calls the control loop every time so this is ignored. - * \param \c pll_ratio Integer ratio between input reference clock and the PLL output. - * Only used by sw_pll_do_control. Don't care otherwise. - * \param \c ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. - * Pass in zero if you are sure the mclk sampling timing is precise. This - * will disable the scaling of the mclk count inside \c sw_pll_do_control. - * Only used by \c sw_pll_do_control. Don't care otherwise. - * \param \c lut_table_base Pointer to the base of the fractional PLL LUT used - * \param \c num_lut_entries Number of entries in the LUT (half sizeof since entries are 16b) - * \param \c app_pll_ctl_reg_val The setting of the app pll control register. - * \param \c app_pll_div_reg_val The setting of the app pll divider register. - * \param \c nominal_lut_idx The index into the LUT which gives the nominal output. Normally - * close to halfway to allow symmetrical range. - * \param \c ppm_range The pre-calculated PPM range. Used to determine the maximum deviation - * of counted mclk before the PLL resets its state. - * Note this is only used by \c sw_pll_do_control. \c sw_pll_do_control_from_error - * calls the control loop every time so this is ignored. + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param loop_rate_count How many counts of the call to sw_pll_do_control before control is done. + * Note this is only used by sw_pll_do_control. sw_pll_do_control_from_error + * calls the control loop every time so this is ignored. + * \param pll_ratio Integer ratio between input reference clock and the PLL output. + * Only used by sw_pll_do_control. Don't care otherwise. + * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. + * Pass in zero if you are sure the mclk sampling timing is precise. This + * will disable the scaling of the mclk count inside sw_pll_do_control. + * Only used by sw_pll_do_control. Don't care otherwise. + * \param lut_table_base Pointer to the base of the fractional PLL LUT used + * \param num_lut_entries Number of entries in the LUT (half sizeof since entries are 16b) + * \param app_pll_ctl_reg_val The setting of the app pll control register. + * \param app_pll_div_reg_val The setting of the app pll divider register. + * \param nominal_lut_idx The index into the LUT which gives the nominal output. Normally + * close to halfway to allow symmetrical range. + * \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation + * of counted mclk before the PLL resets its state. + * Note this is only used by sw_pll_do_control. sw_pll_do_control_from_error + * calls the control loop every time so this is ignored. * */ void sw_pll_init( sw_pll_state_t * const sw_pll, @@ -95,15 +95,15 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, * example to show how this is done. This will help reduce input jitter which, in turn, relates * to output jitter being a PLL. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. - * \param \c ref_pt The 16b port timer ref ount at the time of calling \c sw_pll_do_control. This value - * is ignored when the pll is initialised with a zero \c ref_clk_expected_inc and the - * control loop will assume that \c mclk_pt sample timing is precise. + * \param sw_pll Pointer to the struct to be initialised. + * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. + * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_do_control. This value + * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the + * control loop will assume that mclk_pt sample timing is precise. * - * \returns The lock status of the PLL. Locked or unlocked high/low. Note that - * this value is only updated when the control loop has run. - * The type is \c sw_pll_lock_status_t. + * \returns The lock status of the PLL. Locked or unlocked high/low. Note that + * this value is only updated when the control loop has run. + * The type is sw_pll_lock_status_t. */ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); @@ -115,11 +115,11 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint * When this is called, the control loop will be executed every n times (set by init) and the * application PLL will be adjusted to minimise the error seen on the input error value. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c error 16b signed input error value - * \returns The lock status of the PLL. Locked or unlocked high/low. Note that - * this value is only updated when the control loop is running. - * The type is \c sw_pll_lock_status_t. + * \param sw_pll Pointer to the struct to be initialised. + * \param error 16b signed input error value + * \returns The lock status of the PLL. Locked or unlocked high/low. Note that + * this value is only updated when the control loop is running. + * The type is sw_pll_lock_status_t. */ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); @@ -129,12 +129,12 @@ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, * * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c Kp New Kp in \c sw_pll_15q16_t format. - * \param \c Ki New Ki in \c sw_pll_15q16_t format. - * \param \c Kii New Ki in \c sw_pll_15q16_t format. + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp New Kp in sw_pll_15q16_t format. + * \param Ki New Ki in sw_pll_15q16_t format. + * \param Kii New Ki in sw_pll_15q16_t format. - * \param \c num_lut_entries The number of elements in the sw_pll LUT. + * \param num_lut_entries The number of elements in the sw_pll LUT. */ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) { @@ -173,28 +173,28 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl * This must be called before use of sw_pll_sdm_do_control or sw_pll_sdm_do_control_from_error. * Call this passing a pointer to the sw_pll_state_t stuct declared locally. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c Kp Proportional PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c Ki Integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c Kii Double integral PI constant. Use \c SW_PLL_15Q16() to convert from a float. - * \param \c loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done. - * Note this is only used by \c sw_pll_sdm_do_control. \c sw_pll_sdm_do_control_from_error - * calls the control loop every time so this is ignored. - * \param \c pll_ratio Integer ratio between input reference clock and the PLL output. - * Only used by sw_pll_sdm_do_control. Don't care otherwise. - * \param \c ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. - * Pass in zero if you are sure the mclk sampling timing is precise. This - * will disable the scaling of the mclk count inside \c sw_pll_sdm_do_control. - * Only used by \c sw_pll_sdm_do_control. Don't care otherwise. - * \param \c app_pll_ctl_reg_val The setting of the app pll control register. - * \param \c app_pll_div_reg_val The setting of the app pll divider register. - * \param \c app_pll_frac_reg_val The setting of the app pll fractional register. - * \param \c ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally - * close to halfway to allow symmetrical range. - * \param \c ppm_range The pre-calculated PPM range. Used to determine the maximum deviation - * of counted mclk before the PLL resets its state. Note this is only used - * by \c sw_pll_sdm_do_control. \c sw_pll_sdm_do_control_from_error - * calls the control loop every time so this is ignored. + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param loop_rate_count How many counts of the call to sw_pll_sdm_do_control before control is done. + * Note this is only used by sw_pll_sdm_do_control. sw_pll_sdm_do_control_from_error + * calls the control loop every time so this is ignored. + * \param pll_ratio Integer ratio between input reference clock and the PLL output. + * Only used by sw_pll_sdm_do_control. Don't care otherwise. + * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. + * Pass in zero if you are sure the mclk sampling timing is precise. This + * will disable the scaling of the mclk count inside sw_pll_sdm_do_control. + * Only used by sw_pll_sdm_do_control. Don't care otherwise. + * \param app_pll_ctl_reg_val The setting of the app pll control register. + * \param app_pll_div_reg_val The setting of the app pll divider register. + * \param app_pll_frac_reg_val The setting of the app pll fractional register. + * \param ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally + * close to halfway to allow symmetrical range. + * \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation + * of counted mclk before the PLL resets its state. Note this is only used + * by sw_pll_sdm_do_control. sw_pll_sdm_do_control_from_error + * calls the control loop every time so this is ignored. * */ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, @@ -228,13 +228,13 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, * example to show how this is done. This will help reduce input jitter which, in turn, relates * to output jitter being a PLL. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. - * \param \c ref_pt The 16b port timer ref ount at the time of calling \c sw_pll_do_control. This value - * is ignored when the pll is initialised with a zero \c ref_clk_expected_inc and the - * control loop will assume that \c mclk_pt sample timing is precise. + * \param sw_pll Pointer to the struct to be initialised. + * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. + * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_do_control. This value + * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the + * control loop will assume that mclk_pt sample timing is precise. * - * \returns Whether or not control was executed (controoled by loop_rate_count) + * \returns Whether or not control was executed (controoled by loop_rate_count) */ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); @@ -245,9 +245,9 @@ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt * * Takes the raw error input and applies the PI controller algorithm * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c error 16b signed input error value - * \returns The PI processed error + * \param sw_pll Pointer to the struct to be initialised. + * \param error 16b signed input error value + * \returns The PI processed error */ int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); @@ -259,9 +259,9 @@ int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t * Takes the PI processed error and applies a low pass filter and calaculates the Sigma Delta Modulator * control signal. It also checks the range and sets the PLL lock status if exceeded. * - * \param \c sw_pll Pointer to the struct to be initialised. - * \param \c error 32b signed input error value from PI controller - * \returns The Sigma Delta Modulator Control signal + * \param sw_pll Pointer to the struct to be initialised. + * \param error 32b signed input error value from PI controller + * \returns The Sigma Delta Modulator Control signal */ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); @@ -269,7 +269,7 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro * Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM * is often run in a dedicated thread which could be on a remote tile. * - * \param \c sdm_state Pointer to the struct to be initialised. + * \param sdm_state Pointer to the struct to be initialised. */ void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state); From 54a320d763fc8c231704191cf93a95cffc510001 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 14:13:08 +0000 Subject: [PATCH 086/118] Regroup API in docs + various fixes --- doc/diagram_source/sdm_threads.drawio | 37 +++++++++++ doc/rst/images/sdm_threads.png | Bin 0 -> 20790 bytes doc/rst/sw_pll.rst | 35 ++++++++++- examples/simple_sdm/src/simple_sw_pll_sdm.c | 19 +++--- lib_sw_pll/api/sw_pll.h | 65 ++++++++++---------- lib_sw_pll/src/sw_pll.c | 16 +---- lib_sw_pll/src/sw_pll_common.h | 24 ++++++++ lib_sw_pll/src/sw_pll_sdm.c | 19 +----- lib_sw_pll/src/sw_pll_sdm.h | 61 +++++++++++++----- tools/ci/do-ci-build.sh | 2 +- 10 files changed, 186 insertions(+), 92 deletions(-) create mode 100644 doc/diagram_source/sdm_threads.drawio create mode 100644 doc/rst/images/sdm_threads.png diff --git a/doc/diagram_source/sdm_threads.drawio b/doc/diagram_source/sdm_threads.drawio new file mode 100644 index 00000000..317c58d3 --- /dev/null +++ b/doc/diagram_source/sdm_threads.drawio @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/rst/images/sdm_threads.png b/doc/rst/images/sdm_threads.png new file mode 100644 index 0000000000000000000000000000000000000000..1a584a2bb8516c738a42c4f5a3db1f0300860f62 GIT binary patch literal 20790 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 literal 0 HcmV?d00001 diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index f4ca3056..9b82b35a 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -123,8 +123,8 @@ There are trade-offs between the two types of DCO which are summarised in the fo - Moderate - 3 kB - Low - 1 kB * - MIPS Usage - - Low - < 5 - - Fair - ~50 + - Low - ~1 + - High - ~50 * - Lock Range PPM - Moderate - 100-1000 - Wide - 1500-3000 @@ -340,12 +340,43 @@ To help visualise how these resources work together, please see the below diagra lib_sw_pll API -------------- +The Application Programmer Interface (API) for the Software PLL is shown below. It is split into common items needed for both LUT and SDM DCOs and items specific to each type of DCO. + + +WHY DOUBLE INTEGRAL TERM? + +Common API +.......... + +The common API cover initialisation of the entire SW PLL and optional reset of the PI controller only. + .. doxygengroup:: sw_pll_general :content-only: +LUT Based PLL API +................. + +The LUT based API are functions designed to be called from an audio loop. Typically the functions can take up to 210 instruction cycles when control occurs and just a few 10s of cycles when control does not occur. If run at a rate of 48 kHz then it will consume approximately 1 MIPS. + .. doxygengroup:: sw_pll_lut :content-only: +SDM Based PLL API +................. + +All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and it is expected that the user provide the fork (par) and call the SDM in a loop. A typical idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received periodically. The SDM calculation and register write takes 45 instuction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. + +The control part of the SDM SW PLL takes 75 instuction cycles when active and a few 10s of cycles when inactive so you will need to budget around 1 MIPS for this. + +An example of how to implement the threading, timing barrier and non-blocking channel poll can be found in ``examples/simple_sdm/simple_sw_pll_sdm.c``. A thread diagram of how this can look is shown below. + + +.. figure:: ./images/sdm_threads.png + :width: 100% + + Example Thread Diagram of SDM SW PLL + + .. doxygengroup:: sw_pll_sdm :content-only: diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 0216070b..2a512b8d 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -23,7 +23,7 @@ void sdm_task(chanend_t c_sdm_control){ printf("sdm_task\n"); - const uint32_t sdm_interval = 100; + const uint32_t sdm_interval = 100; // 100 * 10ns ticks = 1MHz sw_pll_sdm_state_t sdm_state; sw_pll_init_sigma_delta(&sdm_state); @@ -33,7 +33,7 @@ void sdm_task(chanend_t c_sdm_control){ hwtimer_t tmr = hwtimer_alloc(); int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval; bool running = true; - int32_t ds_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until + int32_t sdm_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until // the first control value has been received. This avoids issues with // channel lockup if two tasks (eg. init and SDM) try to write at the same // time. @@ -48,7 +48,7 @@ void sdm_task(chanend_t c_sdm_control){ { ctrl_update: { - ds_in = chan_in_word(c_sdm_control); + sdm_in = chan_in_word(c_sdm_control); } break; @@ -59,15 +59,18 @@ void sdm_task(chanend_t c_sdm_control){ break; } - if(ds_in){ + if(sdm_in){ + // Wait until the timer value has been reached + // This implements a timing barrier and keeps + // the loop rate constant. hwtimer_wait_until(tmr, trigger_time); - write_frac_reg(this_tile, frac_val); + sw_pll_write_frac_reg(this_tile, frac_val); trigger_time += sdm_interval; - // calc new ds_out and then wait to write - int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); - frac_val = ds_out_to_frac_reg(ds_out); + // calc new sdm_out and then schedule to write + int32_t sdm_out = sw_pll_do_sigma_delta(&sdm_state, sdm_in); + frac_val = sw_pll_sdm_out_to_frac_reg(sdm_out); } } } diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 34d1f69c..d00716a6 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -70,6 +70,37 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, const unsigned nominal_lut_idx, const unsigned ppm_range); +/** + * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. + * + * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. + * + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp New Kp in sw_pll_15q16_t format. + * \param Ki New Ki in sw_pll_15q16_t format. + * \param Kii New Kii in sw_pll_15q16_t format. + * \param num_lut_entries The number of elements in the sw_pll LUT. + */ +static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) +{ + sw_pll->pi_state.Kp = Kp; + sw_pll->pi_state.Ki = Ki; + sw_pll->pi_state.Kii = Kii; + + sw_pll->pi_state.error_accum = 0; + sw_pll->pi_state.error_accum_accum = 0; + if(Ki){ + sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT + }else{ + sw_pll->pi_state.i_windup_limit = 0; + } + if(Kii){ + sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT + }else{ + sw_pll->pi_state.ii_windup_limit = 0; + } +} + /**@}*/ // END: addtogroup sw_pll_general @@ -123,40 +154,6 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint */ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); - -/** - * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. - * - * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. - * - * \param sw_pll Pointer to the struct to be initialised. - * \param Kp New Kp in sw_pll_15q16_t format. - * \param Ki New Ki in sw_pll_15q16_t format. - * \param Kii New Ki in sw_pll_15q16_t format. - - * \param num_lut_entries The number of elements in the sw_pll LUT. - */ -static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) -{ - sw_pll->pi_state.Kp = Kp; - sw_pll->pi_state.Ki = Ki; - sw_pll->pi_state.Kii = Kii; - - sw_pll->pi_state.error_accum = 0; - sw_pll->pi_state.error_accum_accum = 0; - if(Ki){ - sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT - }else{ - sw_pll->pi_state.i_windup_limit = 0; - } - if(Kii){ - sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT - }else{ - sw_pll->pi_state.ii_windup_limit = 0; - } -} - - /**@}*/ // END: addtogroup sw_pll_lut diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index b20f5d83..823018db 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -117,21 +117,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, __attribute__((always_inline)) inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { - sw_pll->pi_state.error_accum += error; // Integral error. - sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; - sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; - - sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error. - sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; - sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; - - // Use long long maths to avoid overflow if ever we had a large error accum term - int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); - int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); - int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum); - - // Convert back to 32b since we are handling LUTs of around a hundred entries - int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS); + int32_t total_error = sw_pll_do_pi_ctrl(sw_pll, error); sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error); write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->lut_state.current_reg_val)); diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index 08b7b8c9..e1714c11 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -81,3 +81,27 @@ typedef struct sw_pll_state_t{ sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO }sw_pll_state_t; + + +// This is the core PI controller code used by both SDM and LUT SW PLLs +__attribute__((always_inline)) +inline int32_t sw_pll_do_pi_ctrl(sw_pll_state_t * const sw_pll, int16_t error) +{ + sw_pll->pi_state.error_accum += error; // Integral error. + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; + + sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error. + sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; + sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum; + + // Use long long maths to avoid overflow if ever we had a large error accum term + int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); + int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); + int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum); + + // Convert back to 32b since we are handling LUTs of around a hundred entries + int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS); + + return total_error; +} \ No newline at end of file diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index 60aba950..e7787355 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -51,23 +51,6 @@ void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state){ } -__attribute__((always_inline)) -int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) -{ - sw_pll->pi_state.error_accum += error; // Integral error. - sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; - sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum; - - // Use long long maths to avoid overflow if ever we had a large error accum term - int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error); - int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum); - - // Convert back to 32b since we are handling LUTs of around a hundred entries - int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS); - - return total_error; -} - __attribute__((always_inline)) int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error) { @@ -122,7 +105,7 @@ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt else { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); - int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff); + int32_t error = sw_pll_do_pi_ctrl(sw_pll, -sw_pll->pfd_state.mclk_diff); sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, error); // Save for next iteration to calc diff diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index 55a677af..69b0c951 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -10,47 +10,80 @@ typedef int tileref_t; +/** + * \addtogroup sw_pll_sdm sw_pll_sdm + * + * The public API for using the Software PLL. + * @{ + */ +/** + * low level sw_pll_do_sigma_delta function that turns a control signal + * into a Sigma Delta Modulated output signal. + * + * + * \param sdm_state Pointer to the SDM state. + * \param sdm_in 32b signed input error value. Note limited range. + * See SW_PLL_SDM_UPPER_LIMIT and SW_PLL_SDM_LOWER_LIMIT. + * \returns Sigma Delta modulated signal. + */ __attribute__((always_inline)) -static inline int32_t do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t ds_in){ +static inline int32_t sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t sdm_in){ // Third order, 9 level output delta sigma. 20 bit unsigned input. - int32_t ds_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; - if (ds_out > 8){ - ds_out = 8; + int32_t sdm_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; + if (sdm_out > 8){ + sdm_out = 8; } - if (ds_out < 0){ - ds_out = 0; + if (sdm_out < 0){ + sdm_out = 0; } - sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (ds_out<<9) - (ds_out<<8); - sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (ds_out<<14); - sdm_state->ds_x1 += ds_in - (ds_out<<17); + sdm_state->ds_x3 += (sdm_state->ds_x2>>5) - (sdm_out<<9) - (sdm_out<<8); + sdm_state->ds_x2 += (sdm_state->ds_x1>>5) - (sdm_out<<14); + sdm_state->ds_x1 += sdm_in - (sdm_out<<17); - return ds_out; + return sdm_out; } +/** + * low level sw_pll_sdm sw_pll_sdm_out_to_frac_reg function that turns + * a sigma delta output signal into a PLL fractional register setting. + * + * \param sdm_out 32b signed input value. + * \returns Fractional register value. + */ __attribute__((always_inline)) -static inline uint32_t ds_out_to_frac_reg(int32_t ds_out){ +static inline uint32_t sw_pll_sdm_out_to_frac_reg(int32_t sdm_out){ // bit 31 is frac enable // bits 15..8 are the f value // bits 7..0 are the p value // Freq - F + (f + 1)/(p + 1) uint32_t frac_val = 0; - if (ds_out == 0){ + if (sdm_out == 0){ frac_val = 0x00000007; // step 0/8 } else{ - frac_val = ((ds_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8 + frac_val = ((sdm_out - 1) << 8) | 0x80000007; // steps 1/8 to 8/8 } return frac_val; } +/** + * low level sw_pll_write_frac_reg function that writes the PLL frational + * register. + * + * \param this_tile The ID of the xcore tile that is doing the write. + * \param frac_val 16b signed input error value + */ __attribute__((always_inline)) -static inline void write_frac_reg(tileref_t this_tile, uint32_t frac_val){ +static inline void sw_pll_write_frac_reg(tileref_t this_tile, uint32_t frac_val){ write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); } +/**@}*/ // END: addtogroup sw_pll_sdm + + extern void sw_pll_app_pll_init(const unsigned tileid, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, diff --git a/tools/ci/do-ci-build.sh b/tools/ci/do-ci-build.sh index fd7079b6..9d59c4e8 100755 --- a/tools/ci/do-ci-build.sh +++ b/tools/ci/do-ci-build.sh @@ -5,4 +5,4 @@ set -ex cmake -B build -DCMAKE_TOOLCHAIN_FILE=modules/fwk_io/xmos_cmake_toolchain/xs3a.cmake -cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple --target simple_sdm --target i2s_slave -j$(nproc) +cmake --build build --target all --target test_app --target test_app_low_level_api --target test_app_sdm_dco --target test_app_sdm_ctrl --target simple_lut --target simple_sdm --target i2s_slave_lut -j$(nproc) From c7bddf50eac7741fc85d60b1c97cb1fb36fa1a8e Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 14:20:48 +0000 Subject: [PATCH 087/118] Add missing h files to git --- examples/i2s_slave_lut/src/fractions.h | 333 +++++++++++++++++++++++++ examples/simple_lut/src/fractions.h | 333 +++++++++++++++++++++++++ examples/simple_sdm/src/fractions.h | 333 +++++++++++++++++++++++++ 3 files changed, 999 insertions(+) create mode 100644 examples/i2s_slave_lut/src/fractions.h create mode 100644 examples/simple_lut/src/fractions.h create mode 100644 examples/simple_sdm/src/fractions.h diff --git a/examples/i2s_slave_lut/src/fractions.h b/examples/i2s_slave_lut/src/fractions.h new file mode 100644 index 00000000..7c6c4a01 --- /dev/null +++ b/examples/i2s_slave_lut/src/fractions.h @@ -0,0 +1,333 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +// Header file listing all fraction options for a max denominator of 80 +// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. +short frac_values_80[327] = { +0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 +0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 +0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 +0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 +0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 +0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 +0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 +0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 +0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 +0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 +0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 +0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 +0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 +0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 +0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 +0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 +0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 +0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 +0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 +0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 +0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 +0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 +0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 +0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 +0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 +0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 +0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 +0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 +0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 +0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 +0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 +0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 +0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 +0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 +0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 +0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 +0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 +0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 +0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 +0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 +0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 +0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 +0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 +0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 +0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 +0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 +0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 +0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 +0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 +0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 +0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 +0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 +0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 +0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 +0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 +0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 +0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 +0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 +0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 +0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 +0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 +0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 +0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 +0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 +0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 +0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 +0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 +0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 +0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 +0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 +0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 +0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 +0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 +0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 +0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 +0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 +0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 +0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 +0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 +0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 +0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 +0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 +0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 +0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 +0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 +0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 +0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 +0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 +0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 +0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 +0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 +0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 +0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 +0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 +0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 +0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 +0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 +0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 +0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 +0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 +0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 +0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 +0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 +0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 +0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 +0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 +0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 +0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 +0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 +0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 +0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 +0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 +0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 +0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 +0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 +0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 +0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 +0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 +0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 +0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 +0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 +0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 +0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 +0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 +0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 +0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 +0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 +0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 +0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 +0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 +0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 +0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 +0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 +0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 +0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 +0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 +0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 +0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 +0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 +0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 +0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 +0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 +0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 +0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 +0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 +0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 +0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 +0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 +0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 +0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 +0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 +0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 +0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 +0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 +0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 +0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 +0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 +0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 +0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 +0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 +0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 +0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 +0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 +0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 +0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 +0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 +0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 +0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 +0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 +0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 +0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 +0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 +0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 +0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 +0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 +0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 +0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 +0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 +0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 +0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 +0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 +0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 +0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 +0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 +0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 +0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 +0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 +0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 +0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 +0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 +0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 +0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 +0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 +0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 +0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 +0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 +0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 +0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 +0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 +0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 +0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 +0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 +0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 +0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 +0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 +0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 +0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 +0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 +0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 +0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 +0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 +0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 +0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 +0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 +0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 +0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 +0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 +0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 +0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 +0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 +0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 +0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 +0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 +0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 +0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 +0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 +0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 +0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 +0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 +0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 +0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 +0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 +0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 +0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 +0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 +0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 +0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 +0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 +0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 +0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 +0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 +0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 +0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 +0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 +0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 +0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 +0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 +0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 +0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 +0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 +0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 +0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 +0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 +0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 +0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 +0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 +0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 +0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 +0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 +0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 +0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 +0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 +0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 +0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 +0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 +0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 +0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 +0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 +0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 +0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 +0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 +0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 +0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 +0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 +0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 +0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 +0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 +0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 +0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 +0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 +0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 +0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 +0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 +0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 +0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 +0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 +0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 +0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 +0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 +0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 +0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 +0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 +0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 +0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 +0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 +0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 +0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 +0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 +0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 +0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 +0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 +0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 +0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 +0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 +0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 +0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 +0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 +0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 +0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 +0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 +0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 +0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 +0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 +0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 +0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 +0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 +0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 +0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 +0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 +0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 +0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 +0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 +0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 +0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 +0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 +0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 +0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 +}; diff --git a/examples/simple_lut/src/fractions.h b/examples/simple_lut/src/fractions.h new file mode 100644 index 00000000..7c6c4a01 --- /dev/null +++ b/examples/simple_lut/src/fractions.h @@ -0,0 +1,333 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +// Header file listing all fraction options for a max denominator of 80 +// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. +short frac_values_80[327] = { +0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 +0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 +0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 +0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 +0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 +0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 +0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 +0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 +0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 +0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 +0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 +0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 +0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 +0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 +0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 +0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 +0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 +0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 +0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 +0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 +0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 +0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 +0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 +0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 +0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 +0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 +0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 +0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 +0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 +0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 +0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 +0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 +0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 +0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 +0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 +0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 +0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 +0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 +0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 +0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 +0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 +0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 +0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 +0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 +0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 +0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 +0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 +0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 +0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 +0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 +0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 +0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 +0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 +0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 +0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 +0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 +0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 +0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 +0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 +0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 +0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 +0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 +0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 +0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 +0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 +0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 +0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 +0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 +0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 +0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 +0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 +0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 +0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 +0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 +0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 +0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 +0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 +0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 +0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 +0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 +0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 +0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 +0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 +0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 +0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 +0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 +0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 +0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 +0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 +0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 +0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 +0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 +0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 +0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 +0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 +0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 +0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 +0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 +0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 +0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 +0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 +0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 +0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 +0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 +0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 +0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 +0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 +0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 +0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 +0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 +0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 +0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 +0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 +0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 +0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 +0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 +0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 +0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 +0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 +0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 +0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 +0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 +0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 +0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 +0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 +0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 +0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 +0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 +0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 +0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 +0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 +0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 +0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 +0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 +0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 +0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 +0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 +0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 +0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 +0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 +0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 +0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 +0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 +0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 +0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 +0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 +0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 +0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 +0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 +0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 +0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 +0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 +0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 +0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 +0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 +0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 +0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 +0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 +0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 +0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 +0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 +0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 +0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 +0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 +0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 +0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 +0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 +0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 +0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 +0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 +0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 +0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 +0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 +0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 +0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 +0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 +0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 +0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 +0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 +0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 +0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 +0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 +0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 +0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 +0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 +0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 +0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 +0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 +0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 +0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 +0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 +0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 +0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 +0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 +0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 +0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 +0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 +0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 +0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 +0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 +0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 +0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 +0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 +0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 +0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 +0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 +0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 +0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 +0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 +0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 +0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 +0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 +0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 +0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 +0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 +0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 +0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 +0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 +0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 +0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 +0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 +0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 +0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 +0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 +0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 +0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 +0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 +0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 +0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 +0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 +0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 +0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 +0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 +0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 +0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 +0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 +0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 +0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 +0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 +0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 +0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 +0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 +0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 +0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 +0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 +0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 +0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 +0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 +0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 +0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 +0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 +0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 +0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 +0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 +0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 +0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 +0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 +0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 +0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 +0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 +0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 +0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 +0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 +0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 +0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 +0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 +0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 +0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 +0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 +0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 +0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 +0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 +0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 +0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 +0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 +0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 +0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 +0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 +0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 +0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 +0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 +0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 +0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 +0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 +0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 +0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 +0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 +0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 +0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 +0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 +0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 +0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 +0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 +0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 +0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 +0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 +0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 +0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 +0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 +0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 +0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 +0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 +0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 +0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 +0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 +0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 +0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 +0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 +0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 +0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 +0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 +0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 +0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 +0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 +0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 +0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 +0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 +0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 +0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 +0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 +0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 +0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 +0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 +0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 +0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 +0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 +0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 +}; diff --git a/examples/simple_sdm/src/fractions.h b/examples/simple_sdm/src/fractions.h new file mode 100644 index 00000000..7c6c4a01 --- /dev/null +++ b/examples/simple_sdm/src/fractions.h @@ -0,0 +1,333 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +// Header file listing all fraction options for a max denominator of 80 +// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. +short frac_values_80[327] = { +0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 +0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 +0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 +0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 +0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 +0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 +0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 +0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 +0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 +0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 +0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 +0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 +0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 +0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 +0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 +0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 +0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 +0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 +0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 +0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 +0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 +0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 +0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 +0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 +0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 +0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 +0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 +0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 +0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 +0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 +0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 +0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 +0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 +0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 +0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 +0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 +0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 +0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 +0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 +0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 +0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 +0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 +0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 +0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 +0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 +0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 +0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 +0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 +0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 +0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 +0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 +0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 +0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 +0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 +0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 +0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 +0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 +0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 +0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 +0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 +0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 +0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 +0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 +0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 +0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 +0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 +0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 +0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 +0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 +0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 +0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 +0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 +0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 +0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 +0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 +0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 +0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 +0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 +0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 +0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 +0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 +0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 +0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 +0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 +0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 +0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 +0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 +0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 +0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 +0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 +0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 +0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 +0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 +0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 +0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 +0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 +0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 +0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 +0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 +0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 +0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 +0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 +0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 +0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 +0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 +0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 +0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 +0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 +0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 +0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 +0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 +0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 +0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 +0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 +0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 +0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 +0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 +0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 +0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 +0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 +0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 +0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 +0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 +0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 +0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 +0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 +0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 +0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 +0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 +0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 +0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 +0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 +0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 +0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 +0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 +0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 +0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 +0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 +0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 +0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 +0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 +0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 +0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 +0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 +0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 +0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 +0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 +0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 +0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 +0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 +0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 +0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 +0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 +0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 +0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 +0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 +0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 +0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 +0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 +0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 +0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 +0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 +0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 +0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 +0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 +0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 +0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 +0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 +0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 +0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 +0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 +0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 +0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 +0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 +0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 +0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 +0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 +0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 +0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 +0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 +0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 +0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 +0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 +0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 +0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 +0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 +0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 +0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 +0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 +0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 +0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 +0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 +0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 +0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 +0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 +0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 +0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 +0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 +0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 +0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 +0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 +0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 +0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 +0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 +0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 +0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 +0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 +0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 +0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 +0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 +0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 +0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 +0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 +0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 +0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 +0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 +0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 +0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 +0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 +0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 +0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 +0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 +0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 +0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 +0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 +0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 +0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 +0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 +0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 +0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 +0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 +0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 +0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 +0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 +0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 +0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 +0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 +0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 +0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 +0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 +0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 +0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 +0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 +0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 +0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 +0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 +0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 +0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 +0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 +0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 +0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 +0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 +0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 +0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 +0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 +0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 +0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 +0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 +0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 +0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 +0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 +0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 +0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 +0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 +0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 +0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 +0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 +0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 +0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 +0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 +0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 +0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 +0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 +0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 +0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 +0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 +0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 +0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 +0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 +0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 +0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 +0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 +0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 +0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 +0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 +0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 +0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 +0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 +0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 +0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 +0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 +0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 +0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 +0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 +0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 +0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 +0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 +0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 +0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 +0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 +0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 +0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 +0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 +0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 +0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 +0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 +0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 +0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 +0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 +0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 +0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 +0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 +0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 +0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 +0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 +0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 +0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 +0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 +0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 +0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 +0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 +0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 +0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 +0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 +0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 +0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 +0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 +}; From 29c1f6cc14d6c99c7f3ce7e1b18dca209d114381 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 14:40:18 +0000 Subject: [PATCH 088/118] Vanilla sw_pll now called sw_pll_lut --- examples/i2s_slave_lut/src/i2s_slave_sw_pll.c | 28 ++--- examples/simple_lut/src/simple_sw_pll.c | 28 ++--- lib_sw_pll/api/sw_pll.h | 109 +++++++++--------- lib_sw_pll/src/sw_pll.c | 32 ++--- tests/test_app/main.c | 30 ++--- tests/test_app_low_level_api/main.c | 30 ++--- tests/test_app_sdm_ctrl/main.c | 2 +- tests/test_app_sdm_dco/main.c | 4 +- 8 files changed, 132 insertions(+), 131 deletions(-) diff --git a/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c b/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c index 62731b2f..a31d8e38 100644 --- a/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c +++ b/examples/i2s_slave_lut/src/i2s_slave_sw_pll.c @@ -102,7 +102,7 @@ static i2s_restart_t i2s_restart_check(void *app_data){ old_mclk_pt = mclk_pt; old_bclk_pt = bclk_pt; - sw_pll_do_control(cb_args->sw_pll, mclk_pt, bclk_pt); + sw_pll_lut_do_control(cb_args->sw_pll, mclk_pt, bclk_pt); if(cb_args->sw_pll->lock_status != cb_args->curr_lock_status){ cb_args->curr_lock_status = cb_args->sw_pll->lock_status; @@ -175,19 +175,19 @@ void sw_pll_test(void){ printf("Initialising SW PLL\n"); sw_pll_state_t sw_pll; - sw_pll_init(&sw_pll, - SW_PLL_15Q16(0.0), - SW_PLL_15Q16(1.0), - SW_PLL_15Q16(0.0), - CONTROL_LOOP_COUNT, - PLL_RATIO, - BCLKS_PER_LRCLK, - frac_values_80, - SW_PLL_NUM_LUT_ENTRIES(frac_values_80), - APP_PLL_CTL_12288, - APP_PLL_DIV_12288, - APP_PLL_NOMINAL_INDEX_12288, - PPM_RANGE); + sw_pll_lut_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), + CONTROL_LOOP_COUNT, + PLL_RATIO, + BCLKS_PER_LRCLK, + frac_values_80, + SW_PLL_NUM_LUT_ENTRIES(frac_values_80), + APP_PLL_CTL_12288, + APP_PLL_DIV_12288, + APP_PLL_NOMINAL_INDEX_12288, + PPM_RANGE); printf("i_windup_limit: %ld\n", sw_pll.pi_state.i_windup_limit); diff --git a/examples/simple_lut/src/simple_sw_pll.c b/examples/simple_lut/src/simple_sw_pll.c index db9b1f4a..b3f19c71 100644 --- a/examples/simple_lut/src/simple_sw_pll.c +++ b/examples/simple_lut/src/simple_sw_pll.c @@ -38,19 +38,19 @@ void sw_pll_test(void){ setup_recovered_ref_clock_output(p_recovered_ref_clk, clk_recovered_ref_clk, p_mclk, PLL_RATIO); sw_pll_state_t sw_pll; - sw_pll_init(&sw_pll, - SW_PLL_15Q16(0.0), - SW_PLL_15Q16(1.0), - SW_PLL_15Q16(0.0), - CONTROL_LOOP_COUNT, - PLL_RATIO, - 0, - frac_values_80, - SW_PLL_NUM_LUT_ENTRIES(frac_values_80), - APP_PLL_CTL_12288, - APP_PLL_DIV_12288, - APP_PLL_NOMINAL_INDEX_12288, - PPM_RANGE); + sw_pll_lut_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(1.0), + SW_PLL_15Q16(0.0), + CONTROL_LOOP_COUNT, + PLL_RATIO, + 0, + frac_values_80, + SW_PLL_NUM_LUT_ENTRIES(frac_values_80), + APP_PLL_CTL_12288, + APP_PLL_DIV_12288, + APP_PLL_NOMINAL_INDEX_12288, + PPM_RANGE); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; @@ -61,7 +61,7 @@ void sw_pll_test(void){ uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. uint32_t t0 = get_reference_time(); - sw_pll_do_control(&sw_pll, mclk_pt, 0); + sw_pll_lut_do_control(&sw_pll, mclk_pt, 0); uint32_t t1 = get_reference_time(); if(t1 - t0 > max_time){ max_time = t1 - t0; diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index d00716a6..32145f00 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -24,52 +24,6 @@ * @{ */ - -/** - * sw_pll initialisation function. - * - * This must be called before use of sw_pll_do_control. - * Call this passing a pointer to the sw_pll_state_t stuct declared locally. - * - * \param sw_pll Pointer to the struct to be initialised. - * \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float. - * \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float. - * \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float. - * \param loop_rate_count How many counts of the call to sw_pll_do_control before control is done. - * Note this is only used by sw_pll_do_control. sw_pll_do_control_from_error - * calls the control loop every time so this is ignored. - * \param pll_ratio Integer ratio between input reference clock and the PLL output. - * Only used by sw_pll_do_control. Don't care otherwise. - * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. - * Pass in zero if you are sure the mclk sampling timing is precise. This - * will disable the scaling of the mclk count inside sw_pll_do_control. - * Only used by sw_pll_do_control. Don't care otherwise. - * \param lut_table_base Pointer to the base of the fractional PLL LUT used - * \param num_lut_entries Number of entries in the LUT (half sizeof since entries are 16b) - * \param app_pll_ctl_reg_val The setting of the app pll control register. - * \param app_pll_div_reg_val The setting of the app pll divider register. - * \param nominal_lut_idx The index into the LUT which gives the nominal output. Normally - * close to halfway to allow symmetrical range. - * \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation - * of counted mclk before the PLL resets its state. - * Note this is only used by sw_pll_do_control. sw_pll_do_control_from_error - * calls the control loop every time so this is ignored. - * - */ -void sw_pll_init( sw_pll_state_t * const sw_pll, - const sw_pll_15q16_t Kp, - const sw_pll_15q16_t Ki, - const sw_pll_15q16_t Kii, - const size_t loop_rate_count, - const size_t pll_ratio, - const uint32_t ref_clk_expected_inc, - const int16_t * const lut_table_base, - const size_t num_lut_entries, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const unsigned nominal_lut_idx, - const unsigned ppm_range); - /** * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. * @@ -112,7 +66,54 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl */ /** - * sw_pll control function. + * sw_lut_pll initialisation function. + * + * This must be called before use of sw_pll_lut_do_control. + * Call this passing a pointer to the sw_pll_state_t stuct declared locally. + * + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp Proportional PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Ki Integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param Kii Double integral PI constant. Use SW_PLL_15Q16() to convert from a float. + * \param loop_rate_count How many counts of the call to sw_pll_lut_do_control before control is done. + * Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error + * calls the control loop every time so this is ignored. + * \param pll_ratio Integer ratio between input reference clock and the PLL output. + * Only used by sw_pll_lut_do_control. Don't care otherwise. + * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_lut_do_control is called. + * Pass in zero if you are sure the mclk sampling timing is precise. This + * will disable the scaling of the mclk count inside sw_pll_lut_do_control. + * Only used by sw_pll_lut_do_control. Don't care otherwise. + * \param lut_table_base Pointer to the base of the fractional PLL LUT used + * \param num_lut_entries Number of entries in the LUT (half sizeof since entries are 16b) + * \param app_pll_ctl_reg_val The setting of the app pll control register. + * \param app_pll_div_reg_val The setting of the app pll divider register. + * \param nominal_lut_idx The index into the LUT which gives the nominal output. Normally + * close to halfway to allow symmetrical range. + * \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation + * of counted mclk before the PLL resets its state. + * Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error + * calls the control loop every time so this is ignored. + * + */ +void sw_pll_lut_init( sw_pll_state_t * const sw_pll, + const sw_pll_15q16_t Kp, + const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const int16_t * const lut_table_base, + const size_t num_lut_entries, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const unsigned nominal_lut_idx, + const unsigned ppm_range); + + + +/** + * sw_pll LUT version control function. * * This must be called periodically for every reference clock transition. * Typically, in an audio system, this would be at the I2S or reference clock input rate. @@ -127,8 +128,8 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl * to output jitter being a PLL. * * \param sw_pll Pointer to the struct to be initialised. - * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. - * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_do_control. This value + * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_lut_do_control. + * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_lut_do_control. This value * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the * control loop will assume that mclk_pt sample timing is precise. * @@ -136,7 +137,7 @@ static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pl * this value is only updated when the control loop has run. * The type is sw_pll_lock_status_t. */ -sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); +sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); /** * low level sw_pll control function for use as pure PLL control loop. @@ -152,7 +153,7 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint * this value is only updated when the control loop is running. * The type is sw_pll_lock_status_t. */ -sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); +sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); /**@}*/ // END: addtogroup sw_pll_lut @@ -179,7 +180,7 @@ sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, * calls the control loop every time so this is ignored. * \param pll_ratio Integer ratio between input reference clock and the PLL output. * Only used by sw_pll_sdm_do_control. Don't care otherwise. - * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_do_control is called. + * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_sdm_do_control is called. * Pass in zero if you are sure the mclk sampling timing is precise. This * will disable the scaling of the mclk count inside sw_pll_sdm_do_control. * Only used by sw_pll_sdm_do_control. Don't care otherwise. @@ -226,8 +227,8 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, * to output jitter being a PLL. * * \param sw_pll Pointer to the struct to be initialised. - * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_do_control. - * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_do_control. This value + * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_sdm_do_control. + * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_sdm_do_control. This value * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the * control loop will assume that mclk_pt sample timing is precise. * diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index 823018db..7abe9df0 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -72,19 +72,19 @@ static inline uint16_t lookup_pll_frac(sw_pll_state_t * const sw_pll, const int3 return sw_pll->lut_state.lut_table_base[frac_index]; } -void sw_pll_init( sw_pll_state_t * const sw_pll, - const sw_pll_15q16_t Kp, - const sw_pll_15q16_t Ki, - const sw_pll_15q16_t Kii, - const size_t loop_rate_count, - const size_t pll_ratio, - const uint32_t ref_clk_expected_inc, - const int16_t * const lut_table_base, - const size_t num_lut_entries, - const uint32_t app_pll_ctl_reg_val, - const uint32_t app_pll_div_reg_val, - const unsigned nominal_lut_idx, - const unsigned ppm_range) +void sw_pll_lut_init( sw_pll_state_t * const sw_pll, + const sw_pll_15q16_t Kp, + const sw_pll_15q16_t Ki, + const sw_pll_15q16_t Kii, + const size_t loop_rate_count, + const size_t pll_ratio, + const uint32_t ref_clk_expected_inc, + const int16_t * const lut_table_base, + const size_t num_lut_entries, + const uint32_t app_pll_ctl_reg_val, + const uint32_t app_pll_div_reg_val, + const unsigned nominal_lut_idx, + const unsigned ppm_range) { // Get PLL started and running at nominal sw_pll_app_pll_init(get_local_tile_id(), @@ -115,7 +115,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll, __attribute__((always_inline)) -inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) +inline sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) { int32_t total_error = sw_pll_do_pi_ctrl(sw_pll, error); sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error); @@ -125,7 +125,7 @@ inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const return sw_pll->lock_status; } -sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) +sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { if (++sw_pll->loop_counter == sw_pll->loop_rate_count) { @@ -145,7 +145,7 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint else { sw_pll_calc_error_from_port_timers(&sw_pll->pfd_state, &sw_pll->first_loop, mclk_pt, ref_clk_pt); - sw_pll_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); + sw_pll_lut_do_control_from_error(sw_pll, sw_pll->pfd_state.mclk_diff); // Save for next iteration to calc diff sw_pll->pfd_state.mclk_pt_last = mclk_pt; diff --git a/tests/test_app/main.c b/tests/test_app/main.c index 94391904..219e694f 100644 --- a/tests/test_app/main.c +++ b/tests/test_app/main.c @@ -3,7 +3,7 @@ /// /// Application to call the control loop with the parameters fully /// controllable by an external application. This app expects the -/// sw_pll_init parameters on the commannd line. These will be integers +/// sw_pll_lut_init parameters on the commannd line. These will be integers /// for lut_table_base, skip the parameter in the list and append the whole /// lut to the command line /// @@ -66,19 +66,19 @@ int main(int argc, char** argv) { fprintf(stderr, "\n"); sw_pll_state_t sw_pll; - sw_pll_init( &sw_pll, - SW_PLL_15Q16(kp), - SW_PLL_15Q16(ki), - SW_PLL_15Q16(kii), - loop_rate_count, - pll_ratio, - ref_clk_expected_inc, - lut_table_base, - num_lut_entries, - app_pll_ctl_reg_val, - app_pll_div_reg_val, - nominal_lut_idx, - ppm_range); + sw_pll_lut_init( &sw_pll, + SW_PLL_15Q16(kp), + SW_PLL_15Q16(ki), + SW_PLL_15Q16(kii), + loop_rate_count, + pll_ratio, + ref_clk_expected_inc, + lut_table_base, + num_lut_entries, + app_pll_ctl_reg_val, + app_pll_div_reg_val, + nominal_lut_idx, + ppm_range); for(;;) { @@ -104,7 +104,7 @@ int main(int argc, char** argv) { sscanf(read_buf, "%hu %hu", &mclk_pt, &ref_pt); fprintf(stderr, "%hu %hu\n", mclk_pt, ref_pt); uint32_t t0 = get_reference_time(); - sw_pll_lock_status_t s = sw_pll_do_control(&sw_pll, mclk_pt, ref_pt); + sw_pll_lock_status_t s = sw_pll_lut_do_control(&sw_pll, mclk_pt, ref_pt); uint32_t t1 = get_reference_time(); // xsim doesn't support our register and the val that was set gets diff --git a/tests/test_app_low_level_api/main.c b/tests/test_app_low_level_api/main.c index 64cbc882..1cc2f77e 100644 --- a/tests/test_app_low_level_api/main.c +++ b/tests/test_app_low_level_api/main.c @@ -3,7 +3,7 @@ /// /// Application to call the control loop with the parameters fully /// controllable by an external application. This app expects the -/// sw_pll_init parameters on the commannd line. These will be integers +/// sw_pll_lut_init parameters on the commannd line. These will be integers /// for lut_table_base, skip the parameter in the list and append the whole /// lut to the command line /// @@ -66,19 +66,19 @@ int main(int argc, char** argv) { fprintf(stderr, "\n"); sw_pll_state_t sw_pll; - sw_pll_init( &sw_pll, - SW_PLL_15Q16(kp), - SW_PLL_15Q16(ki), - SW_PLL_15Q16(kii), - loop_rate_count, - pll_ratio, - ref_clk_expected_inc, - lut_table_base, - num_lut_entries, - app_pll_ctl_reg_val, - app_pll_div_reg_val, - nominal_lut_idx, - ppm_range); + sw_pll_lut_init( &sw_pll, + SW_PLL_15Q16(kp), + SW_PLL_15Q16(ki), + SW_PLL_15Q16(kii), + loop_rate_count, + pll_ratio, + ref_clk_expected_inc, + lut_table_base, + num_lut_entries, + app_pll_ctl_reg_val, + app_pll_div_reg_val, + nominal_lut_idx, + ppm_range); for(;;) { @@ -104,7 +104,7 @@ int main(int argc, char** argv) { fprintf(stderr, "%hu\n", error); uint32_t t0 = get_reference_time(); - sw_pll_lock_status_t s = sw_pll_do_control_from_error(&sw_pll, error); + sw_pll_lock_status_t s = sw_pll_lut_do_control_from_error(&sw_pll, error); uint32_t t1 = get_reference_time(); // xsim doesn't support our register and the val that was set gets diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index 202bae21..75bff034 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -98,7 +98,7 @@ void control_task(int argc, char** argv, chanend_t c_sdm_control) { sscanf(read_buf, "%hd", &mclk_diff); uint32_t t0 = get_reference_time(); - int32_t error = sw_pll_sdm_do_control_from_error(&sw_pll, -mclk_diff); + int32_t error = sw_pll_do_pi_ctrl(&sw_pll, -mclk_diff); int32_t dco_ctl = sw_pll_sdm_post_control_proc(&sw_pll, error); uint32_t t1 = get_reference_time(); diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c index 563f2da1..b8ac6660 100644 --- a/tests/test_app_sdm_dco/main.c +++ b/tests/test_app_sdm_dco/main.c @@ -51,8 +51,8 @@ int main(int argc, char** argv) { // calc new ds_out and then wait to write uint32_t t0 = get_reference_time(); - int32_t ds_out = do_sigma_delta(&sdm_state, ds_in); - uint32_t frac_val = ds_out_to_frac_reg(ds_out); + int32_t ds_out = sw_pll_do_sigma_delta(&sdm_state, ds_in); + uint32_t frac_val = sw_pll_sdm_out_to_frac_reg(ds_out); uint32_t t1 = get_reference_time(); printf("%ld %lu %lu\n", ds_out, frac_val, t1 - t0); From cb8edba20c49eed18fccf2f49a6ffb0d84d5fa5f Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:03:21 +0000 Subject: [PATCH 089/118] actions->jenkins for doc build --- .github/workflows/docs.yml | 64 -------------------------------------- Jenkinsfile | 19 +++++++++-- 2 files changed, 17 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 95128b46..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Documentation - -on: - schedule: - # run at 5am every day - - cron: '0 5 * * *' - push: - paths: - - '.github/workflows/docs.yml' - - 'doc/settings.json' - - 'doc/exclude_patterns.inc' - - 'doc/**' - pull_request: - paths: - - '.github/workflows/docs.yml' - - 'doc/settings.json' - - 'doc/exclude-patterns.inc' - - 'doc/**' - - # Allow manually triggering the workflow. - workflow_dispatch: {} - -env: - XCORE_DOC_BUILDER: 'ghcr.io/xmos/doc_builder:v2.0.0' - -jobs: - build_documentation: - name: Build and package documentation - if: github.repository_owner == 'xmos' - runs-on: ubuntu-latest - steps: - - name: Checkout this repo - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.10.x' - - - name: Pull doc_builder container - run: | - docker pull ${XCORE_DOC_BUILDER} - - - name: Build documentation - run: | - # Move files specific to this repo - mv 'doc/settings.json' 'settings.json' - mv 'doc/substitutions.rst-inc' 'doc/substitutions.rst' - - docker run --user "$(id -u):$(id -g)" \ - --rm \ - -v ${{ github.workspace }}:/build \ - -e EXCLUDE_PATTERNS="/build/doc/exclude-patterns.inc" \ - -e OUTPUT_DIR=/build/doc/_build_sw_pll \ - -e PDF=1 \ - -e DOXYGEN_INCLUDE=/build/doc/Doxyfile.inc \ - -e DOXYGEN_INPUT=ignore ${XCORE_DOC_BUILDER} - - - name: Save documentation artifacts - uses: actions/upload-artifact@v3 - with: - name: docs-sw_pll - path: doc/_build_sw_pll - if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn` - retention-days: 30 diff --git a/Jenkinsfile b/Jenkinsfile index 148d9cab..1edc103f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,6 +27,7 @@ pipeline { environment { PYTHON_VERSION = "3.10.5" VENV_DIRNAME = ".venv" + WORKSPACE = "lib_sw_pll" } stages { @@ -37,10 +38,10 @@ pipeline { stages{ stage('Checkout'){ steps { - sh 'mkdir lib_sw_pll' + sh 'mkdir ${WORKSPACE}' // source checks require the directory // name to be the same as the repo name - dir('lib_sw_pll') { + dir('${WORKSPACE}') { // checkout repo checkout scm installPipfile(false) @@ -52,6 +53,20 @@ pipeline { } } } + stage('Docs') { + environment { XMOSDOC_VERSION = "v4.0" } + steps { + sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" + sh """docker run -u "\$(id -u):\$(id -g)" \ + --rm \ + -v ${WORKSPACE}:/build \ + ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" + + // Zip and archive doc files + zip dir: "${WORKSPACE}/doc/_build/", zipFile: "sw_pll_docs.zip" + archiveArtifacts artifacts: "sw_pll_docs.zip" + } + } stage('Build'){ steps { dir('lib_sw_pll') { From ddd8f7e1cb2504359c13cd6c55e94fbfbc0b4547 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:10:20 +0000 Subject: [PATCH 090/118] Debug --- Jenkinsfile | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1edc103f..1eefdae1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -53,20 +53,20 @@ pipeline { } } } - stage('Docs') { - environment { XMOSDOC_VERSION = "v4.0" } - steps { - sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" - sh """docker run -u "\$(id -u):\$(id -g)" \ - --rm \ - -v ${WORKSPACE}:/build \ - ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" + // stage('Docs') { + // environment { XMOSDOC_VERSION = "v4.0" } + // steps { + // sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" + // sh """docker run -u "\$(id -u):\$(id -g)" \ + // --rm \ + // -v ${WORKSPACE}:/build \ + // ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" - // Zip and archive doc files - zip dir: "${WORKSPACE}/doc/_build/", zipFile: "sw_pll_docs.zip" - archiveArtifacts artifacts: "sw_pll_docs.zip" - } - } + // // Zip and archive doc files + // zip dir: "${WORKSPACE}/doc/_build/", zipFile: "sw_pll_docs.zip" + // archiveArtifacts artifacts: "sw_pll_docs.zip" + // } + // } stage('Build'){ steps { dir('lib_sw_pll') { From 8f53fe6031cec7d4ee617fe17038f92c8a7e8829 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:15:25 +0000 Subject: [PATCH 091/118] debug --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1eefdae1..c2602b2a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,7 +41,7 @@ pipeline { sh 'mkdir ${WORKSPACE}' // source checks require the directory // name to be the same as the repo name - dir('${WORKSPACE}') { + dir('lib_sw_pll') { // checkout repo checkout scm installPipfile(false) From 8b5ff375474039510d3ecb8c227bd123a41a5ae9 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:18:08 +0000 Subject: [PATCH 092/118] debug --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c2602b2a..9e3bcfb4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,9 +25,9 @@ pipeline { ) } environment { + REPO = 'lib_sw_pll' PYTHON_VERSION = "3.10.5" VENV_DIRNAME = ".venv" - WORKSPACE = "lib_sw_pll" } stages { @@ -38,10 +38,10 @@ pipeline { stages{ stage('Checkout'){ steps { - sh 'mkdir ${WORKSPACE}' + sh 'mkdir ${REPO}' // source checks require the directory // name to be the same as the repo name - dir('lib_sw_pll') { + dir("${REPO}") { // checkout repo checkout scm installPipfile(false) From 96788c893c7bfff936d5085454e96f69f2e56d85 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:28:08 +0000 Subject: [PATCH 093/118] Add docs back in --- Jenkinsfile | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9e3bcfb4..f28a5deb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -53,23 +53,25 @@ pipeline { } } } - // stage('Docs') { - // environment { XMOSDOC_VERSION = "v4.0" } - // steps { - // sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" - // sh """docker run -u "\$(id -u):\$(id -g)" \ - // --rm \ - // -v ${WORKSPACE}:/build \ - // ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" + stage('Docs') { + environment { XMOSDOC_VERSION = "v4.0" } + steps { + dir("${REPO}") { + sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" + sh """docker run -u "\$(id -u):\$(id -g)" \ + --rm \ + -v ${REPO}:/build \ + ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" - // // Zip and archive doc files - // zip dir: "${WORKSPACE}/doc/_build/", zipFile: "sw_pll_docs.zip" - // archiveArtifacts artifacts: "sw_pll_docs.zip" - // } - // } + // Zip and archive doc files + zip dir: "doc/_build/", zipFile: "sw_pll_docs.zip" + archiveArtifacts artifacts: "sw_pll_docs.zip" + } + } + } stage('Build'){ steps { - dir('lib_sw_pll') { + dir("${REPO}") { withVenv { withTools(params.TOOLS_VERSION) { sh './tools/ci/do-ci-build.sh' @@ -80,7 +82,7 @@ pipeline { } stage('Test'){ steps { - dir('lib_sw_pll') { + dir("${REPO}") { withVenv { withTools(params.TOOLS_VERSION) { catchError { @@ -98,7 +100,7 @@ pipeline { } stage('Python examples'){ steps { - dir('lib_sw_pll') { + dir("${REPO}") { withVenv { catchError { sh './tools/ci/do-model-examples.sh' From a76581fe69340d91466e937a0520ef6532eb199d Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 15:48:20 +0000 Subject: [PATCH 094/118] Tweak docker paths --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f28a5deb..ceffbefe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,7 +56,7 @@ pipeline { stage('Docs') { environment { XMOSDOC_VERSION = "v4.0" } steps { - dir("${REPO}") { + dir("${REPO}/doc") { sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" sh """docker run -u "\$(id -u):\$(id -g)" \ --rm \ @@ -64,7 +64,7 @@ pipeline { ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" // Zip and archive doc files - zip dir: "doc/_build/", zipFile: "sw_pll_docs.zip" + zip dir: "_out/", zipFile: "sw_pll_docs.zip" archiveArtifacts artifacts: "sw_pll_docs.zip" } } From 50173b445dd89059151910462f8a695ce40bdf30 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 17:04:54 +0000 Subject: [PATCH 095/118] settings.yml in root and update paths --- .gitignore | 1 + Jenkinsfile | 6 +++--- doc/exclude-patterns.inc | 6 ++++-- doc/rst/sw_pll.rst | 16 ++++++++-------- index.rst | 8 -------- doc/settings.yml => settings.yml | 21 ++++++++++----------- 6 files changed, 26 insertions(+), 32 deletions(-) delete mode 100644 index.rst rename doc/settings.yml => settings.yml (73%) diff --git a/.gitignore b/.gitignore index 4c11f61f..4eb3cce7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__/ modules/ doc/doc/_build doc/_out +_build diff --git a/Jenkinsfile b/Jenkinsfile index ceffbefe..e9debe57 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,15 +56,15 @@ pipeline { stage('Docs') { environment { XMOSDOC_VERSION = "v4.0" } steps { - dir("${REPO}/doc") { + dir("${REPO}") { sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" sh """docker run -u "\$(id -u):\$(id -g)" \ --rm \ - -v ${REPO}:/build \ + -v $(pwd):/build \ ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" // Zip and archive doc files - zip dir: "_out/", zipFile: "sw_pll_docs.zip" + zip dir: "doc/_out/", zipFile: "sw_pll_docs.zip" archiveArtifacts artifacts: "sw_pll_docs.zip" } } diff --git a/doc/exclude-patterns.inc b/doc/exclude-patterns.inc index 1cd66753..e8a68639 100644 --- a/doc/exclude-patterns.inc +++ b/doc/exclude-patterns.inc @@ -1,6 +1,8 @@ # The following patterns are to be excluded from the documentation build -documents/README.rst -xcore_sdk +LICENSE.rst +CHANGELOG.rst +README.rst +modules test build diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 9b82b35a..55ab45e5 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -345,14 +345,6 @@ The Application Programmer Interface (API) for the Software PLL is shown below. WHY DOUBLE INTEGRAL TERM? -Common API -.......... - -The common API cover initialisation of the entire SW PLL and optional reset of the PI controller only. - -.. doxygengroup:: sw_pll_general - :content-only: - LUT Based PLL API ................. @@ -380,3 +372,11 @@ An example of how to implement the threading, timing barrier and non-blocking ch .. doxygengroup:: sw_pll_sdm :content-only: + +Common API +.......... + +The common API covers an optional reset of the PI controller only for both LUT and SDM options. + +.. doxygengroup:: sw_pll_general + :content-only: \ No newline at end of file diff --git a/index.rst b/index.rst deleted file mode 100644 index b37815ef..00000000 --- a/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -#################### -SW PLL DOCUMENTATION -#################### - -.. toctree:: - :maxdepth: 2 - - doc/sw_pll diff --git a/doc/settings.yml b/settings.yml similarity index 73% rename from doc/settings.yml rename to settings.yml index dd9ffaba..4df0bfec 100644 --- a/doc/settings.yml +++ b/settings.yml @@ -15,27 +15,26 @@ version: 2.0.0 products: SW_PLL: documentation: - build_path: ./_build - output_path: ./_out + build_path: doc/_build + output_path: doc/_out documentation: - title: Maximal Demo Project - root_doc: index.rst - exclude_patterns_path: ./exclude-patterns.inc - substitutions_path: ./substitutions.inc + title: Software PLL + root_doc: doc/index.rst + exclude_patterns_path: doc/exclude-patterns.inc + substitutions_path: doc/substitutions.inc linkcheck_ignore_regex: [".*xmos-broken-link.*"] - doc_dirname: rst - build_path: ./_build - output_path: ./_out + doc_dirname: doc + build_path: doc/_build + output_path: doc/_out doxygen_dirname: _doxygen - // doctrees_dirname: _doctrees doctrees_dirname: _doctrees autosection_label_depth: 3 latex_toc_depth: 3 enable_latex_index: false doxygen_projects: SW_PLL: - doxyfile_path: ./Doxyfile.inc + doxyfile_path: doc/Doxyfile.inc doxy_overrides: | GENERATE_HTML = yes GENERATE_LATEX = yes From 9e6b2e20460c55e58525029d30a47afbe34dcf9e Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 29 Nov 2023 18:09:29 +0000 Subject: [PATCH 096/118] Missing escape character --- Jenkinsfile | 2 +- README.rst | 2 +- doc/rst/sw_pll.rst | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e9debe57..8822a7f6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -60,7 +60,7 @@ pipeline { sh "docker pull ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION" sh """docker run -u "\$(id -u):\$(id -g)" \ --rm \ - -v $(pwd):/build \ + -v \$(pwd):/build \ ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" // Zip and archive doc files diff --git a/README.rst b/README.rst index ad516bc9..d0a6c353 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ This library contains software that, together with the on-chip application PLL, It supports both Look Up Table (LUT) and Sigma Delta Modulated (SDM) Digitally Controlled Oscillators (DCO), a Phase Frequency Detector (PFD) and configurable Proportional Integral (PI) controllers which together form a hybrid Software/Hardware Phase Locked Loop (PLL). -Examples are provided showing a master clock locking to an low frequency input clock and to an I2S slave interface. +Examples are provided showing a master clock locking to a low frequency input reference clock and also to an I2S slave interface. ********************************* Building and running the examples diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 55ab45e5..8f1a4eaf 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -141,6 +141,7 @@ This document provides a guide to generating the LUT and configuring the availab reach the appropriate compromise of performance and resource usage for your application. + Steps to tune the PI loop ------------------------- @@ -155,6 +156,12 @@ Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1. .. note:: After changing the configuration, ensure you delete `fractions.h` otherwise the script will re-use the last calculated values. This is done to speed execution time of the script by avoiding the generation step. +A double integral term is supported in the PI loop because the the clock counting PFD included measures +the frequency error. The phase error is the integral of the frequency error and hence if phase locking +is required as well as frequency locking then we need to support the intergral of the integral of +the frequency error. Simply changing the Kp, Ki and Kii constants is all that is needed in this case. + +Typically a small Kii term is used if needed because it accumulates very quickily. Running the PI simulation and LUT generation script --------------------------------------------------- @@ -342,9 +349,6 @@ lib_sw_pll API The Application Programmer Interface (API) for the Software PLL is shown below. It is split into common items needed for both LUT and SDM DCOs and items specific to each type of DCO. - -WHY DOUBLE INTEGRAL TERM? - LUT Based PLL API ................. From 4867dfe00a7d35f247292d9dd3b1398ce626173e Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 30 Nov 2023 11:26:40 +0000 Subject: [PATCH 097/118] More doc fixes --- Jenkinsfile | 2 +- doc/index.rst | 2 +- doc/rst/images/PLL_block_diagram.png | Bin 0 -> 27751 bytes doc/rst/images/lut_dco_range.png | Bin 0 -> 43411 bytes doc/rst/images/lut_pll.png | Bin 0 -> 35557 bytes doc/rst/images/modulated_fft_lut.png | Bin 0 -> 33597 bytes doc/rst/images/modulated_fft_sdm.png | Bin 0 -> 35419 bytes doc/rst/images/pll_step_response.png | Bin 0 -> 59968 bytes doc/rst/images/resource_setup_example.png | Bin 0 -> 64438 bytes doc/rst/images/sdm_dco_range.png | Bin 0 -> 52752 bytes doc/rst/images/sdm_pll.png | Bin 0 -> 30857 bytes doc/rst/images/tracking_lut.png | Bin 0 -> 42954 bytes doc/rst/images/tracking_sdm.png | Bin 0 -> 38225 bytes doc/rst/sw_pll.rst | 38 +++++++++++----------- python/sw_pll/dco_model.py | 2 +- settings.yml | 14 +++----- 16 files changed, 26 insertions(+), 32 deletions(-) create mode 100644 doc/rst/images/PLL_block_diagram.png create mode 100644 doc/rst/images/lut_dco_range.png create mode 100644 doc/rst/images/lut_pll.png create mode 100644 doc/rst/images/modulated_fft_lut.png create mode 100644 doc/rst/images/modulated_fft_sdm.png create mode 100644 doc/rst/images/pll_step_response.png create mode 100644 doc/rst/images/resource_setup_example.png create mode 100644 doc/rst/images/sdm_dco_range.png create mode 100644 doc/rst/images/sdm_pll.png create mode 100644 doc/rst/images/tracking_lut.png create mode 100644 doc/rst/images/tracking_sdm.png diff --git a/Jenkinsfile b/Jenkinsfile index 8822a7f6..367605f1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -61,7 +61,7 @@ pipeline { sh """docker run -u "\$(id -u):\$(id -g)" \ --rm \ -v \$(pwd):/build \ - ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v""" + ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v html latex""" // Zip and archive doc files zip dir: "doc/_out/", zipFile: "sw_pll_docs.zip" diff --git a/doc/index.rst b/doc/index.rst index 81e6516e..9947865f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,6 +3,6 @@ SW PLL DOCUMENTATION #################### .. toctree:: - :maxdepth: 2 + :maxdepth: 3 rst/sw_pll diff --git a/doc/rst/images/PLL_block_diagram.png b/doc/rst/images/PLL_block_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..ef19ccbb8bd23d6bc334eb7abfb7fb6eec683e03 GIT binary patch literal 27751 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 literal 0 HcmV?d00001 diff --git a/doc/rst/images/lut_dco_range.png b/doc/rst/images/lut_dco_range.png new file mode 100644 index 0000000000000000000000000000000000000000..6dd4fbbe9c1733dfddeb908e8cc7da62ef598597 GIT binary patch literal 43411 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 G^#1}V1Eb{t literal 0 HcmV?d00001 diff --git a/doc/rst/images/lut_pll.png b/doc/rst/images/lut_pll.png new file mode 100644 index 0000000000000000000000000000000000000000..df9efd2e11f4fc2373ec7a904e4b8b862dc03f56 GIT binary patch literal 35557 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�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 E0Ak{)TmS$7 literal 0 HcmV?d00001 diff --git a/doc/rst/images/modulated_fft_lut.png b/doc/rst/images/modulated_fft_lut.png new file mode 100644 index 0000000000000000000000000000000000000000..b839907d448057fd6957a2587e21727d83fe0da6 GIT binary patch literal 33597 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 ZcREnH!k=-@3%SCY7qu^Bows=MKLA&#ZZrS@ literal 0 HcmV?d00001 diff --git a/doc/rst/images/pll_step_response.png b/doc/rst/images/pll_step_response.png new file mode 100644 index 0000000000000000000000000000000000000000..f00a6bb1357db0140311018ff11c40179066016f GIT binary patch literal 59968 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 SMJxGM+JPfKX8v&M=l=pNvMWXa literal 0 HcmV?d00001 diff --git a/doc/rst/images/resource_setup_example.png b/doc/rst/images/resource_setup_example.png new file mode 100644 index 0000000000000000000000000000000000000000..af49b80c575033b4b9cf0b5ea8e4af965ddee531 GIT binary patch literal 64438 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%0Dq3`tWSdb4G(C+Wm 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 Kz53JYfBqL^()02F literal 0 HcmV?d00001 diff --git a/doc/rst/images/sdm_pll.png b/doc/rst/images/sdm_pll.png new file mode 100644 index 0000000000000000000000000000000000000000..80dd1e6ccbe90776fd49834bccfe10fda2e63cee GIT binary patch literal 30857 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[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%0A*cjC#_0BHAn&aMGmK9YR` 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 literal 0 HcmV?d00001 diff --git a/doc/rst/images/tracking_lut.png b/doc/rst/images/tracking_lut.png new file mode 100644 index 0000000000000000000000000000000000000000..ced9ce56de4238a73a11c117be6c8e5d1c42bdf8 GIT binary patch literal 42954 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 F;Gq$zXUtkwC<}j=|5hE;q@yus8tG1p>LKdyl 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 literal 0 HcmV?d00001 diff --git a/doc/rst/images/tracking_sdm.png b/doc/rst/images/tracking_sdm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a103d6350268c16373c1669930b9b6030e58637 GIT binary patch literal 38225 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 CQc%SJ literal 0 HcmV?d00001 diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 8f1a4eaf..e622ba74 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -1,10 +1,10 @@ How the Software PLL works -------------------------- -A Phase Locked Loop (PLL) is a typically a circuit that allows generation of a clock which is synchronised -to an input reference clock by both phase and frequency. They consist of a number of components: +A Phase Locked Loop (PLL) is a typically hardware that allows generation of a clock which is synchronised +to an input reference clock by both phase and frequency. They consist of a number of sub-components: - - A Phase Frequency Detector (PFD) which measures the difference between a reference clock and the divided generated clock. + - A Phase Frequency Detector (PFD) which measures the difference (error) between a reference clock and the divided generated clock. - A control loop, typically a Proportional Integral (PI) controller to close the loop and zero the error. - A Digitally Controlled Oscillator (DCO) which converts a control signal into a clock frequency. @@ -14,25 +14,26 @@ to an input reference clock by both phase and frequency. They consist of a numbe Basic PLL Block Diagram -xcore-ai devices have on-chip a secondary PLL sometimes called the Application (App) PLL. This PLL +xcore-ai devices have on-chip a secondary PLL sometimes known as the Application PLL. This PLL multiplies the clock from the on-board crystal source and has a fractional register allowing very fine control -over the multiplication and division ratios under software. +over the multiplication and division ratios from software. However, it does not support an external reference clock input and so cannot natively track and lock to an external clock reference. This software PLL module provides a set of scripts and firmware which enables the provision of an input reference clock which, along with a control loop, allows tracking of the external reference -over a certain range. +over a certain range. It also provides a lower level API to allow tracking of virtual clocks rather than +physical signals. There are two types of PLL, or specifically Digitally Controlled Oscillators (DCO), supported in this library. LUT based DCO ............. -The LUT based DCO allows a discrete set of fractional settings resulting in a number of frequency steps. -The LUT is pre-computed table which provides a set of monotonic increasing register settings. The LUT -based DCO requires very low compute allowing it to typically be run in a sample based loop at audio -frequencies such as 48kHz or 44.1kHz. It does require two bytes per LUT entry. It provides reasonable -jitter performance suitable for voice or entry level HiFi. +The LUT based DCO allows a discrete set of fractional settings resulting in a fixed number of frequency steps. +The LUT is pre-computed table which provides a set of monotonic increasing frequency register settings. The LUT +based DCO requires very low compute allowing it to be run in a sample-based loop at audio +frequencies such as 48kHz or 44.1kHz. It required two bytes per LUT entry. It provides reasonable +jitter performance suitable for voice or entry level Hi-Fi. .. figure:: ./images/lut_pll.png :width: 100% @@ -42,7 +43,7 @@ jitter performance suitable for voice or entry level HiFi. The range is governed by the look up table (LUT) which has a finite number of entries and consequently a step size which affects the output jitter performance when the controller oscillates between two -settings. Note that the actual range and number of steps is highly configurable. +settings once locked. Note that the actual range and number of steps is highly configurable. .. figure:: ./images/lut_dco_range.png :width: 100% @@ -52,7 +53,7 @@ settings. Note that the actual range and number of steps is highly configurable. The index into the LUT is controlled by a PI controller which multiplies the error in put and integral error input by the supplied loop constants. -An integrated wind up limiter for the integral term is nominally set at 2x the maximum LUT index +An integrated `wind up` limiter for the integral term is nominally set at 2x the maximum LUT index deviation to prevent excessive overshoot where the starting input error is high. A time domain plot of how the controller (typically running at around 100 Hz) selects between adjacent @@ -72,21 +73,20 @@ SDM Based DCO ............. The SDM based DCO provides a fixed number (9 in this case) of frequency steps which are jumped between -at a high rate (eg. 1 MHz) but requires a dedicated logical core to run the SDM and update the PLL +at a high rate (eg. 1 MHz) but requires a dedicated logical core to run the SDM algorithm and update the PLL fractional register. The SDM is third order. -It typically provides better audio quality by pushing the noise floor up into the +The SDM typically provides better audio quality by pushing the noise floor up into the inaudible part of the spectrum. A fixed set of SDM coefficients and loop filters are provided which -have been hand tuned to provide either 24.576 MHz or 22.5792 MHz clocks suitable for HiFi systems +have been hand tuned to provide either 24.576 MHz or 22.5792 MHz low jitter clocks and are suitable for Hi-Fi systems +and professional audio applications. .. figure:: ./images/sdm_pll.png :width: 100% SDM DCO based PLL -The steps for the SDM output are quite large which means a wide range is typically available. Note -that the trade-off between number of steps, step size and range can be made during the LUT generation -stage. +The steps for the SDM output are quite large which means a wide range is typically available. .. figure:: ./images/sdm_dco_range.png :width: 100% diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index 9c867ff7..c22e6df6 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -357,7 +357,7 @@ def plot_freq_range(self): """ frequencies = [] - for step in range(self.p_value + 1 + 1): # +1 since p value is +1 in datasheet and another +1 so we hit the max value + for step in range(self.p_value + 1): # +1 since p value is +1 in datasheet frequencies.append(self._sdm_out_to_freq(step)) plt.clf() diff --git a/settings.yml b/settings.yml index 4df0bfec..bdad3286 100644 --- a/settings.yml +++ b/settings.yml @@ -1,13 +1,7 @@ --- -######################## -# Maximal Setting File # -######################## - -## Quick settings feature reference -# elements ending in _path are resolved from the directory of this settings file -# string elements can be templated into children using {{ELEMENT}} -# any element defined at the top level will be available to all "product/section" unless overridden - +################################ +# Settings file for docs build # +################################ project: SW_PLL version: 2.0.0 @@ -40,7 +34,7 @@ documentation: GENERATE_LATEX = yes pdfs: - index: + doc/index: pdf_title: '{{product}} Programming Guide \newline' pdf_filename: '{{product}}_progamming_guide_v{{version}}' From 759f021f4871477d0fa06fee14afdf019df0423d Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 30 Nov 2023 12:48:55 +0000 Subject: [PATCH 098/118] Rejig of docs --- doc/rst/sw_pll.rst | 242 +++++++++++++++++++++--------------- lib_sw_pll/src/sw_pll_sdm.h | 8 +- settings.yml | 26 +--- 3 files changed, 155 insertions(+), 121 deletions(-) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index e622ba74..b8a42159 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -1,6 +1,9 @@ How the Software PLL works -------------------------- +Introduction +............ + A Phase Locked Loop (PLL) is a typically hardware that allows generation of a clock which is synchronised to an input reference clock by both phase and frequency. They consist of a number of sub-components: @@ -26,6 +29,28 @@ physical signals. There are two types of PLL, or specifically Digitally Controlled Oscillators (DCO), supported in this library. +There are trade-offs between the two types of DCO which are summarised in the following table. + +.. list-table:: LUT vs SDM DCO trade-offs + :widths: 15 30 30 + :header-rows: 1 + + * - Comparison item + - LUT DCO + - SDM DCO + * - Jitter + - Low - ~1-2 ns + - Very Low - ~10-50 ps + * - Memory Usage + - Moderate - 3 kB + - Low - 1 kB + * - MIPS Usage + - Low - ~1 + - High - ~50 + * - Lock Range PPM + - Moderate - 100-1000 + - Wide - 1500-3000 + LUT based DCO ............. @@ -107,64 +132,15 @@ spread of the noise floor can be seen in the following diagrams. SDM noise plot when when tracking a constant input frequency -There are trade-offs between the two types of DCO which are summarised in the following table. - -.. list-table:: LUT vs SDM DCO trade-offs - :widths: 15 30 30 - :header-rows: 1 - - * - Comparison item - - LUT DCO - - SDM DCO - * - Jitter - - Low - ~1-2 ns - - Very Low - ~10-50 ps - * - Memory Usage - - Moderate - 3 kB - - Low - 1 kB - * - MIPS Usage - - Low - ~1 - - High - ~50 - * - Lock Range PPM - - Moderate - 100-1000 - - Wide - 1500-3000 - - -Controller API Notes -.................... - -In addition to the standard API which takes a clock counting input, for applications where the PLL is -to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level -API allows the Software PLL to track an arbitrary clock source which is calculated by another means. - This document provides a guide to generating the LUT and configuring the available parameters to reach the appropriate compromise of performance and resource usage for your application. - -Steps to tune the PI loop -------------------------- - -Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp* and *Ki* can be found in the function `run_sim()`. - -Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1.0) *Ki* term. - - - Decreasing the ref_to_loop_call_rate parameter will cause the control loop to execute more frequently and larger constants will be needed. - - Try tuning *Ki* value until the desired response curve (settling time, overshoot etc.) is achieved in the ``pll_step_response.png`` output. - - *Kp* can normally remain zero, but you may wish to add a small value to improve step response - -.. note:: - After changing the configuration, ensure you delete `fractions.h` otherwise the script will re-use the last calculated values. This is done to speed execution time of the script by avoiding the generation step. - -A double integral term is supported in the PI loop because the the clock counting PFD included measures -the frequency error. The phase error is the integral of the frequency error and hence if phase locking -is required as well as frequency locking then we need to support the intergral of the integral of -the frequency error. Simply changing the Kp, Ki and Kii constants is all that is needed in this case. - -Typically a small Kii term is used if needed because it accumulates very quickily. +Simulation Model +---------------- Running the PI simulation and LUT generation script ---------------------------------------------------- +................................................... In the ``python/sw_pll`` directory you will find multiple files:: @@ -178,8 +154,8 @@ In the ``python/sw_pll`` directory you will find multiple files:: └── sw_pll_sim.py ``pll_calc.py`` is the command line script that generates the LUT. It is quite a complex to use script which requires in depth -knowledge of the operation of the App PLL. Instead, it is recommended to use ``sw_pll_sim.py`` which calls ``pll_calc.py`` -except with a number of example PLL profiles already provided as a starting point. +knowledge of the operation of the App PLL. Instead, it is recommended to use ``app_pll_model.py`` which calls ``pll_calc.py`` which +wraps the script with defaults. By running `sw_pll_sim.py` a number of operations will take place: @@ -192,17 +168,17 @@ By running `sw_pll_sim.py` a number of operations will take place: - A zoomed-in log FFT plot of the 1 kHz tone to see how the LUT frequency steps affect a pure tone. The same note applies as the above item. - A summary report of the PLL range is printed to the console. -The directory listing following running of ``sw_pll_sim.py`` should look as follows:: +The directory listing following running of ``sw_pll_sim.py`` will be added to as follows:: . ├── fractions.h - ├── pll_calc.py - ├── pll_step_response.png ├── register_setup.h - ├── sw_pll_range.png - ├── modulated_tone_1000Hz.wav - ├── modulated_tone_fft_1000Hz.png - └── sw_pll_sim.py + ├── tracking_lut.png + ├── tracking_sdm.png + ├── modulated_tone_1000Hz_lut.wav + ├── modulated_tone_1000Hz_sdm.wav + ├── modulated_fft_lut.png + └── modulated_fft_sdm.png A typical LUT transfer function is shown below. Note that although not perfectly regular it is monotonic and hence @@ -213,7 +189,6 @@ loop is called to center this noise around different frequencies or decrease the manage the amplitude of this artifact. - Here you can see the step response of the control loop below. You can see it track smaller step changes but for the larger steps it can be seen to clip and not reach the input step, which is larger than the LUT size will allow. The LUT size can be increased if needed to accommodate a wider range. @@ -241,44 +216,36 @@ Below is a typical report showing what information is summarised:: LUT entries: 413 (826 bytes) -The following section provides guidance for adjusting the LUT. +Tuning the Software PLL +----------------------- -How to configure the LUT fractions table ----------------------------------------- +Tuning the PI controller +........................ -The fractions lookup table is a trade-off between PPM range and frequency step size. Frequency -step size will affect jitter amplitude as it is the amount that the PLL will change frequency when it needs -to adjust. Typically, the locked control loop will slowly oscillate between two values that -straddle the target frequency, depending on input frequency. - -Small discontinuities in the LUT may be experienced in certain ranges, particularly close to 0.5 fractional values, so it is preferable -to keep in the lower or upper half of the fractional range. However the LUT table is always monotonic -and so control instability will not occur for that reason. The range of the ``sw_pll`` can be seen -in the ``sw_pll_range.png`` image. It should be a reasonably linear response without significant -discontinuities. If not, try moving the range towards 0.0 or 1.0 where fewer discontinuities will -be observed. +Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp*, *Ki* and optionally *Kii* can be found in the functions `run_lut_sw_pll_sim()` and `run_sd_sw_pll_sim()`. -Steps to vary PPM range and frequency step size -............................................... +Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1.0) *Ki* term. + + - Decreasing the ref_to_loop_call_rate parameter will cause the control loop to execute more frequently and larger constants will be needed. + - Try tuning *Ki* value until the desired response curve (settling time, overshoot etc.) is achieved in the ``tracking_xxx.png`` output. + - *Kp* can normally remain zero, but you may wish to add a small value to improve step response +.. note:: + After changing the configuration, ensure you delete `fractions.h` otherwise the script will re-use the last calculated values. This is done to speed execution time of the script by avoiding the generation step. -1. Ascertain your target PPM range, step size and maximum tolerable table size. Each lookup value is 16b so the total size in bytes is 2 x n. -2. Start with the given example values and run the generator to see if the above three parameters meet your needs. The values are reported by ``sw_pll_sim.py``. -3. If you need to increase the PPM range, you may either: - - Decrease the ``min_F`` to allow the fractional value to have a greater effect. This will also increase step size. It will not affect the LUT size. - - Increase the range of ``fracmin`` and ``fracmax``. Try to keep the range closer to 0 or 1.0. This will decrease step size and increase LUT size. -4. If you need to decrease the step size you may either: - - Increase the ``min_F`` to allow the fractional value to have a greater effect. This will also reduce the PPM range. When the generation script is run the allowable F values are reported so you can tune the ``min_F`` to force use of a higher F value. - - Increase the ``max_denom`` beyond 80. This will increase the LUT size (finer step resolution) but not affect the PPM range. Note this will increase the intrinsic jitter of the PLL hardware on chip due to the way the fractional divider works. 80 has been chosen for a reasonable tradeoff between step size and PLL intrinsic jitter and pushes this jitter beyond 40 kHz which is out of the audio band. The lowest intrinsic fractional PLL jitter freq is input frequency (normally 24 MHz) / ref divider / largest value of n. -5. If the +/-PPM range is not symmetrical and you wish it to be, then adjust the ``fracmin`` and ``fracmax`` values around the center point that the PLL finder algorithm has found. For example if the -PPM range is to great, increase ``fracmin`` and if the +PPM range is too great, decrease the ``fracmax`` value. +A double integral term is supported in the PI loop because the the clock counting PFD included measures +the frequency error. The phase error is the integral of the frequency error and hence if phase locking +is required as well as frequency locking then we need to support the integral of the integral of +the frequency error. Simply changing the Kp, Ki and Kii constants is all that is needed in this case. +Typically a small Kii term is used if needed because it accumulates very quickly. -Note when the process has completed, please inspect the ``sw_pll_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. -This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. +LUT Example Configurations +.......................... -Example configurations -...................... +However the LUT implementation requires an offline generation stage which has many possibilities for customisation. +A few examples are shown below. A number of example configurations, which demonstrate the effect on PPM, step size etc. of changing various parameters, is provided in the ``sw_pll_sim.py`` file. Search for ``profiles`` and ``profile_choice`` in this file. Change profile choice index to select the different example profiles and run the python file again. @@ -326,16 +293,93 @@ Search for ``profiles`` and ``profile_choice`` in this file. Change profile choi Note that the PLL actually multiplies the input crystal, not the reference input clock. A change in the reference input clock only affects the control loop and its associated constants such as how often the PI loop is called. + +Custom LUT Generation Guidance +.............................. + + +The fractions lookup table is a trade-off between PPM range and frequency step size. Frequency +step size will affect jitter amplitude as it is the amount that the PLL will change frequency when it needs +to adjust. Typically, the locked control loop will slowly oscillate between two values that +straddle the target frequency, depending on input frequency. + +Small discontinuities in the LUT may be experienced in certain ranges, particularly close to 0.5 fractional values, so it is preferable +to keep in the lower or upper half of the fractional range. However the LUT table is always monotonic +and so control instability will not occur for that reason. The range of the ``sw_pll`` can be seen +in the ``sw_pll_range.png`` image. It should be a reasonably linear response without significant +discontinuities. If not, try moving the range towards 0.0 or 1.0 where fewer discontinuities will +be observed. + +Steps to vary the LUT PPM range and frequency step size +....................................................... + + +1. Ascertain your target PPM range, step size and maximum tolerable table size. Each lookup value is 16b so the total size in bytes is 2 x n. +2. Start with the given example values and run the generator to see if the above three parameters meet your needs. The values are reported by ``sw_pll_sim.py``. +3. If you need to increase the PPM range, you may either: + - Decrease the ``min_F`` to allow the fractional value to have a greater effect. This will also increase step size. It will not affect the LUT size. + - Increase the range of ``fracmin`` and ``fracmax``. Try to keep the range closer to 0 or 1.0. This will decrease step size and increase LUT size. +4. If you need to decrease the step size you may either: + - Increase the ``min_F`` to allow the fractional value to have a greater effect. This will also reduce the PPM range. When the generation script is run the allowable F values are reported so you can tune the ``min_F`` to force use of a higher F value. + - Increase the ``max_denom`` beyond 80. This will increase the LUT size (finer step resolution) but not affect the PPM range. Note this will increase the intrinsic jitter of the PLL hardware on chip due to the way the fractional divider works. 80 has been chosen for a reasonable tradeoff between step size and PLL intrinsic jitter and pushes this jitter beyond 40 kHz which is out of the audio band. The lowest intrinsic fractional PLL jitter freq is input frequency (normally 24 MHz) / ref divider / largest value of n. +5. If the +/-PPM range is not symmetrical and you wish it to be, then adjust the ``fracmin`` and ``fracmax`` values around the center point that the PLL finder algorithm has found. For example if the -PPM range is to great, increase ``fracmin`` and if the +PPM range is too great, decrease the ``fracmax`` value. + + +Note when the process has completed, please inspect the ``sw_pll_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. +This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. + +SDM Available Configurations +............................ + +The SDM implementation only allows tuning of the PI loop; the DCO section is hand optimised for various profiles shown +below. There are two target PLL output frequencies and two options for SDM update rate depending on how much performance +is available. + + + +.. list-table:: SDM DCO configurations + :widths: 50 50 50 50 50 + :header-rows: 1 + + * - Output frequency MHz + - Range +/- PPM + - Jitter ps + - Noise Floor dBc + - SDM update rate kHz + * - 24.576 + - 3000 + - 10 + - -100 + - 1000 + * - 22.5792 + - 3300 + - 10 + - -100 + - 1000 + * - 24.576 + - 1500 + - 50 + - -93 + - 500 + * - 22.5792 + - 1500 + - 50 + - -93 + - 500 + + Transferring the results to C ............................. Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Either consult the ``sw_pll.h`` API file (below) for details or follow one of the examples in the ``/examples`` directory. -Simple Example Resource Setup ------------------------------ -The xcore-ai has a number of resources on chip. In the `simple` examples both clock blocks and ports are connected together to provide an input to -the PDF and provide a scaled output clock. The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intinsic functions in ``lib_xcore``. + +Simple Usage Example Resource Setup +----------------------------------- + +The xcore-ai has a number of resources on chip. In the `simple` examples both `clock blocks` and `ports` are connected together to provide an input to +the PFD and additionally provide a scaled output clock. The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intrinsic functions in ``lib_xcore``. To help visualise how these resources work together, please see the below diagram. .. figure:: ./images/resource_setup_example.png @@ -349,6 +393,10 @@ lib_sw_pll API The Application Programmer Interface (API) for the Software PLL is shown below. It is split into common items needed for both LUT and SDM DCOs and items specific to each type of DCO. +In addition to the standard API which takes a clock counting input, for applications where the PLL is +to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level +API allows the Software PLL to track an arbitrary clock source which is calculated by another means. + LUT Based PLL API ................. @@ -360,9 +408,9 @@ The LUT based API are functions designed to be called from an audio loop. Typica SDM Based PLL API ................. -All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and it is expected that the user provide the fork (par) and call the SDM in a loop. A typical idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received periodically. The SDM calculation and register write takes 45 instuction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. +All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and it is expected that the user provide the fork (par) and call the SDM in a loop. A typical idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received periodically. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. -The control part of the SDM SW PLL takes 75 instuction cycles when active and a few 10s of cycles when inactive so you will need to budget around 1 MIPS for this. +The control part of the SDM SW PLL takes 75 instruction cycles when active and a few 10 s of cycles when inactive so you will need to budget around 1 MIPS for this. An example of how to implement the threading, timing barrier and non-blocking channel poll can be found in ``examples/simple_sdm/simple_sw_pll_sdm.c``. A thread diagram of how this can look is shown below. diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index 69b0c951..5df00fd3 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -70,8 +70,14 @@ static inline uint32_t sw_pll_sdm_out_to_frac_reg(int32_t sdm_out){ } /** - * low level sw_pll_write_frac_reg function that writes the PLL frational + * low level sw_pll_write_frac_reg function that writes the PLL fractional * register. + * + * NOTE: attempting to write the PLL fractional register from more than + * one logical core at the same time may result in channel lock-up. + * Please ensure the that PLL initiaisation has completed before + * the SDM task writes to the register. The provided example + * implements a method for doing this. * * \param this_tile The ID of the xcore tile that is doing the write. * \param frac_val 16b signed input error value diff --git a/settings.yml b/settings.yml index bdad3286..8c350d69 100644 --- a/settings.yml +++ b/settings.yml @@ -5,36 +5,16 @@ project: SW_PLL version: 2.0.0 - -products: - SW_PLL: - documentation: - build_path: doc/_build - output_path: doc/_out - documentation: title: Software PLL root_doc: doc/index.rst exclude_patterns_path: doc/exclude-patterns.inc substitutions_path: doc/substitutions.inc - linkcheck_ignore_regex: [".*xmos-broken-link.*"] - doc_dirname: doc - build_path: doc/_build - output_path: doc/_out - doxygen_dirname: _doxygen - doctrees_dirname: _doctrees - autosection_label_depth: 3 - latex_toc_depth: 3 - enable_latex_index: false doxygen_projects: SW_PLL: doxyfile_path: doc/Doxyfile.inc - doxy_overrides: | - GENERATE_HTML = yes - GENERATE_LATEX = yes - pdfs: doc/index: - pdf_title: '{{product}} Programming Guide \newline' - pdf_filename: '{{product}}_progamming_guide_v{{version}}' - + pdf_title: '{{project}} Programming Guide \newline' + pdf_filename: '{{project}}_progamming_guide_v{{version}}' + \ No newline at end of file From fdf80c9c8eec107324279b2104fc3c21fd6c4eed Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 30 Nov 2023 12:59:13 +0000 Subject: [PATCH 099/118] Fix path --- Jenkinsfile | 2 +- doc/rst/sw_pll.rst | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 367605f1..b04fce8d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -64,7 +64,7 @@ pipeline { ghcr.io/xmos/xmosdoc:$XMOSDOC_VERSION -v html latex""" // Zip and archive doc files - zip dir: "doc/_out/", zipFile: "sw_pll_docs.zip" + zip dir: "doc/_build/", zipFile: "sw_pll_docs.zip" archiveArtifacts artifacts: "sw_pll_docs.zip" } } diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index b8a42159..3b6ec2f0 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -371,7 +371,11 @@ is available. Transferring the results to C ............................. -Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Either consult the ``sw_pll.h`` API file (below) for details or follow one of the examples in the ``/examples`` directory. +Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Control loop constants +can be directly transferred to the `init()` functions and the generated `.h` files can be copied directly into the source directory +of your project. + +For further information, either consult the ``sw_pll.h`` API file (included at the end of this document) or follow one of the examples in the ``/examples`` directory. From bdb970fe7642bde22c2b09fca6e5c46ae141480e Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 30 Nov 2023 17:30:02 +0000 Subject: [PATCH 100/118] Move typical LUT profiles to sw_pll_sim --- python/sw_pll/app_pll_model.py | 17 +---------------- python/sw_pll/dco_model.py | 2 +- python/sw_pll/sw_pll_sim.py | 24 +++++++++++++++++++++--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/python/sw_pll/app_pll_model.py b/python/sw_pll/app_pll_model.py index 04d2bca6..e6c3f9ef 100644 --- a/python/sw_pll/app_pll_model.py +++ b/python/sw_pll/app_pll_model.py @@ -172,22 +172,7 @@ def get_pll_solution(input_frequency, target_output_frequency, max_denom=80, min ppm_max - (Optional) The allowable PPM deviation for the target nominal frequency. See/doc/sw_pll.rst for guidance fracmin - (Optional) The minimum fractional multiplier. See/doc/sw_pll.rst for guidance fracmax - (Optional) The maximum fractional multiplier. See/doc/sw_pll.rst for guidance - - # Example profiles to produce typical frequencies seen in audio systems - profiles = [ - # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, - # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 2 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, - # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, - # 4 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size - {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, - # 5 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size - {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, - ] + """ diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index c22e6df6..a8439567 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -395,4 +395,4 @@ def write_register_file(self): sdm_dco.plot_freq_range() for i in range(30): output_frequency = sdm_dco.do_modulate(500000) - print(i, output_frequency) + # print(i, output_frequency) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index c58e3a14..e7a4ca0e 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -77,16 +77,34 @@ def do_control_loop(self, output_clock_count, period_fraction=1.0, verbose=False def run_lut_sw_pll_sim(): """ - Test program / example showing how to run the simulator object + Test program / example showing how to run the simulator object """ - nominal_output_hz = 12288000 + + # Example profiles to produce typical frequencies seen in audio systems. ALl assume 24MHz input clock to the hardware PLL. + profiles = [ + # 0 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-250PPM, 29.3Hz steps, 426B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.843, "fracmax":0.95}, + # 1 - 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":80, "min_F":200, "ppm_max":5, "fracmin":0.695, "fracmax":0.905}, + # 2 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-1000PPM, 31.9Hz steps, 1580B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":12288000, "max_denom":90, "min_F":140, "ppm_max":5, "fracmin":0.49, "fracmax":0.81}, + # 3 - 24.576MHz with 48kHz ref (note also works with 16kHz ref), +-100PPM, 9.5Hz steps, 1050B LUT size + {"nominal_ref_frequency":48000.0, "target_output_frequency":24576000, "max_denom":120, "min_F":400, "ppm_max":5, "fracmin":0.764, "fracmax":0.884}, + # 4 - 6.144MHz with 16kHz ref, +-200PPM, 30.2Hz steps, 166B LUT size + {"nominal_ref_frequency":16000.0, "target_output_frequency":6144000, "max_denom":40, "min_F":400, "ppm_max":5, "fracmin":0.635, "fracmax":0.806}, + ] + + profile_used = 1 + profile = profiles[profile_used] + + nominal_output_hz = profile["target_output_frequency"] # This generates the needed header files read later by sim_sw_pll_lut # 12.288MHz with 48kHz ref (note also works with 16kHz ref), +-500PPM, 30.4Hz steps, 826B LUT size get_pll_solution(24000000, nominal_output_hz, max_denom=80, min_F=200, ppm_max=5, fracmin=0.695, fracmax=0.905) output_frequency = nominal_output_hz - nominal_control_rate_hz = 93.75 + nominal_control_rate_hz = profile["nominal_ref_frequency"] / 512 simulation_iterations = 100 Kp = 0.0 Ki = 1.0 From 99efe2f577cad25546295ef389d656aff059706a Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 30 Nov 2023 17:30:31 +0000 Subject: [PATCH 101/118] rst updates --- README.rst | 4 +- doc/rst/sw_pll.rst | 98 +++++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/README.rst b/README.rst index d0a6c353..fe32ea5f 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ or *i2s_slave_lut* which uses the XK-VOICE-SQ66 board:: For simple_xxx.xe, to see the PLL lock, put one scope probe on either LRCLK/BCLK (reference input) and the other on PORT_I2S_DAC_DATA to see the recovered clock which has been hardware divided back down to the same rate as the input reference clock. -For i2s_slave_lut.xe you will need to connect a 48kHz I2S master to the LRCLK, BCLK pins. You may then observe the I2S input being +For i2s_slave_lut.xe you will need to connect a 48kHz I2S master to the LRCLK, BCLK pins. You may then observe the I2S input data being looped back to the output and the MCLK being generated. A divided version of MCLK is output on PORT_I2S_DATA2 which allows direct comparison of the input reference (LRCLK) with the recovered clock at the same, and locked, frequency. @@ -67,5 +67,5 @@ direct comparison of the input reference (LRCLK) with the recovered clock at the Generating new PLL configurations ********************************* -Please see `doc/sw_pll.rst` for further details on how to design and build new sw_pll configurations. This covers the tradeoff between lock range, +Please see `doc/rst/sw_pll.rst` for further details on how to design and build new sw_pll configurations. This covers the tradeoff between lock range, oscillator noise and resource usage. \ No newline at end of file diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 3b6ec2f0..87c0ae4f 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -28,7 +28,6 @@ over a certain range. It also provides a lower level API to allow tracking of vi physical signals. There are two types of PLL, or specifically Digitally Controlled Oscillators (DCO), supported in this library. - There are trade-offs between the two types of DCO which are summarised in the following table. .. list-table:: LUT vs SDM DCO trade-offs @@ -39,11 +38,11 @@ There are trade-offs between the two types of DCO which are summarised in the fo - LUT DCO - SDM DCO * - Jitter - - Low - ~1-2 ns - - Very Low - ~10-50 ps + - Low, ~1-2 ns + - Very Low, ~10-50 ps * - Memory Usage - - Moderate - 3 kB - - Low - 1 kB + - Low, ~2.5 kB + - Low, ~2 kB * - MIPS Usage - Low - ~1 - High - ~50 @@ -73,7 +72,7 @@ settings once locked. Note that the actual range and number of steps is highly c .. figure:: ./images/lut_dco_range.png :width: 100% - LUT discrete output frequencies + Example of LUT Discrete Output Frequencies The index into the LUT is controlled by a @@ -132,15 +131,12 @@ spread of the noise floor can be seen in the following diagrams. SDM noise plot when when tracking a constant input frequency -This document provides a guide to generating the LUT and configuring the available parameters to -reach the appropriate compromise of performance and resource usage for your application. - Simulation Model ---------------- -Running the PI simulation and LUT generation script -................................................... +Contents +........ In the ``python/sw_pll`` directory you will find multiple files:: @@ -153,20 +149,38 @@ In the ``python/sw_pll`` directory you will find multiple files:: ├── pll_calc.py └── sw_pll_sim.py +These are all installable as a Python PIP module by running ``pip install -e .`` from the root of the repo. + +Typically you do not need to access any file other than ``sw_pll_sim.py`` which brings in the other files as modules when run. + +``analysis_tools.py`` contains audio analysis tools for assessing the frequency modulation of a tone from the jitter in +the recovered clock. + +``controller_model.py`` models the PI controllers used in the Software PLL system. + +``dco_model.py`` contains a model of the LUT and SDM digitally controlled oscillators. + +``pll_calc.py`` models the Phase Frequency Detector which is used when inputting a reference clock to the Software PLL. + + +``app_pll_model.py`` models the Application PLL and allows reading/writing include files suitable for inclusion into XCORE +firmware projects. ``pll_calc.py`` is the command line script that generates the LUT. It is quite a complex to use script which requires in depth knowledge of the operation of the App PLL. Instead, it is recommended to use ``app_pll_model.py`` which calls ``pll_calc.py`` which wraps the script with defaults. -By running `sw_pll_sim.py` a number of operations will take place: +Running the PI simulation and LUT generation script +................................................... - - The ``fractions.h`` LUT include file will be generated. - - The ``register_setup.h`` PLL configuration file will be generated. - - A graphical view of the LUT settings ``sw_pll_range.png`` showing index vs. output frequency is generated. +By running ``sw_pll_sim.py`` a number of operations will take place: + + - The ``fractions.h`` LUT include file will be generated (LUT PLL only - this is not needed by SDM) + - The ``register_setup.h`` PLL configuration file will be generated for inclusion in your XCORE project. + - A graphical view of the LUT settings showing index vs. output frequency is generated. - A time domain simulation of the PI loop showing the response to steps and out of range reference inputs is run. - - A graphical view of the simulation is saved to ``pll_step_response.png``. - - A wave file containing a 1 kHz modulated tone for offline analysis. Note that ``ppm_shifts`` will need to be set to ``()`` otherwise it will contain the injected PPM deviations as part of the step response test. - - A zoomed-in log FFT plot of the 1 kHz tone to see how the LUT frequency steps affect a pure tone. The same note applies as the above item. - - A summary report of the PLL range is printed to the console. + - A wave file containing a 1 kHz modulated tone for offline analysis. + - A zoomed-in log FFT plot of the 1 kHz tone to see how the LUT frequency steps affect a pure tone. + - A summary report of the PLL range is also printed to the console. The directory listing following running of ``sw_pll_sim.py`` will be added to as follows:: @@ -189,7 +203,8 @@ loop is called to center this noise around different frequencies or decrease the manage the amplitude of this artifact. -Here you can see the step response of the control loop below. You can see it track smaller step changes but for the +Here you can see the step response of the control loop when the target frequency is changed during the simulation. +You can see it track smaller step changes but for the larger steps it can be seen to clip and not reach the input step, which is larger than the LUT size will allow. The LUT size can be increased if needed to accommodate a wider range. @@ -199,28 +214,12 @@ a handful of control loop iterations. .. image:: ./images/pll_step_response.png :width: 100% -Note that each time you run ``sw_pll_sim.py`` and the ``fractions.h`` file is produced, a short report will be produced that indicates the achieved range of settings. -Below is a typical report showing what information is summarised:: - - $ rm -f fractions.h && python sw_pll_sim.py - Running: lib_sw_pll/python/sw_pll/pll_calc.py -i 24.0 -a -m 80 -t 12.288 -p 6.0 -e 5 -r --fracmin 0.695 --fracmax 0.905 --header - Available F values: [30, 32, 77, 79, 116, 118, 122, 159, 163, 165, 200, 204, 208, 245, 286, 331, 417] - output_frequency: 12288000.0, vco_freq: 2457600000.0, F: 203, R: 1, f: 3, p: 4, OD: 1, ACD: 24, ppm: 0.0 - PLL register settings F: 203, R: 1, OD: 1, ACD: 24, f: 3, p: 4 - min_freq: 12281739Hz - mid_freq: 12288000Hz - max_freq: 12294286Hz - average step size: 30.3791Hz, PPM: 2.47226 - PPM range: -509.771 - PPM range: +511.533 - LUT entries: 413 (826 bytes) - Tuning the Software PLL ----------------------- -Tuning the PI controller -........................ +PI controller +............. Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp*, *Ki* and optionally *Kii* can be found in the functions `run_lut_sw_pll_sim()` and `run_sd_sw_pll_sim()`. @@ -290,14 +289,14 @@ Search for ``profiles`` and ``profile_choice`` in this file. Change profile choi - 30.2 - 166 -Note that the PLL actually multiplies the input crystal, not the reference input clock. A change in the reference input clock only affects the control loop -and its associated constants such as how often the PI loop is called. +Note that the physical PLL actually multiplies the input crystal, not the reference input clock. +It is the PFD and software control loop that detects the frequency error and controls the fractional register to make the PLL track the input. +A change in the reference input clock parameter only affects the control loop and its associated constants such as how often the PI controller is called. Custom LUT Generation Guidance .............................. - The fractions lookup table is a trade-off between PPM range and frequency step size. Frequency step size will affect jitter amplitude as it is the amount that the PLL will change frequency when it needs to adjust. Typically, the locked control loop will slowly oscillate between two values that @@ -305,8 +304,8 @@ straddle the target frequency, depending on input frequency. Small discontinuities in the LUT may be experienced in certain ranges, particularly close to 0.5 fractional values, so it is preferable to keep in the lower or upper half of the fractional range. However the LUT table is always monotonic -and so control instability will not occur for that reason. The range of the ``sw_pll`` can be seen -in the ``sw_pll_range.png`` image. It should be a reasonably linear response without significant +and so control instability will not occur for that reason. The range of the LUT Software PLL can be seen +in the ``lut_dco_range.png`` image. It should be a reasonably linear response without significant discontinuities. If not, try moving the range towards 0.0 or 1.0 where fewer discontinuities will be observed. @@ -314,7 +313,7 @@ Steps to vary the LUT PPM range and frequency step size ....................................................... -1. Ascertain your target PPM range, step size and maximum tolerable table size. Each lookup value is 16b so the total size in bytes is 2 x n. +1. Ascertain your target PPM range, step size and maximum tolerable table size. Each lookup value is 16 bits so the total size in bytes is 2 x n. 2. Start with the given example values and run the generator to see if the above three parameters meet your needs. The values are reported by ``sw_pll_sim.py``. 3. If you need to increase the PPM range, you may either: - Decrease the ``min_F`` to allow the fractional value to have a greater effect. This will also increase step size. It will not affect the LUT size. @@ -325,7 +324,7 @@ Steps to vary the LUT PPM range and frequency step size 5. If the +/-PPM range is not symmetrical and you wish it to be, then adjust the ``fracmin`` and ``fracmax`` values around the center point that the PLL finder algorithm has found. For example if the -PPM range is to great, increase ``fracmin`` and if the +PPM range is too great, decrease the ``fracmax`` value. -Note when the process has completed, please inspect the ``sw_pll_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. +Note when the process has completed, please inspect the ``lut_dco_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. SDM Available Configurations @@ -336,7 +335,6 @@ below. There are two target PLL output frequencies and two options for SDM updat is available. - .. list-table:: SDM DCO configurations :widths: 50 50 50 50 50 :header-rows: 1 @@ -382,7 +380,7 @@ For further information, either consult the ``sw_pll.h`` API file (included at t Simple Usage Example Resource Setup ----------------------------------- -The xcore-ai has a number of resources on chip. In the `simple` examples both `clock blocks` and `ports` are connected together to provide an input to +The xcore-ai device has a number of resources on chip. In the `simple` examples both `clock blocks` and `ports` are connected together to provide an input to the PFD and additionally provide a scaled output clock. The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intrinsic functions in ``lib_xcore``. To help visualise how these resources work together, please see the below diagram. @@ -399,12 +397,12 @@ The Application Programmer Interface (API) for the Software PLL is shown below. In addition to the standard API which takes a clock counting input, for applications where the PLL is to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level -API allows the Software PLL to track an arbitrary clock source which is calculated by another means. +API allows the Software PLL to track an arbitrary clock source which is calculated by another means such as received packets. LUT Based PLL API ................. -The LUT based API are functions designed to be called from an audio loop. Typically the functions can take up to 210 instruction cycles when control occurs and just a few 10s of cycles when control does not occur. If run at a rate of 48 kHz then it will consume approximately 1 MIPS. +The LUT based API are functions designed to be called from an audio loop. Typically the functions can take up to 210 instruction cycles when control occurs and just a few 10s of cycles when control does not occur. If run at a rate of 48 kHz then it will consume approximately 1 MIPS on average. .. doxygengroup:: sw_pll_lut :content-only: @@ -412,7 +410,7 @@ The LUT based API are functions designed to be called from an audio loop. Typica SDM Based PLL API ................. -All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and it is expected that the user provide the fork (par) and call the SDM in a loop. A typical idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received periodically. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. +All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and register write and it is expected that the user provide the fork (par) and call to the SDM. A typical design idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. The control part of the SDM SW PLL takes 75 instruction cycles when active and a few 10 s of cycles when inactive so you will need to budget around 1 MIPS for this. From bad7206281b6a843ce28168ac230a22ee573e463 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 08:34:57 +0000 Subject: [PATCH 102/118] sw_pll_reset -> sw_pll_lut_reset --- lib_sw_pll/api/sw_pll.h | 72 ++++++++++++++++++----------------------- settings.yml | 2 +- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 32145f00..e3437c52 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -17,46 +17,6 @@ #include "sw_pll_pfd.h" #include "sw_pll_sdm.h" -/** - * \addtogroup sw_pll_general sw_pll_general - * - * The public API for using the Software PLL. - * @{ - */ - -/** - * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. - * - * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. - * - * \param sw_pll Pointer to the struct to be initialised. - * \param Kp New Kp in sw_pll_15q16_t format. - * \param Ki New Ki in sw_pll_15q16_t format. - * \param Kii New Kii in sw_pll_15q16_t format. - * \param num_lut_entries The number of elements in the sw_pll LUT. - */ -static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) -{ - sw_pll->pi_state.Kp = Kp; - sw_pll->pi_state.Ki = Ki; - sw_pll->pi_state.Kii = Kii; - - sw_pll->pi_state.error_accum = 0; - sw_pll->pi_state.error_accum_accum = 0; - if(Ki){ - sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT - }else{ - sw_pll->pi_state.i_windup_limit = 0; - } - if(Kii){ - sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT - }else{ - sw_pll->pi_state.ii_windup_limit = 0; - } -} - -/**@}*/ // END: addtogroup sw_pll_general - /** * \addtogroup sw_pll_lut sw_pll_lut @@ -155,6 +115,38 @@ sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const */ sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); + +/** + * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. + * + * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. + * + * \param sw_pll Pointer to the struct to be initialised. + * \param Kp New Kp in sw_pll_15q16_t format. + * \param Ki New Ki in sw_pll_15q16_t format. + * \param Kii New Kii in sw_pll_15q16_t format. + * \param num_lut_entries The number of elements in the sw_pll LUT. + */ +static inline void sw_pll_lut_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries) +{ + sw_pll->pi_state.Kp = Kp; + sw_pll->pi_state.Ki = Ki; + sw_pll->pi_state.Kii = Kii; + + sw_pll->pi_state.error_accum = 0; + sw_pll->pi_state.error_accum_accum = 0; + if(Ki){ + sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT + }else{ + sw_pll->pi_state.i_windup_limit = 0; + } + if(Kii){ + sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT + }else{ + sw_pll->pi_state.ii_windup_limit = 0; + } +} + /**@}*/ // END: addtogroup sw_pll_lut diff --git a/settings.yml b/settings.yml index 8c350d69..512e3cbd 100644 --- a/settings.yml +++ b/settings.yml @@ -16,5 +16,5 @@ documentation: pdfs: doc/index: pdf_title: '{{project}} Programming Guide \newline' - pdf_filename: '{{project}}_progamming_guide_v{{version}}' + pdf_filename: '{{project}}_programming_guide_v{{version}}' \ No newline at end of file From d07932da7e1114b1952ff4335b15a61da818cfb9 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 08:36:53 +0000 Subject: [PATCH 103/118] Missed api usage --- lib_sw_pll/src/sw_pll.c | 2 +- lib_sw_pll/src/sw_pll_sdm.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll.c index 7abe9df0..62afb1da 100644 --- a/lib_sw_pll/src/sw_pll.c +++ b/lib_sw_pll/src/sw_pll.c @@ -93,7 +93,7 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, lut_table_base[nominal_lut_idx]); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, Kii, num_lut_entries); + sw_pll_lut_reset(sw_pll, Kp, Ki, Kii, num_lut_entries); // Setup general controller state sw_pll->lock_status = SW_PLL_UNLOCKED_LOW; diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index e7787355..ad1372a9 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -24,7 +24,8 @@ void sw_pll_sdm_init( sw_pll_state_t * const sw_pll, (uint16_t)(app_pll_frac_reg_val & 0xffff)); // Setup sw_pll with supplied user paramaters - sw_pll_reset(sw_pll, Kp, Ki, Kii, 0); + sw_pll_lut_reset(sw_pll, Kp, Ki, Kii, 0); + // override windup limits sw_pll->pi_state.i_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; sw_pll->pi_state.ii_windup_limit = SW_PLL_SDM_UPPER_LIMIT - SW_PLL_SDM_LOWER_LIMIT; sw_pll->sdm_state.ctrl_mid_point = ctrl_mid_point; From 760e8d92e0ce82464fcec224065648fd158dc65b Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 10:21:56 +0000 Subject: [PATCH 104/118] Tidy the SDM API --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 7 +-- lib_sw_pll/api/sw_pll.h | 59 ++++++++++++--------- lib_sw_pll/src/{sw_pll.c => sw_pll_lut.c} | 0 lib_sw_pll/src/sw_pll_sdm.c | 13 ++++- lib_sw_pll/src/sw_pll_sdm.h | 38 +++++++++---- tests/test_app_sdm_ctrl/main.c | 3 ++ tests/test_app_sdm_dco/main.c | 2 +- 7 files changed, 79 insertions(+), 43 deletions(-) rename lib_sw_pll/src/{sw_pll.c => sw_pll_lut.c} (100%) diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 2a512b8d..82a03ec8 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -37,7 +37,6 @@ void sdm_task(chanend_t c_sdm_control){ // the first control value has been received. This avoids issues with // channel lockup if two tasks (eg. init and SDM) try to write at the same // time. - uint32_t frac_val = 0; while(running){ // Poll for new SDM control value @@ -64,13 +63,9 @@ void sdm_task(chanend_t c_sdm_control){ // This implements a timing barrier and keeps // the loop rate constant. hwtimer_wait_until(tmr, trigger_time); + sw_pll_do_sigma_delta(&sdm_state, this_tile, sdm_in); - sw_pll_write_frac_reg(this_tile, frac_val); trigger_time += sdm_interval; - - // calc new sdm_out and then schedule to write - int32_t sdm_out = sw_pll_do_sigma_delta(&sdm_state, sdm_in); - frac_val = sw_pll_sdm_out_to_frac_reg(sdm_out); } } } diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index e3437c52..fc3d9e32 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -74,6 +74,8 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, /** * sw_pll LUT version control function. + * + * It implements the PDF, controller and DCO output. * * This must be called periodically for every reference clock transition. * Typically, in an audio system, this would be at the I2S or reference clock input rate. @@ -85,7 +87,7 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, * If the precise sampling point of mclk is not easily controlled (for example in an I2S callback) * then an additional timer count may be passed in which will scale the mclk count. See i2s_slave * example to show how this is done. This will help reduce input jitter which, in turn, relates - * to output jitter being a PLL. + * to reduced output jitter. * * \param sw_pll Pointer to the struct to be initialised. * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_lut_do_control. @@ -97,14 +99,14 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, * this value is only updated when the control loop has run. * The type is sw_pll_lock_status_t. */ -sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); +sw_pll_lock_status_t sw_pll_lut_do_control_pfd(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); /** * low level sw_pll control function for use as pure PLL control loop. * * This must be called periodically. * - * When this is called, the control loop will be executed every n times (set by init) and the + * When this is called, the control loop will be executed every time and the * application PLL will be adjusted to minimise the error seen on the input error value. * * \param sw_pll Pointer to the struct to be initialised. @@ -202,6 +204,8 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, /** * sw_pll_sdm_do_control control function. + * + * It implements the PDF and controller and generates a DCO control value for the SDM. * * This must be called periodically for every reference clock transition. * Typically, in an audio system, this would be at the I2S or reference clock input rate. @@ -210,15 +214,15 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, * When this is called, the control loop will be executed every n times (set by init) and the * Sigma Delta Modulator control value will be set according the error seen on the mclk count value. * - * If control is executed, TRUE is returned from the function. + * If control is executed, TRUE is returned from the function and the value can be sent to the SDM. * The most recent calculated control output value can be found written to sw_pll->sdm_state.current_ctrl_val. * * If the precise sampling point of mclk is not easily controlled (for example in an I2S callback) * then an additional timer count may be passed in which will scale the mclk count. See i2s_slave * example to show how this is done. This will help reduce input jitter which, in turn, relates - * to output jitter being a PLL. + * to reduced output jitter. * - * \param sw_pll Pointer to the struct to be initialised. + * \param sw_pll Pointer to the sw_pll state struct. * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_sdm_do_control. * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_sdm_do_control. This value * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the @@ -233,35 +237,42 @@ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt * * This must be called periodically. * - * Takes the raw error input and applies the PI controller algorithm + * Takes the raw error input and applies the PI controller algorithm. + * The most recent calculated control output value can be found written to sw_pll->sdm_state.current_ctrl_val. * - * \param sw_pll Pointer to the struct to be initialised. + * \param sw_pll Pointer to the sw_pll state struct. * \param error 16b signed input error value - * \returns The PI processed error + * \returns The controller lock status */ -int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); +sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error); -/** - * low level sw_pll_sdm post control processing function. - * - * This must be called after sw_pll_sdm_do_control_from_error. - * - * Takes the PI processed error and applies a low pass filter and calaculates the Sigma Delta Modulator - * control signal. It also checks the range and sets the PLL lock status if exceeded. - * - * \param sw_pll Pointer to the struct to be initialised. - * \param error 32b signed input error value from PI controller - * \returns The Sigma Delta Modulator Control signal - */ -int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); /** * Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM * is often run in a dedicated thread which could be on a remote tile. * - * \param sdm_state Pointer to the struct to be initialised. + * \param sw_pll Pointer to the SDM state struct. */ void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state); +/** + * Performs the Sigma Delta Modulation from a control input. + * It performs the SDM algorithm, converts the output to a fractional register setting + * and then writes the value to the PLL fractional register. + * Is typically called in a constant period fast loop and run from a dedicated thread which could be on a remote tile. + * + * NOTE: Attempting to write the PLL fractional register from more than + * one logical core at the same time may result in channel lock-up. + * Please ensure the that PLL initiaisation has completed before + * the SDM task writes to the register. The provided `simple_sdm` example + * implements a method for doing this. + * + * \param sw_pll Pointer to the SDM state struct. + * \param this_tile The ID of the xcore tile that is doing the write. + * Use get_local_tile_id() to obtain this. + * \param sdm_control_in Current control value. + */ +static inline void sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, tileref_t this_tile, int32_t sdm_control_in); + /**@}*/ // END: addtogroup sw_pll_sdm diff --git a/lib_sw_pll/src/sw_pll.c b/lib_sw_pll/src/sw_pll_lut.c similarity index 100% rename from lib_sw_pll/src/sw_pll.c rename to lib_sw_pll/src/sw_pll_lut.c diff --git a/lib_sw_pll/src/sw_pll_sdm.c b/lib_sw_pll/src/sw_pll_sdm.c index ad1372a9..d126f494 100644 --- a/lib_sw_pll/src/sw_pll_sdm.c +++ b/lib_sw_pll/src/sw_pll_sdm.c @@ -83,6 +83,16 @@ int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t erro +__attribute__((always_inline)) +inline sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error) +{ + int32_t ctrl_error = sw_pll_do_pi_ctrl(sw_pll, error); + sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, ctrl_error); + + return sw_pll->lock_status; +} + + bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_clk_pt) { bool control_done = true; @@ -106,8 +116,7 @@ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt else { sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt); - int32_t error = sw_pll_do_pi_ctrl(sw_pll, -sw_pll->pfd_state.mclk_diff); - sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, error); + sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff); // Save for next iteration to calc diff sw_pll->pfd_state.mclk_pt_last = mclk_pt; diff --git a/lib_sw_pll/src/sw_pll_sdm.h b/lib_sw_pll/src/sw_pll_sdm.h index 5df00fd3..4860b9ea 100644 --- a/lib_sw_pll/src/sw_pll_sdm.h +++ b/lib_sw_pll/src/sw_pll_sdm.h @@ -10,15 +10,9 @@ typedef int tileref_t; -/** - * \addtogroup sw_pll_sdm sw_pll_sdm - * - * The public API for using the Software PLL. - * @{ - */ /** - * low level sw_pll_do_sigma_delta function that turns a control signal + * low level sw_pll_calc_sigma_delta function that turns a control signal * into a Sigma Delta Modulated output signal. * * @@ -28,7 +22,7 @@ typedef int tileref_t; * \returns Sigma Delta modulated signal. */ __attribute__((always_inline)) -static inline int32_t sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t sdm_in){ +static inline int32_t sw_pll_calc_sigma_delta(sw_pll_sdm_state_t *sdm_state, int32_t sdm_in){ // Third order, 9 level output delta sigma. 20 bit unsigned input. int32_t sdm_out = ((sdm_state->ds_x3<<4) + (sdm_state->ds_x3<<1)) >> 13; if (sdm_out > 8){ @@ -73,7 +67,7 @@ static inline uint32_t sw_pll_sdm_out_to_frac_reg(int32_t sdm_out){ * low level sw_pll_write_frac_reg function that writes the PLL fractional * register. * - * NOTE: attempting to write the PLL fractional register from more than + * NOTE: Attempting to write the PLL fractional register from more than * one logical core at the same time may result in channel lock-up. * Please ensure the that PLL initiaisation has completed before * the SDM task writes to the register. The provided example @@ -87,9 +81,33 @@ static inline void sw_pll_write_frac_reg(tileref_t this_tile, uint32_t frac_val) write_sswitch_reg(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val); } -/**@}*/ // END: addtogroup sw_pll_sdm +/** + * Performs the Sigma Delta Modulation from a control input. + * It performs the SDM algorithm, converts the output to a fractional register setting + * and then writes the value to the PLL fractional register. + * Is typically called in a constant period fast loop and run from a dedicated thread which could be on a remote tile. + * + * NOTE: Attempting to write the PLL fractional register from more than + * one logical core at the same time may result in channel lock-up. + * Please ensure the that PLL initiaisation has completed before + * the SDM task writes to the register. The provided `simple_sdm` example + * implements a method for doing this. + * + * \param sw_pll Pointer to the SDM state struct. + * \param this_tile The ID of the xcore tile that is doing the write. + * Use get_local_tile_id() to obtain this. + * \param sdm_control_in Current control value. + */ +__attribute__((always_inline)) +static inline void sw_pll_do_sigma_delta(sw_pll_sdm_state_t *sdm_state, tileref_t this_tile, int32_t sdm_control_in){ + + int32_t sdm_out = sw_pll_calc_sigma_delta(sdm_state, sdm_control_in); + uint32_t frac_val = sw_pll_sdm_out_to_frac_reg(sdm_out); + sw_pll_write_frac_reg(this_tile, frac_val); +} +// This is here to allow access without circular dependancies in the includes extern void sw_pll_app_pll_init(const unsigned tileid, const uint32_t app_pll_ctl_reg_val, const uint32_t app_pll_div_reg_val, diff --git a/tests/test_app_sdm_ctrl/main.c b/tests/test_app_sdm_ctrl/main.c index 75bff034..f76a9ed9 100644 --- a/tests/test_app_sdm_ctrl/main.c +++ b/tests/test_app_sdm_ctrl/main.c @@ -26,6 +26,9 @@ #define IN_LINE_SIZE 1000 +extern int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error); + + DECLARE_JOB(control_task, (int, char**, chanend_t)); void control_task(int argc, char** argv, chanend_t c_sdm_control) { diff --git a/tests/test_app_sdm_dco/main.c b/tests/test_app_sdm_dco/main.c index b8ac6660..0dab4979 100644 --- a/tests/test_app_sdm_dco/main.c +++ b/tests/test_app_sdm_dco/main.c @@ -51,7 +51,7 @@ int main(int argc, char** argv) { // calc new ds_out and then wait to write uint32_t t0 = get_reference_time(); - int32_t ds_out = sw_pll_do_sigma_delta(&sdm_state, ds_in); + int32_t ds_out = sw_pll_calc_sigma_delta(&sdm_state, ds_in); uint32_t frac_val = sw_pll_sdm_out_to_frac_reg(ds_out); uint32_t t1 = get_reference_time(); From 6e57aab06113da49dbbbf59ef564a8d0a58daf7c Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 10:54:25 +0000 Subject: [PATCH 105/118] API typo --- doc/rst/sw_pll.rst | 8 -------- lib_sw_pll/api/sw_pll.h | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 87c0ae4f..5520351c 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -426,11 +426,3 @@ An example of how to implement the threading, timing barrier and non-blocking ch .. doxygengroup:: sw_pll_sdm :content-only: - -Common API -.......... - -The common API covers an optional reset of the PI controller only for both LUT and SDM options. - -.. doxygengroup:: sw_pll_general - :content-only: \ No newline at end of file diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index fc3d9e32..0f2b8902 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -99,7 +99,7 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, * this value is only updated when the control loop has run. * The type is sw_pll_lock_status_t. */ -sw_pll_lock_status_t sw_pll_lut_do_control_pfd(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); +sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt, const uint16_t ref_pt); /** * low level sw_pll control function for use as pure PLL control loop. From b1e54d1d550faaf8a1259bd353b3949108e3d7c6 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 11:08:10 +0000 Subject: [PATCH 106/118] API doc tidy --- doc/rst/sw_pll.rst | 6 +++--- lib_sw_pll/api/sw_pll.h | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 5520351c..b01e7b48 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -393,9 +393,9 @@ To help visualise how these resources work together, please see the below diagra lib_sw_pll API -------------- -The Application Programmer Interface (API) for the Software PLL is shown below. It is split into common items needed for both LUT and SDM DCOs and items specific to each type of DCO. +The Application Programmer Interface (API) for the Software PLL is shown below. It is split into items specific to LUT and SDM DCOs . -In addition to the standard API which takes a clock counting input, for applications where the PLL is +In addition to the standard API which takes a clock counting input (implements the PFD), for applications where the PLL is to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level API allows the Software PLL to track an arbitrary clock source which is calculated by another means such as received packets. @@ -410,7 +410,7 @@ The LUT based API are functions designed to be called from an audio loop. Typica SDM Based PLL API ................. -All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and register write and it is expected that the user provide the fork (par) and call to the SDM. A typical design idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. +All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and register write and it is expected that the user provide the fork (par) and call to the SDM. A typical design idiom is to have the task running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. The control part of the SDM SW PLL takes 75 instruction cycles when active and a few 10 s of cycles when inactive so you will need to budget around 1 MIPS for this. diff --git a/lib_sw_pll/api/sw_pll.h b/lib_sw_pll/api/sw_pll.h index 0f2b8902..fe310764 100644 --- a/lib_sw_pll/api/sw_pll.h +++ b/lib_sw_pll/api/sw_pll.h @@ -39,7 +39,7 @@ * Note this is only used by sw_pll_lut_do_control. sw_pll_lut_do_control_from_error * calls the control loop every time so this is ignored. * \param pll_ratio Integer ratio between input reference clock and the PLL output. - * Only used by sw_pll_lut_do_control. Don't care otherwise. + * Only used by sw_pll_lut_do_control for the PFD. Don't care otherwise. * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_lut_do_control is called. * Pass in zero if you are sure the mclk sampling timing is precise. This * will disable the scaling of the mclk count inside sw_pll_lut_do_control. @@ -75,7 +75,7 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, /** * sw_pll LUT version control function. * - * It implements the PDF, controller and DCO output. + * It implements the PFD, controller and DCO output. * * This must be called periodically for every reference clock transition. * Typically, in an audio system, this would be at the I2S or reference clock input rate. @@ -89,7 +89,7 @@ void sw_pll_lut_init( sw_pll_state_t * const sw_pll, * example to show how this is done. This will help reduce input jitter which, in turn, relates * to reduced output jitter. * - * \param sw_pll Pointer to the struct to be initialised. + * \param sw_pll Pointer to the sw_pll state struct. * \param mclk_pt The 16b port timer count of mclk at the time of calling sw_pll_lut_do_control. * \param ref_pt The 16b port timer ref ount at the time of calling sw_pll_lut_do_control. This value * is ignored when the pll is initialised with a zero ref_clk_expected_inc and the @@ -109,7 +109,7 @@ sw_pll_lock_status_t sw_pll_lut_do_control(sw_pll_state_t * const sw_pll, const * When this is called, the control loop will be executed every time and the * application PLL will be adjusted to minimise the error seen on the input error value. * - * \param sw_pll Pointer to the struct to be initialised. + * \param sw_pll Pointer to the sw_pll state struct. * \param error 16b signed input error value * \returns The lock status of the PLL. Locked or unlocked high/low. Note that * this value is only updated when the control loop is running. @@ -121,9 +121,9 @@ sw_pll_lock_status_t sw_pll_lut_do_control_from_error(sw_pll_state_t * const sw_ /** * Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings. * - * Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset. + * Sets Kp, Ki and the windup limits. Note this resets the PFD accumulators too and so PI controller state is reset. * - * \param sw_pll Pointer to the struct to be initialised. + * \param sw_pll Pointer to the state struct to be reset. * \param Kp New Kp in sw_pll_15q16_t format. * \param Ki New Ki in sw_pll_15q16_t format. * \param Kii New Kii in sw_pll_15q16_t format. @@ -173,14 +173,14 @@ static inline void sw_pll_lut_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, s * Note this is only used by sw_pll_sdm_do_control. sw_pll_sdm_do_control_from_error * calls the control loop every time so this is ignored. * \param pll_ratio Integer ratio between input reference clock and the PLL output. - * Only used by sw_pll_sdm_do_control. Don't care otherwise. + * Only used by sw_pll_sdm_do_control in the PFD. Don't care otherwise. * \param ref_clk_expected_inc Expected ref clock increment each time sw_pll_sdm_do_control is called. * Pass in zero if you are sure the mclk sampling timing is precise. This * will disable the scaling of the mclk count inside sw_pll_sdm_do_control. * Only used by sw_pll_sdm_do_control. Don't care otherwise. * \param app_pll_ctl_reg_val The setting of the app pll control register. * \param app_pll_div_reg_val The setting of the app pll divider register. - * \param app_pll_frac_reg_val The setting of the app pll fractional register. + * \param app_pll_frac_reg_val The initial setting of the app pll fractional register. * \param ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally * close to halfway to allow symmetrical range. * \param ppm_range The pre-calculated PPM range. Used to determine the maximum deviation @@ -205,7 +205,7 @@ void sw_pll_sdm_init(sw_pll_state_t * const sw_pll, /** * sw_pll_sdm_do_control control function. * - * It implements the PDF and controller and generates a DCO control value for the SDM. + * It implements the PFD and controller and generates a DCO control value for the SDM. * * This must be called periodically for every reference clock transition. * Typically, in an audio system, this would be at the I2S or reference clock input rate. @@ -249,7 +249,7 @@ sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_ /** * Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM - * is often run in a dedicated thread which could be on a remote tile. + * is usually run in a dedicated thread which could be on a remote tile. * * \param sw_pll Pointer to the SDM state struct. */ From c0cdef26970723db6ad0e97f6d6989f82b9272e9 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 15:11:22 +0000 Subject: [PATCH 107/118] remove redundant file --- examples/simple_sdm/src/fractions.h | 333 ---------------------------- 1 file changed, 333 deletions(-) delete mode 100644 examples/simple_sdm/src/fractions.h diff --git a/examples/simple_sdm/src/fractions.h b/examples/simple_sdm/src/fractions.h deleted file mode 100644 index 7c6c4a01..00000000 --- a/examples/simple_sdm/src/fractions.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2023 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. -// Header file listing all fraction options for a max denominator of 80 -// These values to go in the bottom 16 bits of the secondary PLL fractional-n divider register. -short frac_values_80[327] = { -0x0405, // Index: 1638 Fraction: 5/6 = 0.8333 -0x414E, // Index: 1639 Fraction: 66/79 = 0.8354 -0x3C48, // Index: 1640 Fraction: 61/73 = 0.8356 -0x3742, // Index: 1641 Fraction: 56/67 = 0.8358 -0x323C, // Index: 1642 Fraction: 51/61 = 0.8361 -0x2D36, // Index: 1643 Fraction: 46/55 = 0.8364 -0x2830, // Index: 1644 Fraction: 41/49 = 0.8367 -0x232A, // Index: 1645 Fraction: 36/43 = 0.8372 -0x424F, // Index: 1646 Fraction: 67/80 = 0.8375 -0x1E24, // Index: 1647 Fraction: 31/37 = 0.8378 -0x3843, // Index: 1648 Fraction: 57/68 = 0.8382 -0x191E, // Index: 1649 Fraction: 26/31 = 0.8387 -0x2E37, // Index: 1650 Fraction: 47/56 = 0.8393 -0x1418, // Index: 1651 Fraction: 21/25 = 0.8400 -0x3944, // Index: 1652 Fraction: 58/69 = 0.8406 -0x242B, // Index: 1653 Fraction: 37/44 = 0.8409 -0x343E, // Index: 1654 Fraction: 53/63 = 0.8413 -0x0F12, // Index: 1655 Fraction: 16/19 = 0.8421 -0x3A45, // Index: 1656 Fraction: 59/70 = 0.8429 -0x2A32, // Index: 1657 Fraction: 43/51 = 0.8431 -0x1A1F, // Index: 1658 Fraction: 27/32 = 0.8438 -0x404C, // Index: 1659 Fraction: 65/77 = 0.8442 -0x252C, // Index: 1660 Fraction: 38/45 = 0.8444 -0x3039, // Index: 1661 Fraction: 49/58 = 0.8448 -0x3B46, // Index: 1662 Fraction: 60/71 = 0.8451 -0x0A0C, // Index: 1663 Fraction: 11/13 = 0.8462 -0x3C47, // Index: 1664 Fraction: 61/72 = 0.8472 -0x313A, // Index: 1665 Fraction: 50/59 = 0.8475 -0x262D, // Index: 1666 Fraction: 39/46 = 0.8478 -0x424E, // Index: 1667 Fraction: 67/79 = 0.8481 -0x1B20, // Index: 1668 Fraction: 28/33 = 0.8485 -0x2C34, // Index: 1669 Fraction: 45/53 = 0.8491 -0x3D48, // Index: 1670 Fraction: 62/73 = 0.8493 -0x1013, // Index: 1671 Fraction: 17/20 = 0.8500 -0x3842, // Index: 1672 Fraction: 57/67 = 0.8507 -0x272E, // Index: 1673 Fraction: 40/47 = 0.8511 -0x3E49, // Index: 1674 Fraction: 63/74 = 0.8514 -0x161A, // Index: 1675 Fraction: 23/27 = 0.8519 -0x333C, // Index: 1676 Fraction: 52/61 = 0.8525 -0x1C21, // Index: 1677 Fraction: 29/34 = 0.8529 -0x3F4A, // Index: 1678 Fraction: 64/75 = 0.8533 -0x2228, // Index: 1679 Fraction: 35/41 = 0.8537 -0x282F, // Index: 1680 Fraction: 41/48 = 0.8542 -0x2E36, // Index: 1681 Fraction: 47/55 = 0.8545 -0x343D, // Index: 1682 Fraction: 53/62 = 0.8548 -0x3A44, // Index: 1683 Fraction: 59/69 = 0.8551 -0x404B, // Index: 1684 Fraction: 65/76 = 0.8553 -0x0506, // Index: 1685 Fraction: 6/7 = 0.8571 -0x424D, // Index: 1686 Fraction: 67/78 = 0.8590 -0x3C46, // Index: 1687 Fraction: 61/71 = 0.8592 -0x363F, // Index: 1688 Fraction: 55/64 = 0.8594 -0x3038, // Index: 1689 Fraction: 49/57 = 0.8596 -0x2A31, // Index: 1690 Fraction: 43/50 = 0.8600 -0x242A, // Index: 1691 Fraction: 37/43 = 0.8605 -0x434E, // Index: 1692 Fraction: 68/79 = 0.8608 -0x1E23, // Index: 1693 Fraction: 31/36 = 0.8611 -0x3740, // Index: 1694 Fraction: 56/65 = 0.8615 -0x181C, // Index: 1695 Fraction: 25/29 = 0.8621 -0x444F, // Index: 1696 Fraction: 69/80 = 0.8625 -0x2B32, // Index: 1697 Fraction: 44/51 = 0.8627 -0x3E48, // Index: 1698 Fraction: 63/73 = 0.8630 -0x1215, // Index: 1699 Fraction: 19/22 = 0.8636 -0x323A, // Index: 1700 Fraction: 51/59 = 0.8644 -0x1F24, // Index: 1701 Fraction: 32/37 = 0.8649 -0x2C33, // Index: 1702 Fraction: 45/52 = 0.8654 -0x3942, // Index: 1703 Fraction: 58/67 = 0.8657 -0x0C0E, // Index: 1704 Fraction: 13/15 = 0.8667 -0x3A43, // Index: 1705 Fraction: 59/68 = 0.8676 -0x2D34, // Index: 1706 Fraction: 46/53 = 0.8679 -0x2025, // Index: 1707 Fraction: 33/38 = 0.8684 -0x343C, // Index: 1708 Fraction: 53/61 = 0.8689 -0x1316, // Index: 1709 Fraction: 20/23 = 0.8696 -0x424C, // Index: 1710 Fraction: 67/77 = 0.8701 -0x2E35, // Index: 1711 Fraction: 47/54 = 0.8704 -0x1A1E, // Index: 1712 Fraction: 27/31 = 0.8710 -0x3C45, // Index: 1713 Fraction: 61/70 = 0.8714 -0x2126, // Index: 1714 Fraction: 34/39 = 0.8718 -0x282E, // Index: 1715 Fraction: 41/47 = 0.8723 -0x2F36, // Index: 1716 Fraction: 48/55 = 0.8727 -0x363E, // Index: 1717 Fraction: 55/63 = 0.8730 -0x3D46, // Index: 1718 Fraction: 62/71 = 0.8732 -0x444E, // Index: 1719 Fraction: 69/79 = 0.8734 -0x0607, // Index: 1720 Fraction: 7/8 = 0.8750 -0x3F48, // Index: 1721 Fraction: 64/73 = 0.8767 -0x3840, // Index: 1722 Fraction: 57/65 = 0.8769 -0x3138, // Index: 1723 Fraction: 50/57 = 0.8772 -0x2A30, // Index: 1724 Fraction: 43/49 = 0.8776 -0x2328, // Index: 1725 Fraction: 36/41 = 0.8780 -0x4049, // Index: 1726 Fraction: 65/74 = 0.8784 -0x1C20, // Index: 1727 Fraction: 29/33 = 0.8788 -0x3239, // Index: 1728 Fraction: 51/58 = 0.8793 -0x1518, // Index: 1729 Fraction: 22/25 = 0.8800 -0x3A42, // Index: 1730 Fraction: 59/67 = 0.8806 -0x2429, // Index: 1731 Fraction: 37/42 = 0.8810 -0x333A, // Index: 1732 Fraction: 52/59 = 0.8814 -0x424B, // Index: 1733 Fraction: 67/76 = 0.8816 -0x0E10, // Index: 1734 Fraction: 15/17 = 0.8824 -0x434C, // Index: 1735 Fraction: 68/77 = 0.8831 -0x343B, // Index: 1736 Fraction: 53/60 = 0.8833 -0x252A, // Index: 1737 Fraction: 38/43 = 0.8837 -0x3C44, // Index: 1738 Fraction: 61/69 = 0.8841 -0x1619, // Index: 1739 Fraction: 23/26 = 0.8846 -0x353C, // Index: 1740 Fraction: 54/61 = 0.8852 -0x1E22, // Index: 1741 Fraction: 31/35 = 0.8857 -0x454E, // Index: 1742 Fraction: 70/79 = 0.8861 -0x262B, // Index: 1743 Fraction: 39/44 = 0.8864 -0x2E34, // Index: 1744 Fraction: 47/53 = 0.8868 -0x363D, // Index: 1745 Fraction: 55/62 = 0.8871 -0x3E46, // Index: 1746 Fraction: 63/71 = 0.8873 -0x464F, // Index: 1747 Fraction: 71/80 = 0.8875 -0x0708, // Index: 1748 Fraction: 8/9 = 0.8889 -0x4048, // Index: 1749 Fraction: 65/73 = 0.8904 -0x383F, // Index: 1750 Fraction: 57/64 = 0.8906 -0x3036, // Index: 1751 Fraction: 49/55 = 0.8909 -0x282D, // Index: 1752 Fraction: 41/46 = 0.8913 -0x2024, // Index: 1753 Fraction: 33/37 = 0.8919 -0x3940, // Index: 1754 Fraction: 58/65 = 0.8923 -0x181B, // Index: 1755 Fraction: 25/28 = 0.8929 -0x424A, // Index: 1756 Fraction: 67/75 = 0.8933 -0x292E, // Index: 1757 Fraction: 42/47 = 0.8936 -0x3A41, // Index: 1758 Fraction: 59/66 = 0.8939 -0x1012, // Index: 1759 Fraction: 17/19 = 0.8947 -0x3B42, // Index: 1760 Fraction: 60/67 = 0.8955 -0x2A2F, // Index: 1761 Fraction: 43/48 = 0.8958 -0x444C, // Index: 1762 Fraction: 69/77 = 0.8961 -0x191C, // Index: 1763 Fraction: 26/29 = 0.8966 -0x3C43, // Index: 1764 Fraction: 61/68 = 0.8971 -0x2226, // Index: 1765 Fraction: 35/39 = 0.8974 -0x2B30, // Index: 1766 Fraction: 44/49 = 0.8980 -0x343A, // Index: 1767 Fraction: 53/59 = 0.8983 -0x3D44, // Index: 1768 Fraction: 62/69 = 0.8986 -0x464E, // Index: 1769 Fraction: 71/79 = 0.8987 -0x0809, // Index: 1770 Fraction: 9/10 = 0.9000 -0x3F46, // Index: 1771 Fraction: 64/71 = 0.9014 -0x363C, // Index: 1772 Fraction: 55/61 = 0.9016 -0x2D32, // Index: 1773 Fraction: 46/51 = 0.9020 -0x2428, // Index: 1774 Fraction: 37/41 = 0.9024 -0x4047, // Index: 1775 Fraction: 65/72 = 0.9028 -0x1B1E, // Index: 1776 Fraction: 28/31 = 0.9032 -0x2E33, // Index: 1777 Fraction: 47/52 = 0.9038 -0x4148, // Index: 1778 Fraction: 66/73 = 0.9041 -0x1214, // Index: 1779 Fraction: 19/21 = 0.9048 -0x4249, // Index: 1780 Fraction: 67/74 = 0.9054 -0x2F34, // Index: 1781 Fraction: 48/53 = 0.9057 -0x1C1F, // Index: 1782 Fraction: 29/32 = 0.9062 -0x434A, // Index: 1783 Fraction: 68/75 = 0.9067 -0x262A, // Index: 1784 Fraction: 39/43 = 0.9070 -0x3035, // Index: 1785 Fraction: 49/54 = 0.9074 -0x3A40, // Index: 1786 Fraction: 59/65 = 0.9077 -0x444B, // Index: 1787 Fraction: 69/76 = 0.9079 -0x090A, // Index: 1788 Fraction: 10/11 = 0.9091 -0x464D, // Index: 1789 Fraction: 71/78 = 0.9103 -0x3C42, // Index: 1790 Fraction: 61/67 = 0.9104 -0x3237, // Index: 1791 Fraction: 51/56 = 0.9107 -0x282C, // Index: 1792 Fraction: 41/45 = 0.9111 -0x474E, // Index: 1793 Fraction: 72/79 = 0.9114 -0x1E21, // Index: 1794 Fraction: 31/34 = 0.9118 -0x3338, // Index: 1795 Fraction: 52/57 = 0.9123 -0x484F, // Index: 1796 Fraction: 73/80 = 0.9125 -0x1416, // Index: 1797 Fraction: 21/23 = 0.9130 -0x3439, // Index: 1798 Fraction: 53/58 = 0.9138 -0x1F22, // Index: 1799 Fraction: 32/35 = 0.9143 -0x2A2E, // Index: 1800 Fraction: 43/47 = 0.9149 -0x353A, // Index: 1801 Fraction: 54/59 = 0.9153 -0x4046, // Index: 1802 Fraction: 65/71 = 0.9155 -0x0A0B, // Index: 1803 Fraction: 11/12 = 0.9167 -0x4248, // Index: 1804 Fraction: 67/73 = 0.9178 -0x373C, // Index: 1805 Fraction: 56/61 = 0.9180 -0x2C30, // Index: 1806 Fraction: 45/49 = 0.9184 -0x2124, // Index: 1807 Fraction: 34/37 = 0.9189 -0x383D, // Index: 1808 Fraction: 57/62 = 0.9194 -0x1618, // Index: 1809 Fraction: 23/25 = 0.9200 -0x393E, // Index: 1810 Fraction: 58/63 = 0.9206 -0x2225, // Index: 1811 Fraction: 35/38 = 0.9211 -0x2E32, // Index: 1812 Fraction: 47/51 = 0.9216 -0x3A3F, // Index: 1813 Fraction: 59/64 = 0.9219 -0x464C, // Index: 1814 Fraction: 71/77 = 0.9221 -0x0B0C, // Index: 1815 Fraction: 12/13 = 0.9231 -0x484E, // Index: 1816 Fraction: 73/79 = 0.9241 -0x3C41, // Index: 1817 Fraction: 61/66 = 0.9242 -0x3034, // Index: 1818 Fraction: 49/53 = 0.9245 -0x2427, // Index: 1819 Fraction: 37/40 = 0.9250 -0x3D42, // Index: 1820 Fraction: 62/67 = 0.9254 -0x181A, // Index: 1821 Fraction: 25/27 = 0.9259 -0x3E43, // Index: 1822 Fraction: 63/68 = 0.9265 -0x2528, // Index: 1823 Fraction: 38/41 = 0.9268 -0x3236, // Index: 1824 Fraction: 51/55 = 0.9273 -0x3F44, // Index: 1825 Fraction: 64/69 = 0.9275 -0x0C0D, // Index: 1826 Fraction: 13/14 = 0.9286 -0x4146, // Index: 1827 Fraction: 66/71 = 0.9296 -0x3438, // Index: 1828 Fraction: 53/57 = 0.9298 -0x272A, // Index: 1829 Fraction: 40/43 = 0.9302 -0x4247, // Index: 1830 Fraction: 67/72 = 0.9306 -0x1A1C, // Index: 1831 Fraction: 27/29 = 0.9310 -0x4348, // Index: 1832 Fraction: 68/73 = 0.9315 -0x282B, // Index: 1833 Fraction: 41/44 = 0.9318 -0x363A, // Index: 1834 Fraction: 55/59 = 0.9322 -0x4449, // Index: 1835 Fraction: 69/74 = 0.9324 -0x0D0E, // Index: 1836 Fraction: 14/15 = 0.9333 -0x464B, // Index: 1837 Fraction: 71/76 = 0.9342 -0x383C, // Index: 1838 Fraction: 57/61 = 0.9344 -0x2A2D, // Index: 1839 Fraction: 43/46 = 0.9348 -0x474C, // Index: 1840 Fraction: 72/77 = 0.9351 -0x1C1E, // Index: 1841 Fraction: 29/31 = 0.9355 -0x484D, // Index: 1842 Fraction: 73/78 = 0.9359 -0x2B2E, // Index: 1843 Fraction: 44/47 = 0.9362 -0x3A3E, // Index: 1844 Fraction: 59/63 = 0.9365 -0x494E, // Index: 1845 Fraction: 74/79 = 0.9367 -0x0E0F, // Index: 1846 Fraction: 15/16 = 0.9375 -0x3C40, // Index: 1847 Fraction: 61/65 = 0.9385 -0x2D30, // Index: 1848 Fraction: 46/49 = 0.9388 -0x1E20, // Index: 1849 Fraction: 31/33 = 0.9394 -0x2E31, // Index: 1850 Fraction: 47/50 = 0.9400 -0x3E42, // Index: 1851 Fraction: 63/67 = 0.9403 -0x0F10, // Index: 1852 Fraction: 16/17 = 0.9412 -0x4044, // Index: 1853 Fraction: 65/69 = 0.9420 -0x3033, // Index: 1854 Fraction: 49/52 = 0.9423 -0x2022, // Index: 1855 Fraction: 33/35 = 0.9429 -0x3134, // Index: 1856 Fraction: 50/53 = 0.9434 -0x4246, // Index: 1857 Fraction: 67/71 = 0.9437 -0x1011, // Index: 1858 Fraction: 17/18 = 0.9444 -0x4448, // Index: 1859 Fraction: 69/73 = 0.9452 -0x3336, // Index: 1860 Fraction: 52/55 = 0.9455 -0x2224, // Index: 1861 Fraction: 35/37 = 0.9459 -0x3437, // Index: 1862 Fraction: 53/56 = 0.9464 -0x464A, // Index: 1863 Fraction: 71/75 = 0.9467 -0x1112, // Index: 1864 Fraction: 18/19 = 0.9474 -0x484C, // Index: 1865 Fraction: 73/77 = 0.9481 -0x3639, // Index: 1866 Fraction: 55/58 = 0.9483 -0x2426, // Index: 1867 Fraction: 37/39 = 0.9487 -0x373A, // Index: 1868 Fraction: 56/59 = 0.9492 -0x4A4E, // Index: 1869 Fraction: 75/79 = 0.9494 -0x1213, // Index: 1870 Fraction: 19/20 = 0.9500 -0x393C, // Index: 1871 Fraction: 58/61 = 0.9508 -0x2628, // Index: 1872 Fraction: 39/41 = 0.9512 -0x3A3D, // Index: 1873 Fraction: 59/62 = 0.9516 -0x1314, // Index: 1874 Fraction: 20/21 = 0.9524 -0x3C3F, // Index: 1875 Fraction: 61/64 = 0.9531 -0x282A, // Index: 1876 Fraction: 41/43 = 0.9535 -0x3D40, // Index: 1877 Fraction: 62/65 = 0.9538 -0x1415, // Index: 1878 Fraction: 21/22 = 0.9545 -0x3F42, // Index: 1879 Fraction: 64/67 = 0.9552 -0x2A2C, // Index: 1880 Fraction: 43/45 = 0.9556 -0x4043, // Index: 1881 Fraction: 65/68 = 0.9559 -0x1516, // Index: 1882 Fraction: 22/23 = 0.9565 -0x4245, // Index: 1883 Fraction: 67/70 = 0.9571 -0x2C2E, // Index: 1884 Fraction: 45/47 = 0.9574 -0x4346, // Index: 1885 Fraction: 68/71 = 0.9577 -0x1617, // Index: 1886 Fraction: 23/24 = 0.9583 -0x4548, // Index: 1887 Fraction: 70/73 = 0.9589 -0x2E30, // Index: 1888 Fraction: 47/49 = 0.9592 -0x4649, // Index: 1889 Fraction: 71/74 = 0.9595 -0x1718, // Index: 1890 Fraction: 24/25 = 0.9600 -0x484B, // Index: 1891 Fraction: 73/76 = 0.9605 -0x3032, // Index: 1892 Fraction: 49/51 = 0.9608 -0x494C, // Index: 1893 Fraction: 74/77 = 0.9610 -0x1819, // Index: 1894 Fraction: 25/26 = 0.9615 -0x4B4E, // Index: 1895 Fraction: 76/79 = 0.9620 -0x3234, // Index: 1896 Fraction: 51/53 = 0.9623 -0x4C4F, // Index: 1897 Fraction: 77/80 = 0.9625 -0x191A, // Index: 1898 Fraction: 26/27 = 0.9630 -0x3436, // Index: 1899 Fraction: 53/55 = 0.9636 -0x1A1B, // Index: 1900 Fraction: 27/28 = 0.9643 -0x3638, // Index: 1901 Fraction: 55/57 = 0.9649 -0x1B1C, // Index: 1902 Fraction: 28/29 = 0.9655 -0x383A, // Index: 1903 Fraction: 57/59 = 0.9661 -0x1C1D, // Index: 1904 Fraction: 29/30 = 0.9667 -0x3A3C, // Index: 1905 Fraction: 59/61 = 0.9672 -0x1D1E, // Index: 1906 Fraction: 30/31 = 0.9677 -0x3C3E, // Index: 1907 Fraction: 61/63 = 0.9683 -0x1E1F, // Index: 1908 Fraction: 31/32 = 0.9688 -0x3E40, // Index: 1909 Fraction: 63/65 = 0.9692 -0x1F20, // Index: 1910 Fraction: 32/33 = 0.9697 -0x4042, // Index: 1911 Fraction: 65/67 = 0.9701 -0x2021, // Index: 1912 Fraction: 33/34 = 0.9706 -0x4244, // Index: 1913 Fraction: 67/69 = 0.9710 -0x2122, // Index: 1914 Fraction: 34/35 = 0.9714 -0x4446, // Index: 1915 Fraction: 69/71 = 0.9718 -0x2223, // Index: 1916 Fraction: 35/36 = 0.9722 -0x4648, // Index: 1917 Fraction: 71/73 = 0.9726 -0x2324, // Index: 1918 Fraction: 36/37 = 0.9730 -0x484A, // Index: 1919 Fraction: 73/75 = 0.9733 -0x2425, // Index: 1920 Fraction: 37/38 = 0.9737 -0x4A4C, // Index: 1921 Fraction: 75/77 = 0.9740 -0x2526, // Index: 1922 Fraction: 38/39 = 0.9744 -0x4C4E, // Index: 1923 Fraction: 77/79 = 0.9747 -0x2627, // Index: 1924 Fraction: 39/40 = 0.9750 -0x2728, // Index: 1925 Fraction: 40/41 = 0.9756 -0x2829, // Index: 1926 Fraction: 41/42 = 0.9762 -0x292A, // Index: 1927 Fraction: 42/43 = 0.9767 -0x2A2B, // Index: 1928 Fraction: 43/44 = 0.9773 -0x2B2C, // Index: 1929 Fraction: 44/45 = 0.9778 -0x2C2D, // Index: 1930 Fraction: 45/46 = 0.9783 -0x2D2E, // Index: 1931 Fraction: 46/47 = 0.9787 -0x2E2F, // Index: 1932 Fraction: 47/48 = 0.9792 -0x2F30, // Index: 1933 Fraction: 48/49 = 0.9796 -0x3031, // Index: 1934 Fraction: 49/50 = 0.9800 -0x3132, // Index: 1935 Fraction: 50/51 = 0.9804 -0x3233, // Index: 1936 Fraction: 51/52 = 0.9808 -0x3334, // Index: 1937 Fraction: 52/53 = 0.9811 -0x3435, // Index: 1938 Fraction: 53/54 = 0.9815 -0x3536, // Index: 1939 Fraction: 54/55 = 0.9818 -0x3637, // Index: 1940 Fraction: 55/56 = 0.9821 -0x3738, // Index: 1941 Fraction: 56/57 = 0.9825 -0x3839, // Index: 1942 Fraction: 57/58 = 0.9828 -0x393A, // Index: 1943 Fraction: 58/59 = 0.9831 -0x3A3B, // Index: 1944 Fraction: 59/60 = 0.9833 -0x3B3C, // Index: 1945 Fraction: 60/61 = 0.9836 -0x3C3D, // Index: 1946 Fraction: 61/62 = 0.9839 -0x3D3E, // Index: 1947 Fraction: 62/63 = 0.9841 -0x3E3F, // Index: 1948 Fraction: 63/64 = 0.9844 -0x3F40, // Index: 1949 Fraction: 64/65 = 0.9846 -0x4041, // Index: 1950 Fraction: 65/66 = 0.9848 -0x4142, // Index: 1951 Fraction: 66/67 = 0.9851 -0x4243, // Index: 1952 Fraction: 67/68 = 0.9853 -0x4344, // Index: 1953 Fraction: 68/69 = 0.9855 -0x4445, // Index: 1954 Fraction: 69/70 = 0.9857 -0x4546, // Index: 1955 Fraction: 70/71 = 0.9859 -0x4647, // Index: 1956 Fraction: 71/72 = 0.9861 -0x4748, // Index: 1957 Fraction: 72/73 = 0.9863 -0x4849, // Index: 1958 Fraction: 73/74 = 0.9865 -0x494A, // Index: 1959 Fraction: 74/75 = 0.9867 -0x4A4B, // Index: 1960 Fraction: 75/76 = 0.9868 -0x4B4C, // Index: 1961 Fraction: 76/77 = 0.9870 -0x4C4D, // Index: 1962 Fraction: 77/78 = 0.9872 -0x4D4E, // Index: 1963 Fraction: 78/79 = 0.9873 -0x4E4F, // Index: 1964 Fraction: 79/80 = 0.9875 -}; From 98afbfbc9157dc9ce16c74225eb0e581844f1be4 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 15:42:02 +0000 Subject: [PATCH 108/118] Small fixes --- lib_sw_pll/src/sw_pll_common.h | 2 +- python/sw_pll/sw_pll_sim.py | 2 ++ settings.yml | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib_sw_pll/src/sw_pll_common.h b/lib_sw_pll/src/sw_pll_common.h index e1714c11..728bf942 100644 --- a/lib_sw_pll/src/sw_pll_common.h +++ b/lib_sw_pll/src/sw_pll_common.h @@ -104,4 +104,4 @@ inline int32_t sw_pll_do_pi_ctrl(sw_pll_state_t * const sw_pll, int16_t error) int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS); return total_error; -} \ No newline at end of file +} diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index e7a4ca0e..48b998cd 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -240,6 +240,8 @@ def run_sd_sw_pll_sim(): Kii = 0.25 sw_pll = sim_sw_pll_sd(nominal_output_hz, nominal_control_rate_hz, Kp, Ki, Kii=Kii) + sw_pll.dco.write_register_file() + output_clock_count = 0 test_tone_hz = 1000 diff --git a/settings.yml b/settings.yml index 512e3cbd..6de98920 100644 --- a/settings.yml +++ b/settings.yml @@ -17,4 +17,3 @@ documentation: doc/index: pdf_title: '{{project}} Programming Guide \newline' pdf_filename: '{{project}}_programming_guide_v{{version}}' - \ No newline at end of file From 77a8ebc77f86a83e2e527bf4f0b13cf95ab77561 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 15:49:06 +0000 Subject: [PATCH 109/118] Note about file overwrite in sim --- python/sw_pll/sw_pll_sim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index 48b998cd..ecbf2cd1 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -295,5 +295,5 @@ def run_sd_sw_pll_sim(): if __name__ == '__main__': run_lut_sw_pll_sim() - run_sd_sw_pll_sim() + run_sd_sw_pll_sim() # Note this will overwrite the "register_setup.h" file generated by run_lut_sw_pll_sim \ No newline at end of file From 75b836482d229034824cc2d676d532a694ab144b Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 16:08:24 +0000 Subject: [PATCH 110/118] docgen and source check tidy --- .xmos_ignore_source_check | 3 ++- doc/exclude-patterns.inc | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.xmos_ignore_source_check b/.xmos_ignore_source_check index 1350ef87..65067d42 100644 --- a/.xmos_ignore_source_check +++ b/.xmos_ignore_source_check @@ -1,2 +1,3 @@ python/sw_pll/pll_calc.py -register_setup.h \ No newline at end of file +register_setup.h +fractions.h diff --git a/doc/exclude-patterns.inc b/doc/exclude-patterns.inc index e8a68639..aa8a41e0 100644 --- a/doc/exclude-patterns.inc +++ b/doc/exclude-patterns.inc @@ -6,11 +6,6 @@ modules test build -# Do not build .md files - -*.md -**/*.md - # Do not build the app docs examples/* tools/* From 1142c71d3a8e1f0ac1afc0f67adc449852103cc2 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 16:21:42 +0000 Subject: [PATCH 111/118] Make the SDM generator produce the timing info for SDM loop --- examples/simple_sdm/src/register_setup.h | 10 +++++----- examples/simple_sdm/src/simple_sw_pll_sdm.c | 2 +- python/sw_pll/dco_model.py | 13 +++++++------ python/sw_pll/sw_pll_sim.py | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/simple_sdm/src/register_setup.h b/examples/simple_sdm/src/register_setup.h index 183e7ff5..dc4da7f9 100644 --- a/examples/simple_sdm/src/register_setup.h +++ b/examples/simple_sdm/src/register_setup.h @@ -2,14 +2,14 @@ /* Input freq: 24000000 F: 146 R: 0 - f: 7 - p: 7 + f: 4 + p: 10 OD: 5 ACD: 5 */ #define APP_PLL_CTL_REG 0x0A809200 #define APP_PLL_DIV_REG 0x80000005 -#define APP_PLL_FRAC_REG 0x80000707 -#define SW_PLL_SDM_CTRL_MID 553648 - +#define APP_PLL_FRAC_REG 0x8000040A +#define SW_PLL_SDM_CTRL_MID 478151 +#define SW_PLL_SDM_RATE 1000000 diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 82a03ec8..6e304aa4 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -23,7 +23,7 @@ void sdm_task(chanend_t c_sdm_control){ printf("sdm_task\n"); - const uint32_t sdm_interval = 100; // 100 * 10ns ticks = 1MHz + const uint32_t sdm_interval = XS1_TIMER_HZ / SW_PLL_SDM_RATE; // in 10ns ticks = 1MHz sw_pll_sdm_state_t sdm_state; sw_pll_init_sigma_delta(&sdm_state); diff --git a/python/sw_pll/dco_model.py b/python/sw_pll/dco_model.py index a8439567..a08d6339 100644 --- a/python/sw_pll/dco_model.py +++ b/python/sw_pll/dco_model.py @@ -281,10 +281,10 @@ class sigma_delta_dco(sdm): """ - profiles = {"24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6, "mod_init":478151}, - "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6, "mod_init":498283}, - "24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6, "mod_init":553648}, - "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6, "mod_init":555326} + profiles = {"24.576_1M": {"input_freq":24000000, "F":int(147.455 - 1), "R":1 - 1, "f":5 - 1, "p":11 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":24.576e6, "mod_init":478151, "sdm_rate":1000000}, + "22.5792_1M": {"input_freq":24000000, "F":int(135.474 - 1), "R":1 - 1, "f":9 - 1, "p":19 - 1, "OD":6 - 1, "ACD":6 - 1, "output_frequency":22.5792e6, "mod_init":498283, "sdm_rate":1000000}, + "24.576_500k": {"input_freq":24000000, "F":int(278.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":2 - 1, "ACD":17 - 1, "output_frequency":24.576e6, "mod_init":553648, "sdm_rate":500000}, + "22.5792_500k": {"input_freq":24000000, "F":int(293.529 - 1), "R":2 - 1, "f":9 - 1, "p":17 - 1, "OD":3 - 1, "ACD":13 - 1, "output_frequency":22.5792e6, "mod_init":555326, "sdm_rate":500000} } @@ -295,7 +295,7 @@ def __init__(self, profile): self.profile = profile self.p_value = 8 # 8 frac settings + 1 non frac setting - input_freq, F, R, f, p, OD, ACD, _, _ = list(self.profiles[profile].values()) + input_freq, F, R, f, p, OD, ACD, _, _, _ = list(self.profiles[profile].values()) self.app_pll = app_pll_frac_calc(input_freq, F, R, f, p, OD, ACD) self.sdm_out = 0 @@ -374,7 +374,8 @@ def write_register_file(self): with open(register_file, "w") as reg_vals: reg_vals.write(f"/* Autogenerated SDM App PLL setup by {Path(__file__).name} using {self.profile} profile */\n") reg_vals.write(self.app_pll.gen_register_file_text()) - reg_vals.write(f"#define SW_PLL_SDM_CTRL_MID {self.profiles[self.profile]['mod_init']}") + reg_vals.write(f"#define SW_PLL_SDM_CTRL_MID {self.profiles[self.profile]['mod_init']}\n") + reg_vals.write(f"#define SW_PLL_SDM_RATE {self.profiles[self.profile]['sdm_rate']}\n") reg_vals.write("\n\n") return register_file diff --git a/python/sw_pll/sw_pll_sim.py b/python/sw_pll/sw_pll_sim.py index ecbf2cd1..62a0d928 100644 --- a/python/sw_pll/sw_pll_sim.py +++ b/python/sw_pll/sw_pll_sim.py @@ -179,7 +179,7 @@ def __init__( self, Init a Sigma Delta Modulator based SW_PLL instance """ - self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=20000) + self.pfd = port_timer_pfd(target_output_frequency, nominal_nominal_control_rate_frequency, ppm_range=3000) self.dco = sigma_delta_dco("24.576_1M") self.controller = sdm_pi_ctrl( (self.dco.sdm_in_max + self.dco.sdm_in_min) / 2, self.dco.sdm_in_max, From 3c4a881ef3009ae62d7cae490d1522d868e8455d Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 1 Dec 2023 16:46:10 +0000 Subject: [PATCH 112/118] Warning comments in sdm example --- examples/simple_sdm/src/simple_sw_pll_sdm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 6e304aa4..4f0a2041 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -58,14 +58,18 @@ void sdm_task(chanend_t c_sdm_control){ break; } + // Wait until the timer value has been reached + // This implements a timing barrier and keeps + // the loop rate constant. + hwtimer_wait_until(tmr, trigger_time); + trigger_time += sdm_interval; + + // Do not write to the frac reg until we get out first + // control value. This will avoid the writing of the + // frac reg from two different threads which may cause + // a channel deadlock. if(sdm_in){ - // Wait until the timer value has been reached - // This implements a timing barrier and keeps - // the loop rate constant. - hwtimer_wait_until(tmr, trigger_time); sw_pll_do_sigma_delta(&sdm_state, this_tile, sdm_in); - - trigger_time += sdm_interval; } } } From bb14c914f8a4cdbacf594fe224c2a0a8b71adcd9 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 4 Dec 2023 10:09:17 +0000 Subject: [PATCH 113/118] Add descriptions of the PI and PFD --- doc/rst/sw_pll.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index b01e7b48..b6f294b1 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -130,7 +130,39 @@ spread of the noise floor can be seen in the following diagrams. SDM noise plot when when tracking a constant input frequency +Phase Frequency Detector +........................ +The Software PLL PFD detects frequency by counting clocks over a specific time period. The clock counted is the output from the PLL and the +time period over which the count happens is a multiple of the input reference clock. This way the frequency difference between the +input and output clock can be directly ascertained by comparing the read count increment with the expected count increment for the +nominal case where the input and output are locked. + +The PFD cannot directly measure phase, however, by taking the time integral of the frequency we can derive the phase which can be done +in the PI controller. + +The PFD uses three chip resources: + +- A one bit port to capture the PLL output clock (always Port 1D on Tile[1] of xcore-ai) +- A clock block to turn the captured PLL output clock into a signal which can be distributed across the xcore tile +- An unconnected dummy input port (eg. Port 32A) clocked from the clock block. The in-built counter of this port + can then be read and allows a direct count of the PLL output clock. + +The port timers are 16 bits and so the PFD needs to account for wrapping because the overflow period at, for example, 24.576 MHz +is 2.67 milliseconds and a typical control period is in the order 10 milliseconds. + + +Proportional Integral Controller +................................ + +The PI controller uses fixed point (15Q16) types to calculate the error and accumulated error which are summed to produce the +output. In addition a double integral term is included to allow calculation of the integral term of phase error, which itself +is the integral of the frequency error which is the output from the PFD. + +Wind-up protection is included in the PI controller which clips the integral and double integral accumulator errors and is nominally +set to LUT size for the LUT based DCO and the control range for the SDM based DCO. + +The SDM controller also includes a low-pass filter for additional jitter reduction. Simulation Model ---------------- From 74068d82c673f0fa9f07df0484cd4361f49711c7 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 4 Dec 2023 10:10:33 +0000 Subject: [PATCH 114/118] Section re-layout --- doc/rst/sw_pll.rst | 64 +++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index b6f294b1..1c77754e 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -1,8 +1,8 @@ How the Software PLL works --------------------------- +========================== Introduction -............ +------------ A Phase Locked Loop (PLL) is a typically hardware that allows generation of a clock which is synchronised to an input reference clock by both phase and frequency. They consist of a number of sub-components: @@ -51,7 +51,7 @@ There are trade-offs between the two types of DCO which are summarised in the fo - Wide - 1500-3000 LUT based DCO -............. +------------- The LUT based DCO allows a discrete set of fractional settings resulting in a fixed number of frequency steps. The LUT is pre-computed table which provides a set of monotonic increasing frequency register settings. The LUT @@ -94,7 +94,7 @@ LUT entries, and the consequential frequency modulation effect, can be seen in t LUT noise plot when when tracking a constant input frequency SDM Based DCO -............. +------------- The SDM based DCO provides a fixed number (9 in this case) of frequency steps which are jumped between at a high rate (eg. 1 MHz) but requires a dedicated logical core to run the SDM algorithm and update the PLL @@ -131,7 +131,7 @@ spread of the noise floor can be seen in the following diagrams. SDM noise plot when when tracking a constant input frequency Phase Frequency Detector -........................ +------------------------ The Software PLL PFD detects frequency by counting clocks over a specific time period. The clock counted is the output from the PLL and the time period over which the count happens is a multiple of the input reference clock. This way the frequency difference between the @@ -148,12 +148,14 @@ The PFD uses three chip resources: - An unconnected dummy input port (eg. Port 32A) clocked from the clock block. The in-built counter of this port can then be read and allows a direct count of the PLL output clock. +A diagram of the resource setup is shown in the `Simple Usage Example Resource Setup`_ section. + The port timers are 16 bits and so the PFD needs to account for wrapping because the overflow period at, for example, 24.576 MHz is 2.67 milliseconds and a typical control period is in the order 10 milliseconds. Proportional Integral Controller -................................ +-------------------------------- The PI controller uses fixed point (15Q16) types to calculate the error and accumulated error which are summed to produce the output. In addition a double integral term is included to allow calculation of the integral term of phase error, which itself @@ -164,11 +166,13 @@ set to LUT size for the LUT based DCO and the control range for the SDM based DC The SDM controller also includes a low-pass filter for additional jitter reduction. +See the `Tuning the Software PLL`_ section for information about how to optimise the PI controller. + Simulation Model ----------------- +================ Contents -........ +-------- In the ``python/sw_pll`` directory you will find multiple files:: @@ -202,7 +206,7 @@ knowledge of the operation of the App PLL. Instead, it is recommended to use ``a wraps the script with defaults. Running the PI simulation and LUT generation script -................................................... +--------------------------------------------------- By running ``sw_pll_sim.py`` a number of operations will take place: @@ -248,13 +252,14 @@ a handful of control loop iterations. Tuning the Software PLL ------------------------ +======================= + +LUT based DCO Tuning +-------------------- PI controller ............. -Note, in the python simulation file ``sw_pll_sim.py``, the PI constants *Kp*, *Ki* and optionally *Kii* can be found in the functions `run_lut_sw_pll_sim()` and `run_sd_sw_pll_sim()`. - Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1.0) *Ki* term. - Decreasing the ref_to_loop_call_rate parameter will cause the control loop to execute more frequently and larger constants will be needed. @@ -269,6 +274,9 @@ the frequency error. The phase error is the integral of the frequency error and is required as well as frequency locking then we need to support the integral of the integral of the frequency error. Simply changing the Kp, Ki and Kii constants is all that is needed in this case. +.. note:: + In the python simulation file ``sw_pll_sim.py``, the PI constants *Kp*, *Ki* and optionally *Kii* can be found in the functions `run_lut_sw_pll_sim()` and `run_sd_sw_pll_sim()`. + Typically a small Kii term is used if needed because it accumulates very quickly. @@ -321,9 +329,10 @@ Search for ``profiles`` and ``profile_choice`` in this file. Change profile choi - 30.2 - 166 -Note that the physical PLL actually multiplies the input crystal, not the reference input clock. -It is the PFD and software control loop that detects the frequency error and controls the fractional register to make the PLL track the input. -A change in the reference input clock parameter only affects the control loop and its associated constants such as how often the PI controller is called. +.. note:: + The physical PLL actually multiplies the input crystal, not the reference input clock. + It is the PFD and software control loop that detects the frequency error and controls the fractional register to make the PLL track the input. + A change in the reference input clock parameter only affects the control loop and its associated constants such as how often the PI controller is called. Custom LUT Generation Guidance @@ -356,8 +365,12 @@ Steps to vary the LUT PPM range and frequency step size 5. If the +/-PPM range is not symmetrical and you wish it to be, then adjust the ``fracmin`` and ``fracmax`` values around the center point that the PLL finder algorithm has found. For example if the -PPM range is to great, increase ``fracmin`` and if the +PPM range is too great, decrease the ``fracmax`` value. -Note when the process has completed, please inspect the ``lut_dco_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. -This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. +.. note:: + When the process has completed, please inspect the ``lut_dco_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. + This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. + +SDM based DCO Tuning +-------------------- SDM Available Configurations ............................ @@ -397,11 +410,14 @@ is available. - -93 - 500 +The SDM based DCO Software PLL has been pre-tuned and should not need modification in normal circumstances. Due to the large control range values +needed by the SDM DCO, a relatively large integral term is used which applies a large gain. If you do need to tune the SDM DCO PI controller then +it is recommended to start with the provided values in the example in ``/examples/simple_sdm``. Transferring the results to C -............................. +----------------------------- -Once the LUT has been generated and simulated in Python, the values can be transferred to the firmware application. Control loop constants +Once the LUT has been generated or SDM profile selected and has simulated in Python, the values can be transferred to the firmware application. Control loop constants can be directly transferred to the `init()` functions and the generated `.h` files can be copied directly into the source directory of your project. @@ -410,7 +426,7 @@ For further information, either consult the ``sw_pll.h`` API file (included at t Simple Usage Example Resource Setup ------------------------------------ +=================================== The xcore-ai device has a number of resources on chip. In the `simple` examples both `clock blocks` and `ports` are connected together to provide an input to the PFD and additionally provide a scaled output clock. The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intrinsic functions in ``lib_xcore``. @@ -422,8 +438,8 @@ To help visualise how these resources work together, please see the below diagra Use of Ports and Clock Blocks in the examples -lib_sw_pll API --------------- +Software PLL API +================ The Application Programmer Interface (API) for the Software PLL is shown below. It is split into items specific to LUT and SDM DCOs . @@ -432,7 +448,7 @@ to be controlled using a PI fed with a raw error input, a low-level API is also API allows the Software PLL to track an arbitrary clock source which is calculated by another means such as received packets. LUT Based PLL API -................. +----------------- The LUT based API are functions designed to be called from an audio loop. Typically the functions can take up to 210 instruction cycles when control occurs and just a few 10s of cycles when control does not occur. If run at a rate of 48 kHz then it will consume approximately 1 MIPS on average. @@ -440,7 +456,7 @@ The LUT based API are functions designed to be called from an audio loop. Typica :content-only: SDM Based PLL API -................. +----------------- All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and register write and it is expected that the user provide the fork (par) and call to the SDM. A typical design idiom is to have the task running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. From ca10b63da746df1d7393061e775cf7da490ce91e Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 4 Dec 2023 14:29:22 +0000 Subject: [PATCH 115/118] Change titles in doc --- doc/rst/sw_pll.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index 1c77754e..88407de8 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -254,7 +254,7 @@ a handful of control loop iterations. Tuning the Software PLL ======================= -LUT based DCO Tuning +LUT based PLL Tuning -------------------- PI controller @@ -369,7 +369,7 @@ Steps to vary the LUT PPM range and frequency step size When the process has completed, please inspect the ``lut_dco_range.png`` output figure which shows how the fractional PLL setting affects the output frequency. This should be monotonic and not contain an significant discontinuities for the control loop to operate satisfactorily. -SDM based DCO Tuning +SDM based PLL Tuning -------------------- SDM Available Configurations From 69695024d6fb9c4a6e034070923779a4fa74d4ad Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 4 Dec 2023 17:57:08 +0000 Subject: [PATCH 116/118] Refactoring of the resource setup var names and docs to make clearer --- ...rce_setup_sw_pll_i2s_slave_example.drawio} | 61 ++++---- ...esource_setup_sw_pll_simple_example.drawio | 130 ++++++++++++++++++ doc/rst/images/resource_setup_example.png | Bin 64438 -> 0 bytes doc/rst/sw_pll.rst | 50 +++++-- examples/shared/src/resource_setup.c | 22 +-- examples/shared/src/resource_setup.h | 2 +- examples/simple_lut/src/simple_sw_pll.c | 15 +- examples/simple_sdm/src/simple_sw_pll_sdm.c | 16 +-- 8 files changed, 221 insertions(+), 75 deletions(-) rename doc/diagram_source/{resource_setup_sw_pll_Example.drawio => resource_setup_sw_pll_i2s_slave_example.drawio} (72%) create mode 100644 doc/diagram_source/resource_setup_sw_pll_simple_example.drawio delete mode 100644 doc/rst/images/resource_setup_example.png diff --git a/doc/diagram_source/resource_setup_sw_pll_Example.drawio b/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio similarity index 72% rename from doc/diagram_source/resource_setup_sw_pll_Example.drawio rename to doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio index e3f01a46..57eacc9c 100644 --- a/doc/diagram_source/resource_setup_sw_pll_Example.drawio +++ b/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio @@ -1,6 +1,6 @@ - + - + @@ -21,8 +21,15 @@ - - + + + + + + + + + @@ -35,29 +42,23 @@ - + - + - + - - - - - - - + @@ -74,35 +75,23 @@ - + - - - - - - - - - - + - - - - - + + - - + + - - + + @@ -126,8 +115,8 @@ - - + + diff --git a/doc/diagram_source/resource_setup_sw_pll_simple_example.drawio b/doc/diagram_source/resource_setup_sw_pll_simple_example.drawio new file mode 100644 index 00000000..a2a09979 --- /dev/null +++ b/doc/diagram_source/resource_setup_sw_pll_simple_example.drawio @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/rst/images/resource_setup_example.png b/doc/rst/images/resource_setup_example.png deleted file mode 100644 index af49b80c575033b4b9cf0b5ea8e4af965ddee531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64438 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%0Dq3`tWSdb4G(C+Wm z?%?sh<7kuV9Z)Vh(1fLa7 zW&XTi`Dsnyc@wKl%WsF>L)MvdlF%w0$V!6;d+*@k6KiCx z8fGaQbGx(V6p?T1cN1+Dhsqb%PB_QrbMvE9p0EyXZJ+8Y#n6vmy>%j7)a(-Gb--yAxc}K_aAcyx9*O?t|AQm#`;gLC5 zCm%Hc?WF9JA-fW}wZSq^u65W{4I?+}5pY9MWIf6CU{`Z*AV2-hCUqa2ARS-{ON>@O?fCGruhbZc`?_TfCt1Ltp zFI#_Fr+K`{`>u~&Gx~5C+E^M={z%oNFh(w?OgM6OXS$57(ZLd+xs^`X(EVnj;c{t7 z&CUU278)CilQM!fr7QpgFI}FyRFvry$n++2U;SM1+s+IGPYc}T*OX8e6|=E^cY`E^r=w~%Om`#^P^xk7L7~3f*b5prv>)7k&o1?V9k&&0VvhbXObD6 zc546b&{?u{w6jfJz>b}JE(5`&K*mHUHbo_beF2ga#LhhF(1KN-lT24n52OQI&K*k! z6_UW;QkkTv6bK~Nk5#X4|F;I>rQxGR;aEa*6orCKGYS*|C->q;+tHU7H(aNeiC|0C z4nJ-sSR2(4!emzs3ix5Z^-pihTZ#aDXAK*V8dIpR7KvU=rZI820^^N&}mqiGeVfq@@?^RW-cYJ~GwScZ@=J_BHF} z3@D?RrmjJIcVuI+-!aUk(04%Mz~9V{_?6EvW+*8p>K$8A{yyA-B+Gb&Gs#|t)5;rA zi>gYUUQg(UXX+8Rheaf<_7S)jT&Vv6X&4U3pB4+4XHT|jqoc}`Lf$K;SvB&((TOr3 zqBvhxwD>3R%~6l{7Un#JL|72Rau1j_bQ9{R_yRl;GBx;5meQAXpU=1wAIIM~F^1Lw zKaUG%c;tia9z;ESu*Ab78+~(gtW8dT!ycgDTJ`fsd8B_G(X-O+D14UTwMUi|23vnPnA=zmZNRc#w;4n9Hx@CY6; zZ#hKxQq6b&;;UP{nz733b9u9&Eghl!V~G;_jlC=?Ij1*(-s{}{YF|QEN`&zm2Ilcs;U8|eA^mZu@R`ZxpN54d2{m|ldWMlq~3?4jV z_yfpfRoD__G(`67>eu1m^S{$n#2{a5XQq}fm9wv&F|4esu>(O^> zZGk}} zV2R!KS{w>iQEGLPEUHcOvOq_tv;h+f$V^g4QCac_%-idY^y7k!XrbZA0QCRf(fftq z-}h@sIiq&pDr=_}m*7;%Us2l0Nkx5@KuNxA&^5G?%bw)}>~xKNEhm2QrRBu4e|@J4 zi;n3+;!Dn zWNj7gxDB8Y{nLE%F>R?i+vBa>0cReqUQi;{^qvwEcx-E&$#xC)6`Va^aTN_UzP zjQE3$O(>RM@5e#Ifn7uUB2eMdzfWCXw?f0EG8AA6|Be1w2!7Fq9@yp;D+ znUyI}VSPXa_f-NRlV1m%ez{tsbt+{6+;|$jT=>vBK_e=RiWTxmH`a36a$ARt3O#U= z*FJp!sM~Mqfy$mE+Nnv|st?;`W@*Tiqr+pJic}dMK-iTu{zVIJJmIi5Wm#Oy<{IR} zXI#uWP&0!_@y9F-D8D1>Lk}2)a22I{JU@I#4esn1FxWFCGm-XK8RE2aNRwq?_7m84 z?o&iFO4o%E-~8%zu^wpcQiy|t_1hc;L}kI!ZOWYVu4hAfs<*<5asqzQk=(rzUP65W z`MmRUJ`R7@oWid|rED7cJyS;fSQdX~CKr5HB?h7@emX&7a+gUEGY8 zQGJ@wv7qpJq!Zrk-wO*Nl%HlUhBxW*@0VUn1lhR=!&TcqpXKrwaz_*OWfnJ=Wg62) zH8A#@iR+v54R7H)C@T6V$@BgMa#>8cv9;E~pxXYUn7; s+6gC9=DFDCoiQX=-e-F #include "resource_setup.h" -void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count) +void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_clock_counter, xclock_t clk_ref_clk, port_t p_ref_clk_timing) { - // Create clock from mclk port and use it to clock the p_ref_clk port. + // Create clock from mclk port and use it to clock the p_clock_counter port. clock_enable(clk_mclk); port_enable(p_mclk); clock_set_source_port(clk_mclk, p_mclk); - // Clock p_ref_clk from MCLK - port_enable(p_ref_clk_in); - port_set_clock(p_ref_clk_in, clk_mclk); + // Clock p_clock_counter from MCLK + port_enable(p_clock_counter); + port_set_clock(p_clock_counter, clk_mclk); clock_start(clk_mclk); - // Create clock from ref_clock_port and use it to clock the p_ref_clk_count port. - clock_enable(clk_word_clk); - clock_set_source_port(clk_word_clk, p_ref_clk_in); - port_enable(p_ref_clk_count); - port_set_clock(p_ref_clk_count, clk_word_clk); + // Create clock from ref_clock_port and use it to clock the p_clock_counter port. + clock_enable(clk_ref_clk); + clock_set_source_port(clk_ref_clk, p_clock_counter); + port_enable(p_ref_clk_timing); + port_set_clock(p_ref_clk_timing, clk_ref_clk); - clock_start(clk_word_clk); + clock_start(clk_ref_clk); } diff --git a/examples/shared/src/resource_setup.h b/examples/shared/src/resource_setup.h index c45ada45..36b3e00f 100644 --- a/examples/shared/src/resource_setup.h +++ b/examples/shared/src/resource_setup.h @@ -10,7 +10,7 @@ // and its internal counter is used to count the PLL clock cycles(normal timers cannot count custom clocks) // It also sets up a dummy port clocked by the input reference to act as a timing barrier so that // the output clock count can be precisely sampled. -void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_ref_clk_in, xclock_t clk_word_clk, port_t p_ref_clk_count); +void setup_ref_and_mclk_ports_and_clocks(port_t p_mclk, xclock_t clk_mclk, port_t p_clock_counter, xclock_t clk_ref_clk, port_t p_ref_clk_timing); // Sets up a divided version of the PLL output so it can visually be compared (eg. on a DSO) // with the input reference clock to the PLL diff --git a/examples/simple_lut/src/simple_sw_pll.c b/examples/simple_lut/src/simple_sw_pll.c index b3f19c71..b233da06 100644 --- a/examples/simple_lut/src/simple_sw_pll.c +++ b/examples/simple_lut/src/simple_sw_pll.c @@ -27,10 +27,10 @@ void sw_pll_test(void){ // Declare mclk and refclk resources and connect up port_t p_mclk = PORT_MCLK_IN; xclock_t clk_mclk = XS1_CLKBLK_1; - port_t p_ref_clk = PORT_I2S_LRCLK; - xclock_t clk_word_clk = XS1_CLKBLK_2; - port_t p_ref_clk_count = XS1_PORT_32A; - setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_ref_clk, clk_word_clk, p_ref_clk_count); + port_t p_clock_counter = PORT_I2S_LRCLK; + xclock_t clk_ref_clk = XS1_CLKBLK_2; + port_t p_ref_clk_timing = XS1_PORT_32A; + setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_clock_counter, clk_ref_clk, p_ref_clk_timing); // Make a test output to observe the recovered mclk divided down to the refclk frequency xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3; @@ -44,7 +44,7 @@ void sw_pll_test(void){ SW_PLL_15Q16(0.0), CONTROL_LOOP_COUNT, PLL_RATIO, - 0, + 0, /* No jitter compensation needed */ frac_values_80, SW_PLL_NUM_LUT_ENTRIES(frac_values_80), APP_PLL_CTL_12288, @@ -57,9 +57,8 @@ void sw_pll_test(void){ uint32_t max_time = 0; while(1) { - port_in(p_ref_clk_count); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time. - uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. - + port_in(p_ref_clk_timing); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time. + uint16_t mclk_pt = port_get_trigger_time(p_clock_counter);// Get the port timer val from p_clock_counter (which is clocked running from the PLL output). uint32_t t0 = get_reference_time(); sw_pll_lut_do_control(&sw_pll, mclk_pt, 0); uint32_t t1 = get_reference_time(); diff --git a/examples/simple_sdm/src/simple_sw_pll_sdm.c b/examples/simple_sdm/src/simple_sw_pll_sdm.c index 4f0a2041..b47249d3 100644 --- a/examples/simple_sdm/src/simple_sw_pll_sdm.c +++ b/examples/simple_sdm/src/simple_sw_pll_sdm.c @@ -83,10 +83,10 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ // Declare mclk and refclk resources and connect up port_t p_mclk = PORT_MCLK_IN; xclock_t clk_mclk = XS1_CLKBLK_1; - port_t p_ref_clk = PORT_I2S_LRCLK; - xclock_t clk_word_clk = XS1_CLKBLK_2; - port_t p_ref_clk_count = XS1_PORT_32A; - setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_ref_clk, clk_word_clk, p_ref_clk_count); + port_t p_clock_counter = PORT_I2S_LRCLK; + xclock_t clk_ref_clk = XS1_CLKBLK_2; + port_t p_ref_clk_timing = XS1_PORT_32A; + setup_ref_and_mclk_ports_and_clocks(p_mclk, clk_mclk, p_clock_counter, clk_ref_clk, p_ref_clk_timing); // Make a test output to observe the recovered mclk divided down to the refclk frequency xclock_t clk_recovered_ref_clk = XS1_CLKBLK_3; @@ -100,20 +100,20 @@ void sw_pll_sdm_test(chanend_t c_sdm_control){ SW_PLL_15Q16(0.25), CONTROL_LOOP_COUNT, PLL_RATIO, - 0, + 0, /* No jitter compensation needed */ APP_PLL_CTL_REG, APP_PLL_DIV_REG, APP_PLL_FRAC_REG, SW_PLL_SDM_CTRL_MID, - 3000 /*PPM_RANGE*/); + 3000 /*PPM_RANGE FOR PFD*/); sw_pll_lock_status_t lock_status = SW_PLL_LOCKED; uint32_t max_time = 0; while(1) { - port_in(p_ref_clk_count); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time. - uint16_t mclk_pt = port_get_trigger_time(p_ref_clk);// Get the port timer val from p_ref_clk (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. + port_in(p_ref_clk_timing); // This blocks each time round the loop until it can sample input (rising edges of word clock). So we know the count will be +1 each time. + uint16_t mclk_pt = port_get_trigger_time(p_clock_counter);// Get the port timer val from p_clock_counter (which is running from MCLK). So this is basically a 16 bit free running counter running from MCLK. uint32_t t0 = get_reference_time(); bool ctrl_done = sw_pll_sdm_do_control(&sw_pll, mclk_pt, 0); From 7b1c9816b232da744256ce26dcba97df1f535632 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 5 Dec 2023 09:20:56 +0000 Subject: [PATCH 117/118] Final tidy items from doc review --- doc/diagram_source/PLL_block_diagram.drawio | 40 ++++--- ...urce_setup_sw_pll_i2s_slave_example.drawio | 4 +- doc/exclude-patterns.inc | 1 + doc/rst/images/PLL_block_diagram.png | Bin 27751 -> 29387 bytes doc/rst/sw_pll.rst | 111 ++++++++++-------- 5 files changed, 85 insertions(+), 71 deletions(-) diff --git a/doc/diagram_source/PLL_block_diagram.drawio b/doc/diagram_source/PLL_block_diagram.drawio index 90bb4310..a3087952 100644 --- a/doc/diagram_source/PLL_block_diagram.drawio +++ b/doc/diagram_source/PLL_block_diagram.drawio @@ -1,59 +1,65 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + diff --git a/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio b/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio index 57eacc9c..26b4b269 100644 --- a/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio +++ b/doc/diagram_source/resource_setup_sw_pll_i2s_slave_example.drawio @@ -1,4 +1,4 @@ - + @@ -78,7 +78,7 @@ - + diff --git a/doc/exclude-patterns.inc b/doc/exclude-patterns.inc index aa8a41e0..03f27852 100644 --- a/doc/exclude-patterns.inc +++ b/doc/exclude-patterns.inc @@ -2,6 +2,7 @@ LICENSE.rst CHANGELOG.rst README.rst +README.md modules test build diff --git a/doc/rst/images/PLL_block_diagram.png b/doc/rst/images/PLL_block_diagram.png index ef19ccbb8bd23d6bc334eb7abfb7fb6eec683e03..9d24548fef32b9ac7a29b65fff8945cd52f35ea6 100644 GIT binary patch literal 29387 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 G@BKfF6)774 literal 27751 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 diff --git a/doc/rst/sw_pll.rst b/doc/rst/sw_pll.rst index af652cb1..850790e6 100644 --- a/doc/rst/sw_pll.rst +++ b/doc/rst/sw_pll.rst @@ -38,8 +38,8 @@ There are trade-offs between the two types of DCO which are summarised in the fo - LUT DCO - SDM DCO * - Jitter - - Low, ~1-2 ns - - Very Low, ~10-50 ps + - Low, 1-2 ns + - Very Low, 10-50 ps * - Memory Usage - Low, ~2.5 kB - Low, ~2 kB @@ -47,8 +47,8 @@ There are trade-offs between the two types of DCO which are summarised in the fo - Low - ~1 - High - ~50 * - Lock Range PPM - - Moderate - 100-1000 - - Wide - 1500-3000 + - Moderate, 100-1000 + - Wide, 1500-3000 LUT based DCO ------------- @@ -56,7 +56,7 @@ LUT based DCO The LUT based DCO allows a discrete set of fractional settings resulting in a fixed number of frequency steps. The LUT is pre-computed table which provides a set of monotonically increasing frequency register settings. The LUT based DCO requires very low compute allowing it to be run in a sample-based loop at audio -frequencies such as 48kHz or 44.1kHz. It required two bytes per LUT entry. It provides reasonable +frequencies such as 48kHz or 44.1kHz. It required two bytes per LUT entry and provides reasonable jitter performance suitable for voice or entry level Hi-Fi. .. figure:: ./images/lut_pll.png @@ -66,7 +66,7 @@ jitter performance suitable for voice or entry level Hi-Fi. The range is governed by the look up table (LUT) which has a finite number of entries and consequently -a step size which affects the output jitter performance when the controller oscillates between two +has a frequency step size. This affects the output jitter performance when the controller oscillates between two settings once locked. Note that the actual range and number of steps is highly configurable. .. figure:: ./images/lut_dco_range.png @@ -79,7 +79,7 @@ The index into the LUT is controlled by a PI controller which multiplies the error input and integral error input by the supplied loop constants. An integrated `wind up` limiter for the integral term is nominally set at 2x the maximum LUT index deviation to prevent excessive overshoot where the starting input error is high. A double integrator term -is also available. +is also available to help zero phase error. A time domain plot of how the controller (typically running at around 100 Hz) selects between adjacent LUT entries, and the consequential frequency modulation effect, can be seen in the following diagrams. @@ -103,7 +103,7 @@ fractional register. The SDM is third order. The SDM typically provides better audio quality by pushing the noise floor up into the inaudible part of the spectrum. A fixed set of SDM coefficients and loop filters are provided which -have been hand tuned to provide either 24.576 MHz or 22.5792 MHz low jitter clocks and are suitable for Hi-Fi systems +have been hand tuned to provide either 24.576 MHz or 22.5792 MHz low jitter clocks and are suitable for Hi-Fi and professional audio applications. .. figure:: ./images/sdm_pll.png @@ -136,22 +136,22 @@ Phase Frequency Detector The Software PLL PFD detects frequency by counting clocks over a specific time period. The clock counted is the output from the PLL and the time period over which the count happens is a multiple of the input reference clock. This way the frequency difference between the -input and output clock can be directly ascertained by comparing the read count increment with the expected count increment for the +input and output clock can be directly measured by comparing the read count increment with the expected count increment for the nominal case where the input and output are locked. The PFD cannot directly measure phase, however, by taking the time integral of the frequency we can derive the phase which can be done -in the PI controller. +by the PI controller. The PFD uses three chip resources: - A one bit port to capture the PLL output clock (always Port 1D on Tile[1] of xcore-ai) - A clock block to turn the captured PLL output clock into a signal which can be distributed across the xcore tile -- An unconnected dummy input port (eg. Port 32A) clocked from the clock block. The in-built counter of this port - can then be read and allows a direct count of the PLL output clock. +- An input port (either one already in use or an unconnected dummy port such as Port 32A) clocked from the above clock block. The in-built counter of this port + can then be read and provides a count of the PLL output clock. -A diagram of the resource setup is shown in the `Simple Usage Example Resource Setup`_ section. +Two diagrams showing practical xcore resource setups are shown in the `Example Applictaion Resource Setup`_ section. -The port timers are 16 bits and so the PFD accounts for wrapping because the overflow period at, for example, 24.576 MHz +The port timers are 16 bits and so the PFD must account for wrapping because the overflow period at, for example, 24.576 MHz is 2.67 milliseconds and a typical control period is in the order 10 milliseconds. There may be cases where the port timer sampling time cannot be guaranteed to be fully isochronous, such as when a significant number of @@ -165,11 +165,11 @@ caused by variable instruction timing to be eliminated. Proportional Integral Controller -------------------------------- -The PI controller uses fixed point (15Q16) types to calculate the error and accumulated error which are summed to produce the +The PI controller uses fixed point (15Q16) types and 64 bit intermediate terms to calculate the error and accumulated error which are summed to produce the output. In addition a double integral term is included to allow calculation of the integral term of phase error, which itself is the integral of the frequency error which is the output from the PFD. -Wind-up protection is included in the PI controller which clips the integral and double integral accumulator errors and is nominally +Wind-up protection is included in the PI controller which clips the integral and double integral accumulator terms and is nominally set to LUT size for the LUT based DCO and the control range for the SDM based DCO. The SDM controller also includes a low-pass filter for additional jitter reduction. @@ -179,6 +179,8 @@ See the `Tuning the Software PLL`_ section for information about how to optimise Simulation Model ================ +A complete model of the Software PLL is provided and is written in Python version 3. + Contents -------- @@ -204,14 +206,15 @@ the recovered clock. ``dco_model.py`` contains a model of the LUT and SDM digitally controlled oscillators. -``pll_calc.py`` models the Phase Frequency Detector which is used when inputting a reference clock to the Software PLL. +``pfd_model.py`` models the Phase Frequency Detector which is used when inputting a reference clock to the Software PLL. -``app_pll_model.py`` models the Application PLL and allows reading/writing include files suitable for inclusion into XCORE +``app_pll_model.py`` models the Application PLL hardware and allows reading/writing include files suitable for inclusion into xcore firmware projects. + ``pll_calc.py`` is the command line script that generates the LUT. It is quite a complex to use script which requires in depth -knowledge of the operation of the App PLL. Instead, it is recommended to use ``app_pll_model.py`` which calls ``pll_calc.py`` which -wraps the script with defaults. +knowledge of the operation of the App PLL. Instead it is recommended to use ``app_pll_model.py`` which calls ``pll_calc.py`` which +wraps the script with sensible defaults, or better, use one of the provided profiles driven by ``sw_pll_sim.py``. Running the PI simulation and LUT generation script --------------------------------------------------- @@ -219,11 +222,11 @@ Running the PI simulation and LUT generation script By running ``sw_pll_sim.py`` a number of operations will take place: - The ``fractions.h`` LUT include file will be generated (LUT PLL only - this is not needed by SDM) - - The ``register_setup.h`` PLL configuration file will be generated for inclusion in your XCORE project. + - The ``register_setup.h`` PLL configuration file will be generated for inclusion in your xcore project. - A graphical view of the LUT settings showing index vs. output frequency is generated. - A time domain simulation of the PI loop showing the response to steps and out of range reference inputs is run. - A wave file containing a 1 kHz modulated tone for offline analysis. - - A zoomed-in log FFT plot of the 1 kHz tone to see how the LUT frequency steps affect a pure tone. + - A log FFT plot of the 1 kHz tone to see how the PLL frequency steps affect a pure tone. - A summary report of the PLL range is also printed to the console. The directory listing following running of ``sw_pll_sim.py`` will be added to as follows:: @@ -239,17 +242,9 @@ The directory listing following running of ``sw_pll_sim.py`` will be added to as └── modulated_fft_sdm.png -A typical LUT transfer function is shown below. Note that although not perfectly regular it is monotonic and hence -the control loop will work well with it. This is an artifact of the fractional setting steps available. -You can also see the actual frequency oscillate very slightly over time. This is because the control loop hunts -between two discrete fractional settings in the LUT and is expected. You may adjust the rate at which the control -loop is called to center this noise around different frequencies or decrease the step size (larger LUT) to -manage the amplitude of this artifact. - - -Here you can see the step response of the control loop when the target frequency is changed during the simulation. +Below you can see the step response of the control loop when the target frequency is changed during the simulation. You can see it track smaller step changes but for the -larger steps it can be seen to clip and not reach the input step, which is larger than the LUT size will +larger steps it can be seen to clip and not reach the input step, which is larger than the used LUT size will allow. The LUT size can be increased if needed to accommodate a wider range. The step response is quite fast and you can see even a very sharp change in frequency is accommodated in just @@ -280,19 +275,19 @@ Typically the PID loop tuning should start with 0 *Kp* term and a small (e.g. 1. A double integral term is supported in the PI loop because the the clock counting PFD included measures the frequency error. The phase error is the integral of the frequency error and hence if phase locking is required as well as frequency locking then we need to support the integral of the integral of -the frequency error. Simply changing the Kp, Ki and Kii constants is all that is needed in this case. +the frequency error. Changing the Kp, Ki and Kii constants and observing the simulated (or hardware response) +to a reference change is all that is needed in this case. .. note:: In the python simulation file ``sw_pll_sim.py``, the PI constants *Kp*, *Ki* and optionally *Kii* can be found in the functions `run_lut_sw_pll_sim()` and `run_sd_sw_pll_sim()`. -Typically a small Kii term is used if needed because it accumulates very quickly. +Typically a small Kii term is used, if needed, because it accumulates very quickly. LUT Example Configurations .......................... -However the LUT implementation requires an offline generation stage which has many possibilities for customisation. -A few examples are shown below. +The LUT implementation requires an offline generation stage which has many possibilities for customisation. A number of example configurations, which demonstrate the effect on PPM, step size etc. of changing various parameters, is provided in the ``sw_pll_sim.py`` file. Search for ``profiles`` and ``profile_choice`` in this file. Change profile choice index to select the different example profiles and run the python file again. @@ -355,8 +350,8 @@ Small discontinuities in the LUT may be experienced in certain ranges, particula to keep in the lower or upper half of the fractional range. However the LUT table is always monotonic and so control instability will not occur for that reason. The range of the LUT Software PLL can be seen in the ``lut_dco_range.png`` image. It should be a reasonably linear response without significant -discontinuities. If not, try moving the range towards 0.0 or 1.0 where fewer discontinuities will -be observed. +discontinuities. If discontinuities are seen, try moving the range towards 0.0 or 1.0 where fewer discontinuities may +be observed due the step in the fractions. Steps to vary the LUT PPM range and frequency step size ....................................................... @@ -383,9 +378,9 @@ SDM based PLL Tuning SDM Available Configurations ............................ -The SDM implementation only allows tuning of the PI loop; the DCO section is hand optimised for various profiles shown +The SDM implementation only allows tuning of the PI loop; the DCO section is hand optimised for the provided profiles shown below. There are two target PLL output frequencies and two options for SDM update rate depending on how much performance -is available. +is available from the SDM task. .. list-table:: SDM DCO configurations @@ -419,7 +414,7 @@ is available. - 500 The SDM based DCO Software PLL has been pre-tuned and should not need modification in normal circumstances. Due to the large control range values -needed by the SDM DCO, a relatively large integral term is used which applies a large gain. If you do need to tune the SDM DCO PI controller then +needed by the SDM DCO, a relatively large integral term is used which applies a gain term. If you do need to tune the SDM DCO PI controller then it is recommended to start with the provided values in the example in ``/examples/simple_sdm``. Transferring the results to C @@ -433,21 +428,22 @@ For further information, either consult the ``sw_pll.h`` API file (included at t -Example Applictaion Resource Setup +Example Application Resource Setup ================================== -The xcore-ai device has a number of resources on chip. In the `simple` examples both `clock blocks` and `ports` are connected together to provide an input to -the PFD and additionally provide a scaled output clock. +The xcore-ai device has a number of resources on chip which can be connected together to manage signals and application clocks. +In the provided examples both `clock blocks` and `ports` are connected together to provide an input to +the PFD which calculates frequency error. Resources are additionally provide an optional prescaled output clock for comparison with the input reference. Simple Example Resource Setup ----------------------------- -The output from the PLL is counted via a port timer connected via the output clock clock block. +The output from the PLL is counted using a port timer via the `clk_mclk` clock block. In addition, a precise timing barrier is implemented by clocking a dummy port from the clock block -clocked by the reference clock input to allow precise sampling of the PLL output clock count. +clocked by the reference clock input. This provides a precise sampling point of the PLL output clock count. -The code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intrinsic functions in ``lib_xcore``. +The resource setup code is contained in ``resource_setup.h`` and ``resource_setup.c`` using intrinsic functions in ``lib_xcore``. To help visualise how these resources work together, please see the below diagram. .. figure:: ./images/resource_setup_sw_pll_simple_example.png @@ -459,7 +455,13 @@ To help visualise how these resources work together, please see the below diagra I2S Slave Example Resource Setup -------------------------------- -BLAH +The I2S slave component already uses a clock block which captures the bit clock input. In addition to this, +the PLL output is used to clock a dummy port's counter which is used as the input to the PFD. + +Since the precise sampling time of the PLL output clock count is variable due to instruction timing between +the I2S LRCLK transition and the capture of the PLL output clock count in the I2S callback, an additional dummy port is used. +This precisely synchronises the capture of the PLL output clock count relative to the BCLK transition and +the relationship between these is used to reconstruct the absolute frequency difference with minimal input jitter. .. figure:: ./images/resource_setup_sw_pll_i2s_slave_example.png :width: 100% @@ -472,8 +474,9 @@ Software PLL API The Application Programmer Interface (API) for the Software PLL is shown below. It is split into items specific to LUT and SDM DCOs . In addition to the standard API which takes a clock counting input (implements the PFD), for applications where the PLL is -to be controlled using a PI fed with a raw error input, a low-level API is also provided. This low-level -API allows the Software PLL to track an arbitrary clock source which is calculated by another means such as received packets. +to be controlled using an alternatively derived error input, a low-level API is also provided. This low-level +API allows the Software PLL to track an arbitrary clock source which is calculated by another means such as timing of received packets +over a communications interface. LUT Based PLL API ----------------- @@ -486,9 +489,13 @@ The LUT based API are functions designed to be called from an audio loop. Typica SDM Based PLL API ----------------- -All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and register write and it is expected that the user provide the fork (par) and call to the SDM. A typical design idiom is to have the task running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task. +All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and periodic register write and it is expected that the user provide the fork (par) and call to the SDM. + +A typical design idiom is to have the task running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received as needed at the control loop rate. The SDM calculation and register write takes 45 instruction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task running at 1 us period. +This can be approximately halved it running at 2 us SDM period. -The control part of the SDM SW PLL takes 75 instruction cycles when active and a few 10 s of cycles when inactive so you will need to budget around 1 MIPS for this. +The control part of the SDM SW PLL takes 75 instruction cycles when active and a few 10 s of cycles when inactive so you will need to budget around 1 MIPS for this when +being called at 48 kHz with a control rate of one in every 512 cycles. An example of how to implement the threading, timing barrier and non-blocking channel poll can be found in ``examples/simple_sdm/simple_sw_pll_sdm.c``. A thread diagram of how this can look is shown below. From 3f119370a815509d597d18b94aa98a4188600c5b Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 5 Dec 2023 09:29:17 +0000 Subject: [PATCH 118/118] Add missing images --- .../resource_setup_sw_pll_i2s_slave_example.png | Bin 0 -> 64069 bytes .../resource_setup_sw_pll_simple_example.png | Bin 0 -> 64807 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/rst/images/resource_setup_sw_pll_i2s_slave_example.png create mode 100644 doc/rst/images/resource_setup_sw_pll_simple_example.png diff --git a/doc/rst/images/resource_setup_sw_pll_i2s_slave_example.png b/doc/rst/images/resource_setup_sw_pll_i2s_slave_example.png new file mode 100644 index 0000000000000000000000000000000000000000..ca6d82152835558ab13576c89c43c75d47078cbb GIT binary patch literal 64069 zcmeFZ2Rzk%|39A7QHPVVqC!R)<=ENl5JKUotn8f}d#`jvkx0_I%!U$@kri24MKmNL zvr<+`2+{xbahxt!*S)^?eSPoyzW%@Oa;Zg!R*0xTr-fmupc_mcgy_T!D zJrRDx$MC=20r<}t{)vfNib+~Z?|_#o?(Rf;BYT^@PTt5>B&3BUq~K$Ly_yGg_VJ4E zhSx+VXM6ay$KKZ24LQZm)7nqi$qfz{7ZVm2mVu9UANF)}frB)}#KeU~g(aoLB!$JK zB;lK1A5}wq7aWa@@_@C2wWrgH+aa@H?``c!o&i_y{kqaxL@~z$ff5elY6Sm1exBqf z_qF%*a)N1FnncRYk(0dx-0jJq+S&U$*}_P~c_q|&MOENCWw^lLczI+^J0Hh@#mHdIDC*?lNw?#fU;1sw#ATby$a-O4)lbyZS^3}*6dAqq0 zy`9{D`=PCyt1DR2Zw|Ef^mOz4?O_gX#O0@3zN*wHVof=C`ANJETidz$kbAQZ+=MRVaS1xzZ&e2g<+gDV|+S}3Hc#o=)H$h(r zd;nQdFYf@#;5B$99E5zMhS(``<)t@>i7vfy_4W)v zPWtg?#aYOAzn-@Aj&dHwB7U92;d#6_)_eZ}#|amaf8BlZNxESuxs9t)BJ zezMxX_?+SpN&j=U@~eFy>yFsiA8#vm)?SB~>>9Z|@`hmT?QQRg1aw$aQHa@)w2&!q zxBlNVCo#AIa*uzbJ^gL_AlvG%7E5r06b=JiPA9u$mV*-FaWHgA>H?-tKrPbp4JGs zq}ZuDlD)lXGLnZCA!%HKoYef|Na?30ebrLKqyJd8ZP7AKQDwr zV*h_G{Qq+Wov`GOh)>QNvOnJc6#@Xb6VHG@4l{}U!gJCFNpo*BN7w<_CJez zzeiikc}If6MJ)T^KY9H>SzML)kNWFB2iJsmL4~s9f@F7Hy2{_cYk$#Of5Xd=W7a=Y z?)?A6bAN}zBqV-|V#Fx#DVUFpVgCsRhT?$#s~}RE5*(JDSq!-=l9&FVVDY=_Q||W% zU0;F%8Y$!d=dS-J3r5S%v;ySW`M9`1rSUVk`)iz;%@Sw!^U;>J&|Fw9qgRxr|5GmQ zABz}&Ah9MT{aYWT*ubBUS6k+KR!sh%j1%~KfbgGBAQVUcyPUwkpAz^-qUCa3xYX(2>JKB_TNs^h~ra!{sKEMLuu|V7bd$X z@Bihx@ITEkQ-bV@*z?!QjNiwZ|A#`56a}tOEa1-@f|iA9D^^?$q>zXVOEPyxeCg%_tdWx!qHF#62tOwf27+qOm3$qd=U+@jpu#}(9xFv0L?=f? z9%Ku=71EGbMe>IeC>2$fzi@G~vqO}1s-E^pulyu-|a?(!Aq#Qy161(0nsk;r;q*}C|LTZ>h=fUx+0bRV4YU#Mkpze z5^?_Xx)BP0zrv9ITIfJjJ%11%@$XoqAH3PJv;TP;Q)0`llND>^21gxDPvWgP+fi9mCNq<_d|7QcJ7_=>yY6q;b$uUKOaCr zae86?!}k*eH_qp_l7+$ z*2t!ZBTk518eTws?_-O6{ClM311=#NAexDd+qE}A-a!YXK+#`GqV!;1B&<@#8iHvB4 zZ~m9h{SVAc!PS)f`lp-wUB8cu|K)T4J4Wd*C8a-w-S1l76Z*I8{@>X7{&Vgip;YRB zw=G85YVvPvi~UR7xrEfRc~D{u1+!BE_dmU>jm+5p1-kwRJ^$Bk`7g`3epQZ9p5jlr zYvoUuuzt6)6x9CbOIW{W=6=TmS=-q;{U{0lj}7wwu@B?NmRWI`-?BkJ2!a*%AWmu3 ztVEBLWJ_`1|BcFSskir6{@4Mf@AvDUl~@^Mm%gR^Aq(;s%O@azYlOVth>+pky=oQL zDlIjYgZ>ty$uuPf&pBs8HE)QsGH4%s_DR|~j`lK7yxR9&mmeOyqN(-tI?wK>;f?f< z6eP8-KjGZG(_TAa-TBi`=Y!kl$LCXAXS#!0UI(QX%*ggmDJZ^>4Z71`Fx}%AGUH}! zO6)H2xOOY@80I%Wcr=E{O=2XO5Lngl=#@Y0R5Z-y7#=Rv%Ad2`TqMTJw+MSy9tp3v z6TInubKPSYwG+qC+IBZoIT)xI=vVy2;h#$W_M$xWn^BAcXD`$Gs4b5`n=jY(wcPx4 zmzzuF!gRq{QCH3zCT}%uv=p(!%G9+qef}B`iYLw-PV0Por(K;qaxPv}YUpBVvR_Yt zROsTju)#!0X9p&}eYqjioi{t=D&~d-6Q$i!lHn~}xw3P;{HWF53%I=1>t(poiw-Eh zFY0nF&{JM$+j{hCzmD#QuHrjFmKELgr#IWiY>~{z3YS#L`HmiLRhT^e;3 zuc4O0(8bw?EmFiwN4!W|{aR#)j0fvu#y&TR=#};Co6sqFo74t1e+A^$#OpETS9ADFE5j19R89WhT9{3@>$f_D;de8gTc|jxL9dSb`K`l8I zb4UETE59!~yH(8I;DF&3ElhW(fsIsUq=>tp42FcL4lv=Hq=ab*5pC z%;DGFuU0+L!0Fa>L8zdhMXo98B$KX8_12It`uvQpl?%Rpi!%|QCmn8Qv`P7BIg@Kil?QH6+ptxn@|%r$nQvinPhiuQU5EQ{ z3ZEY2KfmjAz(`p&ZaVMa{ZBR6q9)<85~T-zGq3&pP!nwj^yre@S}dPzNy8-I@hJ6+ zk;^`%mb3~RXrrspf41ZGV61#gsg}=3`{U9;?oB7wZML~nF_#&`F8i!v_8nW z9KNadwa-g)-zO?-O>XKYo0&@Y1{a9+e0YAdOP$sHO}6mbt^QV2>(PuXmznewo<=1p zFMi$Tq!@`ER{2^*#oz;vEiZO=vPE8A#i;Oz!R|(`eeabo#BWs$?6|IVZgeO$v}k&) ztE+)Clz8B#E?t6Kt;il9J+vx6#0oi^AI`prVfXE@ZZ8k;;@?ht@tm&QLlf@ENqky4 zn6mGer+d2UW7y13(-=lbI=3^I_y>pyV3^S~(E(^0YATN=p<)^RqS%f@M_Prr9 zK7%{W=mjFTmXNlTH|AxtgOpD-)i$KF=Aa!x`+iaOq-0A=mm`B!Cj>N zHu0cdX1ywOX5B%cS2EU&bh)xGQR;E2Ah#>y@y^-x+}^L8ZpyL>;>0nR4YCY77w12I z%%)SJWybBoWYq4j2o5kU^S#X^;L=s(!*c#ATKg(q+i*kFn%wQ&cJw>93U(LLVwqcG z)?;ahtem(V`0}>br){ULdiMG8M3cz_CK4l!W?Dj|=Gx_ujQ6GavD2cOX2$+3)(fs~J zGtVf~Tz*>SaM}x(*K!6wsY~bDo*RzdK^T5{$E{&&5RsIQ^2-GG?J@qejnJYvW9Im9 zhgRD5teb&T!$sf=uYx|_cRaZ@Fe7V?uXMNjZW4YXKZ&8ORSd%%&MOv=)0V$v7AXT2~`lM->9wiV5`-lKcm{b zb|9=ZFPQ$#W1cd{))e8C)zUb#J2K- z96$T+KBYX)51533;p#$cvGCmKD*4G~363rZ63sO7Xl~oxr>!Kd%fGk`)J9E1uqd6d zxvZKt!7ot3C_w6w%f#IPQ?ils7%n24(oqJkO>EKBs4BtHXwUBpGxF8_G|WBI=j@{+ z6sZPZn)@Z+HqNWDf5Y>YE&5pT2HIOuQ7W^a9&9i_A9)6Ruct0_JCgve&tr-wYa_MT zZVk~|y3G$xy;_^@9#;;B^Q;=7w+T;ZsP!}RsN2|LPq&v^Wz?dwk?%wyid0iVb63QS z_TvN9V?p_WnuVva>};0e7C3@&G(85RFt<slgjtk}`d>*N2 zKOD`+MY<}udpTp6Lv`3{ZjAaq|JLI^m}u$`(NSXDH%=&*WaLrtT)yxA zP`l7FqDv%aYjrwu2qf*IFr=i91T7XRI^21)DRs?%fo0vJ~%NHtd84S5p4L+=PT^hi;hGGDqf zmk8#|Sl7!jdvWU;bnoC)*oTMj3jr40IB01z*8|!AnM<0cb{~;7Klm|VoPy-n&&OEj zljzGNyx$Xwhfej1A88W^ntEaMS|ipDW1D)(;qfoCx7#fOKkyb?HiJj^l?fYgq4Z%* z8Q*s5_b$V`s^)a6cXXkTe~}EC6o|oZ7PZ1xuQ^T3iL|m2Ng!SXXp&>K)=xRa>DAp^ z$==Vjkc>Tl>qeg$O#)stGhP8M7vFI1&K2?;+oi0Mk1OCx5w|}zR`}9ldgM;!z2cGU zgLS--b;|N(0Exz>-23qSX#WSTJ^Z8mHGC(D_ZsUXh_4#o#cb0!?e3~Ze5a{x8EgMs zMasS$qDuE7pTz{4){vg%*{FGqL)0kO8TtL+7Z=9hQeI=0fTdgqYwb`G<}W%0;$OdK z>e4jaAbjwyWZ;*>#n(d0e8G`p560Bph4rLTKwuQ#sC_H8I#Ae|E^FNORueiZXH2 zLyef-C)Rmf;O)v^PLcD$EL4N-~?`_1ZLj< z{7A5Vt0d7|>z@6)XgbL}-Nz}4T`uuac)53%Y|TthpPYTiUa}5ibYtW@6T(+^CAuxU z+*5Bw;)NOF@DI~F+lbPA&{n>4M?wJoU5CL#pW0H%MFiWN9*~8(J zU>gL0rSq=S^Q0`)?mAOge1M$!sHGoD0b&CAV)(IeM=D z!O1e}N=KD6R~)YGA={!FDQpvdyTVl4ftJtvOP<`kI{cyMewnH9Hcf$1T(ShKDx%ioz5auAe(P&uIX(&+WWK>2sx9Oq<}p(mhSrI@Qwp-bK8HsjH?ttuEh= zW*KJg&MxW+9J!97ekqP)Vn%N;r0*`H-wNPtnlF;J%)kE^HI+6ldRo@G{Rb6;TWZSf zn=XtqXS3gpz9fJn&>t=-3t(ZcZKPVu-q&KQ^1br=LaWd#MN-&{<8`6^VP#v|S@f(n zO3tL0zD;#aSlX3OiucrPs3^w7d_uTDxG%UELfi5SZ#Swu_Pofvf=!x znwn&feB(W7-ea#`(1@V17uI)|Dv$>k(19cMF`{ruK^;mhSq^aFPL83c@# zHoYupRT{A|6L1gXALGyB^b=M~ZlH^1sd>&MfKd>MO$Yq$8k?8NRg!yw`xuLe#i-dA zdV!K5Zj?vmwdPdiUJt&JEy0Zg5z$-oc0_V-WN4IxwJ(*rw&e_Y?XT11e(iC(nQUey z1wp3Jn2lZlO?U~#avfXWBeiByF+oTxE!UsVf=XJ5C8t)7YgIg8ow-3@wP3BGmgLtp zmj-Eq8+@Xz76Ck$MM$V#abHJUJz5f%*Qn9GodqYT{$g?CDJkw!L^MW_3b?XyF$F2P04ek;Xd|( zlerb26jh&L9)hkWfn=~~^2`aapJ>?ek`mI!N01~Wc~i2?R-Ea@9?5)QbWAfNr{{RsTpxr2j$nKYteta=e0Cdr$ZZC=*y@~E+< zJ(@u)-NdX6vwi5c+=pPJ9J3END!Z-$mk@pG8D8ozYqoMq#siefLL{o*@E6r&jC_f|tGk8b!{H(JuqltM$+`Fr zsj%o$0R^ddR)C&;ymacN7P(3UO6Iuj%D-Q9z{%}F4`_l1#}?dBqM$!+6fTYi$A6q< zT)jm5Y$nT$*hZw*h-8AoL+0^?xt+^TR7YR+xn_L0e4$~A=3=^CI}?c+0h1Ywz@H2^ zXITCQAI6;or2pCdnWWus7_i8laM6sG({sGP;no6>F>T;C-_5duE&IPq|CkWXm?{=i z;@MXuxt%*m0MIlp=sv%pwzlC+brRr=6m!- z-dALKK6c+4A}E{$W*D?H5CvVSN-5-RX?T z*xLtnsOm;bX2!N_EqYjD=wueq0XT?z)@KdwlP{RZPd`QN+eQ^_+2*LZz(hKX-0xj^ zh7tK|2Cg*fqDn1#<2k{JjwW2C^mx4sJTxG_L-Xxn3YS_{k?5l<+E09cp+UVi2#-Rr`Ej^L) zTz{$%_TbmklZDhY3Bkz$G2{!Ssln4+h|K6%ef7WKY3wIX&3*Ygo;tpv7h|Bp2$Onm z*K+N_)p_O(i)jatLUlJ2;Ng!wR{M_7zxIz3cJNjoPUXWMygS6-a{nAYYI5|}NAgTx zVQj2LNwrbgm442cY^p^CHE{Ud?JRIKy&pK0ufTT10zI%ByiCa}J>}F=g=lvP-^SXp zx&ExL*MK7*DSq1i8k1TT(Af+W?t~F_$ZEc9*XOgnH4)gc%&eUJw@nvKyNX>YuDce%+jus40xK3Q>mLzFNJt*&zT~G+S{P~%}TQ^{#_$0B*@MKfeIR2JYfCb}eMnONQ`PF*00@1DlKtek!|*0~HWB9DPt~U%OZUpu zij;o}GY@!szR3H-{*mW5&oGH244^@dSto~4o{hTSqVt{m4p7nT8bBQ_7OmiZ>Rl5+5XQ^`*il7OVwcME;<~rCBrUE!Z8-BMRyJ4 z)@<^=*$9EfB6R3Y)dHNV%gk)Aav315dC*uP^@X!QzCPmHcd4%>OaIylueAtcGj{Ea zQvN-kkyi*QUfg@MEZwtv_(hfrAWt#$^J7%2v&~99r99tghMqI2?X)&dLw&ry^E5BS z24mesIiaQ49Y=#w5wVhZIwVlY;B!<(B=@FXsw>!B(!#<_ukIx-zRZKS_Hz-Cwt=8g zj`x9Zt4(4yY9#zD(%NXgBu0(0f0Cx+TffO>)Pc~T&NMVrF7DRBM8Xq+a81U z`uBFVu`&o;IEFqYOuw8*G*!8Vv7;G!W)%|;=&&zyplo?8=Qj>)mFj9NElyKX!MPom zWsr4&BWN^l8fG+{kKjI0qjTDrWx%rN)sd_WOX1;E3^P46W9lf)7)F6BW~*oU2ntP@ z>W>M4h+duKJo?^7gP!+hlIJOGAphayV?N!*A7Y(aJbyuH zb4~6*ZvVD%JL~hgID++*(Nz6OxIyqcZN-gCS-4S@WM*FFKx^AJ>qJVvC8#nMi4TOR z`)n}%8ae8Ijzl6z&%wBMUnm=6yPInxEZaqxU_Yrq=@=dPmLNT1APa~_UhQk}vYqn-AqZkKZ$IVPra)7PRB zlgpK7-!K(l+E@H_qd1{wmPx@4Br1-bSGf#bpV!c_nFeyULv|?1cJ#QIZT)E!mNpd# zh1vErmDMwsjTEqtAaoIdh4;J~79s*nynPhvEgayH3Utb=4ni(8ZF1beUy{3=k1yss zt_^z!v|u6a#+}R<=Hbz&*G``0@v8}^*->^b{ecMEOZ69-24m2z^0jTTD+TI^`1+oY z>*@yZ`z~FjS-Y~4oH8Ofu6KLU)ts}`SeBAmx#5g!MBs3BceNzRJ|Aws?R=+l@m}Jc zeHSxY-EIVA-0uSh5#6v~a+zz~MDk{_!hJKT)%#`F!=sC{j?TRm_o2VnjC8>qRW?hz zCULk|Q5gaqGV&tpBEt9};rbPDPOe#ow>?t}H0@hYW#!y^6gV?J))Ti)1L|TS`H5Pl z*QjY=BE+Ny1aX8|nE?#HyQW}teOFC5k$^j$8vI%7<%aHu`-MJy+FRJdb>>>=w}A1|A@QC2>5oTh z)#^j5a}=&DCFNKCCVp?c#Wv`l%-|TM9X@; z>tJ&$?{9e<^Yx`=sLT1C2A+nOj=ac}ZK8E+#x}6|voQM#06R1|)*JfWrv^2$F50BE zE_yL9txitWI&~e(rJ3H)%C7!kNUJ%n;dH6or!!8fON={}SKs9Pkl_6_{eT;x;aMfZ zlj}m4Lq=~|fU1yveEqz~DmG(W0yWYVnM*vy7|7NQik!mTaiY@zGxSEaL7g_~qO-|!7F z9uMkmY3pIR-~G17|_DUZ`X8-`a|XOn)l zIa&VY;}`d5dPcH$s+(_vJ9Y3)s4)sqnWHoL;vo?6$O;W7kc)KYr6)NSCx)HD(dI~( z^!GiApr)4Iio=zJx)i=JEb)_6!=Q{3nDfzj^wt%M&NQZ!nz)rg`(NQ%wmwYy}Oo zbAju8R99xD5&$)B^0C>`f>vTrc|cCf!M$?d_TQFh9Aun7scC&QHzSZeACt1@U{B%0-h7JpN||gkAkQ2R{RyFeZb{<+~V=ST&Fm`ztH*R67qt6 zW4jC>Ex$p}b>D&qg$#UT@WC+&u&|o zL~v!_@eS)4_dfDb4>@{ZYvlUVpuR|G)QOVYzGQv$8&Mx{RTyvndh}k57@zU}$QjM< zwQo~`#y!M%gY{VWS*}OY3~0~`MQ)-MW56WV;w*w=-Bob8m0H%MXVtOcM zH1zXmbYnM{M5=K2M1-lYai+?#ML)3wq)46m{0x{+(76!8n)U4O>+`)oJWG(!fY^Cj zwjtt_M}U}eM3+Ej=w=aVV}6i!>hifX*+$Tejzm#r+Qh;b|a71A?qvg*`e0a;LTq9 zR3kW#eyuJ=@m(0mIa?F#z8N>B@yk(@rn)8(mE_dZ0Gx8@eg#fl*wGTbeMPhVH%>&e zv+SJnd@T8r$aC=2h~*`oIwpbZ!G-`YJCcyIXqV55!_&Oo(pZGICe(V<5ACk@Ob|HO zNYuy=v*X2`Rn2Cgq1QA^13?Nj#GEN2n*DtA5!Akql4JTvQNnim!&^zv-8_14?_oj` z{z@xSuL9dx>d$pvhbGQ?ym~FCH)qW)5M7M-mK>eRHkoJ}MJ}rnQ-U2a>U{r(`KPs4?3|Alyl`cIx7}z?fD|t%am$0e zVrX5i^AYrkxFYA5ErvkVymBnK|L`1tnfgTOP|C=&Z}B=iGgQBY6-#Sp`YFmSn_}MSd^~?_qcvI^iL~9?MQK8wN=I)Zm@E)w%G&k=ZAj>TsQ#Vej zaj`+*$X79UAx~3n7=9<}?Q6eYAGS?O3tyfK$Bco9m}^7nyv>{!}w z8{|ht6QNaxNR6N*u!oQ)m3?Soex`VSysR}c%etlGOMw7Dtn@u+wz^hydY>yj$N=>i zFb~g(x9*Vh@Mhk)Q+O@!i2)rc?2||51-+vm zb?)8mg3u1R3j&l9^)b@gt?zwre0*J2yf-m=QvlKsY58>b_qzOP_g2i=r;B0#V5wd|dw7L8R)lZHV=`Y761&#EV#G>`d7n&Jb@V>?5ejk`GtE zh?BM5-IA+aWOh6Mg{v{U!N+$<{5Mz;|7n@;`~tHiH&K1n6Cj&zE*DxOmiK0&mf*Ej+=)~jGbpjYX7)2}DVD9iA%iJ?PFQfX)GmxvL&ywUp)hvd;rJVQw1 zUrc=D`B==qJ$L;RI>RHwaK#hwB(qWrA&6Jmj1-rGFi z7=*Hio~f?C&5KN3c42OXaxk7iGKv}34}mKk%+4*%@?0emRF8=HKEbG%B3fyQwa5?P4z?+VO8iV9&eE z+~|ulZ@jzL##!&Fa8`!|QI*97a#&pHsE-iIw~{Ycx<`%N&s_x$@$J>X$B!r=sZ zR1>~#==O9kt{yv~^X{uOiqE8)V+4vmzdQ^xOH$KC5kL7j+lX06g`<(%V%>A_AlVY~ zU2lRr5NVl4e^C_VXvJ473LGAfLK5}8CS=e$_82KJ6yd!Zx)EW6)-qek6E@Xrsc- zccdfNx@B10t*4xDV6S7-i=C!p!z!0$0oh)*y?udP@7QQ?nUg%9sT|n`muLbYKQd=nr57X<0VE z%Cfj0_$-95-$_Im#oLUfc?Ydak&>;N@-gWSckWN`G)%KnS%Sk*k<}L-$GddoUYS?E zTt$C5ST|QuIOyTD%4Qk&wE6YU>u9zdRO8YGXBgOJI-EsL2O2cjc?i!wJTbBidHak$ zD38B<_MMA=ziOGqw^ifT#@6(b4`~xs^WB9q>4{_1gWti4g{N>-^LP6mjrxP|WtCE{T)=u= z8KFr4v1!OT*ENzuM;o@*&UXIN3vP~`aUnC6Q24jArx8lHm2=@!>-y&? z1GA5i%)hF=2-^eN#Ft@9vvv%#ojZ*>yK?Wg;cs`JOi5SDnWAd|uQ0NhybvTq%pjD$ zrPUse+^_a-Co~(Ylmt3YDL?9IeBmh!EYZUVleSG{2T+9z6)9&A44p|$Ezoib22lws zaqj)i9J?WhCe-=cz7MQ2gr7ghOE>0y=zBq_1w(Ik5c+}=l11?n1d)u zBlEEMQAAmqe$_!jN9yeMx1yx!12^`01ZCvzp=I+&CmfAPPU?(ZwktwI2oaL|U;;~oSOg8%1wawaJ z3&qgy*IZeqGch8yJ)+i(A}cVFB6)$hgsTuP8?^!5#_g^HZ^M~%b3t10 z96k~s0hzE%>DvpgOuEl>&ukK_arx57ch)+A_{o;N^N94i6PX8Cn9Wy{-Hj)X1y<}? zO<`xX{?NskMUH?sXWOr;V~a`K?AtQ*4YLe|_|Vm-c|g|rLI|2oy)ljP}q!%E5!$-Sn;Hc&jfPSUlzqb|bC=Y_#|6JXp|)35z3 zho)cL^KmZw_POcxP;JJ(WVshxL%(_O6E5IPY}T^79}_apttur32wNn}`Q$?bQ3J(0 zEPZ_LA`7_0UH1=GWS0o$K^iBWER~OEmpFCZq{DiOQwhMl_Z)c%%1&01B38@brw9oK zRcJ#tRPil=&oT`J`R#mPIq@Sjiw|_i7dWx|pf}7lhiCx@p?@&w#&a$ynGVZr{?M@O z3jScaIy7vaM*LdpA0~}`d=@EJNyK>@-9BW`pBkKh|NG*+At*EXWg_KgLCj^o?h(R| zrg4H&dHUOAYgw}ykPrn(7pj#evteWQc2d4g9j*MExZO!p_D`>>FY<}o%M&G5^PBzfr17!B5g>B}d1J^QCleB<}={7?0;=naP($ptCg>N07 zB?3R~(V1DE@9=obj9&JRTP*vxz|cPGVj}cw`YviKgZJ+Yc`jBBZcB6G+7U8Y-l&2) zgqclj1K#yoDCEJLdnZf}a_hJYkBz3lJeU={$f+z-alDCiM>r128IsxBJDBjiV~_cd z=?~qEa8)vo+2Wi8W>D)?)Mi9ppwo~r+c`+BdV*N&RcUdt?iod#D& zDAkmm3RGZ`bE@LQ3t0R8sO(*)-v9+3dwglbWFe-`<66BB;SZt!7A&|OPM%<426k)>E@hl0sjhPnY=jvcWo@@A6oyf4n z^~EUsE)dkJd0DgxlE20*1+?vvsme$7mE}!YG9aK&%{HblYNfq-Mm&NeKRyY_=`PNe zjLGM!(kYW{QzMT!4U*}QZ53-*%#Z4#a7@Nzbl~@IPd9uYB~COMmi03kV7DU2Vd1#V z{JGW-)(9TB(d;k=%UoHXdbOg-pZ`S~<`ausv=9+=EH?VuC^DnX7*wy@bb%Uxi^IC? zE%DUqFOOjoPE>)nk3yvEebe?_SN?HM>ANk?(AYU-pag9Y!(vx+pdSw%8lv|31O%(o z8HLZ0g)Uv~MG)-LGIb8M0zYwEH(5?ycn+yU5SXC|by9nV{-C@0h{F9yw|)dm@9tFUN2K_r%rGn9&xY3qJDw;Y_0d?h1s8yPqK)I;3(!Xut}9Lw9kjDElH1 zE^q&r+V4A*7rvYWMWt&_FLaD9ey81=!EHdwhpiZ0Uag8Q(BSAq9ukT-+PimF@aJV( z_fwAfPj@3ep} z=yWZg(3pRF(Q-7&t0k}c_}VUbVrSfipy|;MJpd+(|_Cf)Z0<+Q{ z1#<}P?T`%0fN}=3W{q@|N+$CNRuh+Hy>1i?hzi%&P6XHcuc6ywcaq&Rl6&2z)zM#~ znE3B`@Ld3cwii@&QnpvlMD)*%+$pw=m96Kfk?G5OVQ5;qxS!w6z$B?RaOC#%`)g-j zA+$9}#fxv76x5p#=vN`Fr@@2{py{5#Gl zFTT&-0*E0GAo-Kr+NvwtNnw$P0a&taSm+5DN`8GwSMI}hOBA)>S{v3i2oVWs@qxMW z28B<@46==%KK=mA!kCgJXu}phhJG(t*%n|w@GfqGOB7m_Mh#!Vijg)hu_ksR3kf>- zZkonA!Ep&=&+ohhZHMdI)T|^>DR}GZNc=1`IJ>XT-@2LN8QlzxJd!-4B{6+Py)sO~?w|?#R%E zIus|>0~FiSJlKxICgE^8Yv=@gbOMujy{j2Jo6m5u`$AnPl5Y2FKJxY;HkYl*z0_5$Y7%X)vM9wqIeb zZiA}O_w9KjxhqwrNQ(j$F(`cRJONx6Ss2%t*c?5blUBoFrxu|1K8%=+k)b2W(KEIESjn!LmY4|UjIH1EFD zm)aOxk-5AO0=@z(7lP{+Uh5kQi?-`4vbiihhhfgcbsXrP0SA6w0)^cukX9;xjR}i7~7&A zRs02NFJb4BEl>5-sCiJ*g2QiR(AxRNG_`9`Cpp)fr5S(Sc8pKw7dy}`3*?sMR*kc#CZhGj9`W^&A{CPz)?%a$c~YVsAmBv0 z(H^MP)hFp~Elx2BM0wF+2V&?CTCCG{GA}>HDA242sBjdf))#b>E%XA-*i`+#m~>2& zmN+S_Q0UxvP~gF(>EVOKZ5QV0+XY}=IJ9u<0aOZggu3_!?R?$-t8X^4d`D{*l-%%F znW`1ZrFXDCoskB+3XBbnF}4$$xZqd=DkGs&thEc!L3?#hPpLEtUkXZJI#!<6uy_IC z_tN94xVHQ1xUfiSkA9?$FYBzXkM;j(thSD4-3TUcQ~A@IZpH_V@j-+B(DO=aVnk8v z8Ydnqb{)&CP6l?VM;mm31igBymm;<7s&QdH(Y_2yCDGO~HM|>&OQ1J;b31|lP)UwH zy-f2q-ox;0kFjkhsa@@JRny{XtC+lCC!WuESV^{6XbLqo>D2oNVLT+p=W9>jgGN08 zM^)yW!Y_kI2C$Ql%97E0v{j+kfG22uxOQdl%&j%GRcO+@f0 zbOX9iw*0urk+i=G#o=cb{n2Z0_xA@uCFxL>p4_De4GnfARIT zHJ|Pe4Zj1i1p5W-*uJ`8DkHY7WCaB$A6qpLk>()I zKjCn{gswMN_Y+G}LS6(`Y)x=1o1P7~>89$o@)*7D01oR1HK}!nv3lx2o%CaSzor+GPf;7_lEN?=agFu~*zzf9st&K7^8Zh!N0Knz=(qlhB=N z8$&>=ajnJpqo|uspF)bJHxdAox>G||eoG%6PJIf!ZYP4S<66;g2F}kHh^%ahAdwJB z+C-#^amZ}bUxM=#xn*MrVkOEATdpi~Cdh5pA)%xBARk90T6?@TPeWjdEm_jc$nM6g ze3q3skDPS=Hzz5<*c2`e#t>0UZJ}RJ5#!=wy|Zawe5>%Ix+Z>nvhc58A%mtz+J1e; znS8$)7d=%f6gk1Dwhuf6Mk=ZHL;zh=up4OZ%ZI`B4GNFH@0c@V_f~vg@x%+?N~O3~ zdxl-$gS2sP!#Riq3PU>gV;QJsAJreuZ!@4=my`{??T2&B8I+ocA_DqRb|Ax{{QTJU z?Rox~S-Gc=gQW0}3yH#1hN~F(oWi51Tm;gaRF+D*rMhkZ59v)hP)+-H66LfgUvlX| zLG|WCt#ilaZ7?Ivd4_v`+-(BcH~L++4MXOemjCB+gd3?5cS^f|x?}3t@#%Wj@*)i^RzYsQkJ4(Uksd=aYh@bkZd5fl>?B37` zJhsc5#Y*NI-2!DZ4L9prc%l?-avHXwf1X&&QZGUWeZ)AowjnfQ5QFD{<{ofHzyQmQA zEMyP5GQSeCor0=vS4BgXPBd6!1XyecPx|tg$_A=t>;nSR<->gA6TjV?Ts6?&vIoTl?!w2(a-qwiN1s znBrlWklyP)EK{JuEkJl5z%=%^O^b`*cW21V>!H{((Ai`|+wQ_CJZvMi90i{J?v4XD zj3a0_jskAY1qH(sq%_k>l=4aaNZAsW_e3L6$^{4yd*5~w$4+S9CEg90i@J30!;M6< z%BA|m4XU@>Fq7j2(9C(717#4N`XHa%X|Aq#JWc$KVa6XW?E@~Y|i)0ZV2@2M;Cyn(KNE}{ehIopTqt(cRs9P+Q?xOZpU)SZ%o4f>DKv_o5g;c36N zXT!+rvKa>y`KaDH9CuGLlPk4YzI@ib| zpe|N!SfC4PkYxH>hYB;f?LY#_iU7eSn2zjx-z#$eO?_N;$XvxTs%Kk0;0W?0T~JWY zLj9Lq`2N`H^|^>L2-!Q%fA4c4)Et(M7K1;O;sQV`Bru{MfoyX;dsS^v5>Py_%!eFx zxN`=e%XIg_diiEu*|*I6C@AZL<`L--*p8oJf*O`KYykdbLY`NPY{U4yksKb=^opge ztQ>kjwq~&IsJfJZWiWE%dC zK^^QOzHnX*Hr+mS1R=}hTPed|u%<)H@pmZ-*CNr024(*+o>zAhvQe%j{xhJ1G70yS zarS)udv~z5H_#N zTi- z5e3CL+JvjGzQe{m^PP6tvdrDD-E|NobaS$5^@dFs(t?FNCmy}L{IX>Ek6hr<+Q8A( z-Nb+BEVzkOizgz&S53f1yHcECtt0cs?Pm(9@{ZS0U(BrylQ)D)Ogc*C%Hq+5xl(`D z@Ft*g%#Q_WL7#BVoVu0b1b6K-V3{98F@)>hvACb@{4z&a!)~-APdAOr5K+D?A_Pv^ zCKzF=r1OO=)*0!rw}8uUa1$c;sI)17>c{nPZ5I6}W$1f8GfS2Ahz)z=WwS*R^CLg3 zgNdBrCb(bYDPUy@ix$4u_(F{!?-cRy@Y}KDVd+%GxNJ^~oz~&B!nRQnqh@xqHXa|L z@Mne=Uqy5fH)a7$vf$vf3ixYma`5Mex=Dl%e(JNiKpx0eA8%r%etNu%^=i0hBlk%e zEVi?jUO>I@JBY2yLUiBTzI}AwJU0YFbP_(GKZ4b)=9pkkNe%VXAni%hEj9v{(P-_8 zmhyVCmKRo^um!?J`W3@k*%AYW#D2#iZz*lND`%)tg6mKuoN3$MB+QPO=R2-zJ8WPa zuq!ua{un%b-AYFbSKIPLs+aYpQdo5IfR;fsN+FNlE&s7p*QR}q?k9rS8s0jejW+sA zw_+KPEwMQ35`EelgAPaK?YJzQLfxP@kI0d+DI&xjkG>eUrL0VA!Aof{Cs(eI&*zyI zf9ZWAgw7KLLajb1%K1|7bW|cYhgZLG;^_?~*z>a+Y{Qn-e2JVqUh)I+jC*Gzn;51Ra*hyJba+_GHy9Aa9uGB)pss zCbr1dB={jIAd9i*-0dsxw!>JHk__o~YQtDJrgsu7VW^);@YqVt`Sq8YVA!4kzLG_n z$Z)scP)UaK8@D0DeQM{NA*%%!=D%rh{1hV$SHnpPgz5C8V6Ll@bPLJ5`b?nPq|(y62@0qF(>i#-L-sgGuIo}`Of8TYTeGdCtXRSHsJ?|J}{9>fdycjt6@osad_LDimv|4^BzbbbW z{acwnNL`l}#>zF~?!%gc$@cdM!gqx&{{Ak2rgsTjGhj?=zv|S{{u#r=r2(eppO>@$Bc)KL_@gyWqUFyJkT%eWU=-F3K(01<$M?9 z&2Yikv0SVU&D2AN{<@S{DhBzO%3jqEf|xf~kfHw{@3e~_e*Fq{0|VUH8)bMvZ^>^A zt`}jK?a2GqXZ6?6{_pws_j*q4|8-pHsWCF5`a9&Gz*DM$__oI!da-i)WDP`U zHzyi#9{raM)7F(C=J6apAu(!&l-}Wl3i(VbMka*v+1t#D#mMiUZRkn2B%<11dBmW? zmVrJkjLqITlJG8EE%%nJv)-e`U0a|u+ao0OBtE@%By2%UU0#?~_4@DcdX5xX2O$Ch zfaEs6d+N)+!5kiI*mJ&sC;`64`fu1DdnYot)y#{hfgwTsP+I4FJ{*ydOXvpz90cyE zK>x8McmSII0z@L>0R*Pk_H6UltkAdjx0C^k0#z8Ip<+JUfL|cVx@!Vc6POd<#PG)) zeR)m_Xdpo>uxAA4ev6OXw3Xjk3M$}TPHR`V^L%>{0e$|3=-@XA%q=(2eFzYb*3c?E zEbBZi<`Ql|WvnwL=4t)gV7*ZO`2l*f`IvQYNO6Uvx_*i1838O^qQq2-BR&)DH>N0F zU`igg-dJp&d$DL4p#S*2$o%fg1^J!Qpjq16ev_0~1Sz7xoT@weyD@sa)moK3pVqjB z>JCN2CP~Tb>VVyz|AQltZuwy~L`Ojgc2^jd-wZG;7L*mvlSwP)`%Y#P}tuvdjfuB1YzV0 z^#mS($X_^+8r5k=-d!9ob5_T1v{2WS2Gz?4z7+9L=(i>hJ9jvjKBfmfk7KOs;Lsd| z{NV;-0){{{h%*or{-+DTa_eGTjvkV7DSrV1Nrt%J)@{zBJt(@BLi%D1anr{G@&h%0S|x!DgK*U=VDN^7$0ma>&BsDxzZ(=% zu%h7Azq{PGsahAXH+L_<1DG!_MSM}$Axme=M-jO6Lj5j0QP=FA&qOkSqEF+(Z?uMy zBci7pkkvK5N--Nl{1%{wSB98exb)>47T(wCC68yrZx4>2Rr$x&R>F@3>##XgIJa(Z z1KDb$c9wCRy&07BLr3~s!zph;#(tt?zHDI&>heDj(Z)VtF2a5;Pzl$2X$!2NHH2<~ zB&%k6h`^&0)6SZ-L^!v%`da*-2g)6%bd2loi(&0Yom);^%(Obp#lpoOLYIV~9t5+t z{p^~lhGcArbw`!k*I^*uMX~vw4xMBuha%|#5LPR?0rBn+fVXZ3hIH9hzML#Sf4&$1 zE1n-#`_GEPP2^#7$cX>Psjk3bQ-(^8!cB0F)BwRFZH5ifcsz zg|)qiq@#lG3xIWI&byHK*+K4EplRwm_vqd*pjkH&3fZl?)dAIVo%!ip%@l?KdciY< z8Nd}Dg5>CDNx}oj(+nOdg(z%4)Jgibw<WfR`aM=Fj#0mTTXo3j~*MVOH64s&9X@0h^LnNHv2yTTPB&cd;I-j z)x2Yl*E0iUo2gC#ZXBZ0F16noGD>I&AUsJ@MR|eZzgRK)V94V-*4-|y)XXry(bcAzzQ>`VQUD% zn+4v?@|n-GAt3&Nxcm^zPOTJ{@WEyhRKxMcY!y~trPUI!FT1W(m_YjZ!lG{2`3lDx z%CilrBVDQ0Rlj&hlXzWR`ZHzWW8JgJmhv*{pC{>CRPNblh&(f;B$0ze;~F84`~_08 z0GWpEMd>@>v10ao0vJZf{%7tXK+~TyI0k5}c{0DCU(Up3^#_f>lfH%c?13ok-4$ZJ zp-g0hG*tcP9p)@f>9|JsL)ozm`!+l};L$gnsnin?5WOV%?!UAA8eY2xB7OV>c%=ha zsA+%W?=x1Vpp&}&taDAZ+LoFq5nqUVCcmHkxdH!`b8agTIqVc;4ObyMpwh95jBX+`ess23GSIj~w2a%C}FD1HOg> z$Wi}rgodM0)R^I8+@q12q@#VC7gI%>a%<{=1F*?cX_@mK%1e3Pxg7c%DCvTh!qL zTZ)&}&c<@uEy<}D*P6aK1^)B>#gkzuc)wYO!FJsh6-^zm{X?Q+;hZ{y{%M`NXQ#Os zjY_XZo6S=!E$pp&3jOoqED^?XUDWU-pU0WXR3L?(D{-837Th&xlQIYE*2kH^-5C0z z7Hl%OyWn*C&t1#`v^9Qye*an0K`_(jl_$>;w}3`PEAkQ1kRI2qE%BQC33|d;IeHu` zvfDN?zus^Tl;0XDXgf29%h++XdfW)B8mYB%y1o12b^)OS*ef=TY0m6Zgstr6f3@r| zbRk>m^jiUq#Bd9#yqRlINDShRcu*!JmD^zZRs}q$3Mk^f{&eiQGyf_Gm_OEGjlcnN z_?BB?sU_9GN3c5L{;|?hDIb^`t9wF)7bXc>iESDcV)0r#^MgH0EfPxlL7Z0(la~M` zKc?}!Q7D}*@+lBPx^K}uOHH_4_}n=r0O9+?R(zYjPk%}T`+G-VlmFz@ri@kNq3tgT zI-{LMw<9far|+!YTMO&pN*={fD5^4V0anc|#7=Fjp6=$G*CYI9LH`m+j@*n(Qg3-KX3nux zBc~M*6RVG_ZhW$@p2L}*b(#GRuS2BY^tD#Eb$uC-HUv{-qwk!D!Thk&J)T6i`qffu z7m1G%Wi|IND>qwRyd}^q7bRX(VmkaY@RHuY>}rS^AM5CyOnI9`b=xnmEYB7~@<7hGOStjkTu(Rnhv_Vk$wcMeH#Cyqr?EGN^@(Z3|xTLGukxE5mL#^>Un-B z09TYQLB$r6G|q3)Xx*VT8aF_RxxnNSOetw3NluPLtO${bfF=<?|#(WlK<8`9i)a6gkPc+ zCJ!SgwX`cZTczGPa~sB7`UWx2k6$yLH9x(ct(ZfB>w$tX=*3Bf9NvXvJYx$U)lbp${gry-j?@99k4g1W92{Z?XFw$3Kl7?oPO`NoE;Fx zRKS@Ca^6qCy0Sr}nGamw@|A*lj;^#JL_Xv{T2d2_mbFE2ke=n8zmil$7 zt@D&1m+G$gC;v}X^2@kd~-E$lo>suNIq zzBfPR-#ms6_}zQ;_wrzn!@~!aR&VW|yDyT;?l(8^^cs_KDv&K#+Fx>JZjYvuuoc8q zvh!}=YPM18dXREA(4Mt9sKBh!e7WQ(t+0(An8BLlC^S7F5jp&p<<9p>-O?4tx!qZ@ zTf=>I&kne{r&`}4TwQPG360KG{lCiHnircjIRr5>vE-sR`=p#c%NW`}=VtcL>!bfa zSF;3)p!7_WANu>NYaiR!#sdP@k4A=2`Inhj-#*p#S*DMivo=?Q3X|Gpd{YQaYr&;> zV&W=Wbo%ecF(w-Mt{u7nSkTwvx*jMKmCvMKRfM6GPn6Pjh*Lp?m^(wuwuYPgXyWmP zGzvaKD&+AgK6=Yd>#F{urdhAl?0;!K0)|yL5t~l1^$g+KzQr*_!f`l~DBQg0|CV{S z3R{5l9%8QPl0~u7Tjge-;z9RldUtj*yF)BU`mEB?1tQ#Uu0-}{S-&R{b2%6O%+6(t zIGM%r)`x7@oi{aJff6@KD(Iz1lDK;mlnR~Ezs?`9gmw>MPs;wrem!!rdg9Jv)$7e4 z6hTT)L{vyfZ@MBQ;HwzM^!8+~8(nnHeOM+)?MzTcU5RyQhO;$2FO1PT3_a4(l?^xSriy@HSIZ4F{jDsDf?L|hVB%{gB$W?wjG&4 z`;w8jKiiZXnjqbNMdlEZI`M-ui#+Nrshr|V3x(^Km^G96GZv^xH1yHLjA;}%J4c3m z1#+y~#HivG4HQI{LtnEG?Jk5Eo@uD#2T_1wUX>JUk_K4~O%7f=25WCn9+fW<>u9gCmqZQ?b z-1pG*Emi}n&xiKD%I|l~QzcwYkUi}6xFc3R@^UL-7J|XoUHbi5;a4(w)*@nPOq4b? z%6>mLV&$|}Rcs@ZhyU=5dbr_@D@hsdiNSDm!? z-+h(IYJY5Zo94FP<|z_}Ttbz53cwrkbthuuS`vA$N;>+b^j^aXZ~?!lXh`AG6fH@O zls@s^JuZzdpy&>c6{mOeB2h0mzBg|7m0bwaRQz2};W*Kq-&_J6x@{B^)CAzU$6l0j zf`L?O4v#NxeZMh~? zpXWyPaL3W2xT`llE~K$kwyoH#XzSj%AOFPJ=iP6$ZD|J20`L~E(Fh}IDrpS8PDn;^ z+G>@pZ54vKMVoZBuDdm=F5Je?KLL}fCH&{j8ZYkZfQ`o~ckDUY(r!%U0I?{FNq~_f z)(ZtJeQE^#ilW>{GW2_|$ZTNZj9*c^*-x$Goo3xk${z z$$x-<3%p5A$Wt<4Q8@_!idOMWj@wp4SAPP$ij8=wfge6Vt#)g`bhBh0^dM^D_!k!d4jzFw^jUJ{&fRQdm<%)O&sU5{mws1LM!iS*LoDEU z!LHYl#jWd>(eUtAVnNvbi~_en9D?}1NPm9?2sIPe$0h|9mbr#mdDV{N}oobFft@bQrUUXq7@ z(1gxSW&`zyR~{0w$ap!ET~0B&@MKncWW|(&kQu36T|#;DVV4GZ{jw|aE}sv7`ho6F z*%D29luo!R=@QihwF{xa331^T6i$@8Q}+`={3hg6Ri6^{iJiv2*Siu!tLf{*x}gf`0D07g6`X#p(iJUmt+gT=Yu=jT_X-_ z;*%%DoKl9&Q@XuBw!K_N)0Fm5;og2fh3ky{T9~!b@;1X_^p;oWyB2xlNyV{-fPe~R z;rcgy-8`{^!s?Uf5NBCTZU7Lnok^@u-_uSz-FAz(gYov(o9y7V%_dtzpU=Nncrqmx zz8~#+Stpp|Z7c5m>r3F`++mCA$js>0%8OPyCgYf^X*T;LW@-`qh{$$O0>$l^XX8}; zjfXZ;d4Hvjsy-m-%`o-ruMXD=gCIW+oM-L!UGtdo3`a8ri2K>f>c&caw_9 zXfK!S)}+n*9%~cnU%Q+Eoq*`5;C*`=Lt>I z4W=jkI6`X?qWQK5_w)-lniA%dy3`2|p|RGu=BaRIH*Zbt?jYkMzt6L&lCLVS3iwsU^|?VYjMAum`JBn{ z4Yv922DUAvZoSUfe7?}ga>)9jmz00^Ml@ocekZ)WG%b5~TbrQNvq0UfQ*Kmw;|rQZby;Ck8a9 zZe?YykL&NA$POSp=^y)PPOO+JMv*;9BIL$5@T=8U$J;GY|)}k?bPofZe^dHfRU256jVj_s9 zxa#Upr$H>0V=<*9TBD~%8rgr(r3}1Ta?5hb9xGwR^X$kmpGjLF)6VQn=EXv3NMF<+ zcz2F8X8X?6c9#k$m{CS$YNb)#zH&4)Ry#z^u2aj(_|_jV>#Ab6P~u^GNOJ}5&Ly)4 z;j#!5DAHT248L?IysaK!u8Vl8im!U*0TOx-ezI-k4kyO|s2e7SDx;|>@6NtQ%ExF~mFD%#3L}W`U(hP@QYu(#)G-LNO_g-Xrj)LX z{VTxz-mVaep&>~UF!UHmfQp+z4pnK<4Juu0*=j=ZFawhQfNx zIrUpP%Tf^w>34eFEW-_d?ye_GmO+{^QekRnqa#CGh|D?7(+3M`9Vp(_DK}a(`wNUe z64bAZ-~?PJ>8LE8Au!qdghNia`(}?deTr!ZeqMdP!i;XvAL(@g^r?q8?lMm4&>4E9 zPB25W22~sU9Z6n+yjmiqvt*n~R|V&vTCuYy=mB?&WGxC)OG+${?$fE?vM&;cz{by9L>9OMjYd@J3y>6+Pl4 zeSC82hf8axOblo7@eUJ0{Do%|i)Q*AYw$!-|7$&kQL@i|0X*Wi@yj>Al}FE)v~hDh z(aqK1)0=6k1#g?gZoX?G5s0aD`Kc;SxwlY6_(1!fn=Y#x2#2g+-%e6*DX81GNKIWS z+F}c?%|7PUq1T;Zw^QMLB#%NCh2*t}ds{9^cLhcJZlpr^u-n-J?-N0maegOC0&afR z(Bcuv)8!l58Pm!Q$YY}9YS174cnTSBo;Y`0bIiB!8HoNpiAAU_%=xMx&T`m{-Q~jM zPhSD+(T2_6Ovp_STJ>$yelco!rn%FMe=eoUR*x>>!A?aA-jN-Ids%B;-+@@V7*t=6 zz680ev=f0pUR(Hu-r%pMJy39L|2MMPCgR%;IMozl`gguG7viu4=+tnb>I-^vBUw&N zY3N);^voxtS4(>;L*KM#P5XBjV!i*aLzi!abQ=l%Mc{BKhhybd$@y58AL6aH4Ma(E zWNa@ahrR)(Sv_}~KS&lXP3rMqzUx<_CiHIaaBZ<+huq;_|56+uU-il>0Hh>V{$2E) zqLs4+*bdeL#HvHRM`Ttz(xH>hh^QNwQ5?88`De!*&pfovNKU7N3W8An?9$e>?jtCm zpP~L6f)MhbJYzt@LB@-~x#>c(jUO`#Ks;2XEe%SLp;`;Nrx%1iJqES1Pl|Zv35^yl za+aHgEqU62G`##T_p;l;00-0RTu+NooN1Xuygz}|V|_FtoUH+gT68c0uqhGqw8ZBR z&7{=V3mb<14H(ljEYar^{PDu2dzZ{Ik(*I~Ah`%&1poWU^lW~x@&r_a%0Uv;4rNOR zL_z2N^Ya)m4ilXjBCTHl45@AMp9ffE*A57yo`{9nHrPKPWk0Z{tLZgNermr5(;JNB zrM4lR_zv(`1!CohWctV)idXW2g05#^4%AjbkoZ_8H_`VKT+E!}Kaa)ezFi#gz<$xU78bK9CNTkGg7(fcMEwaMmh9 zO!fH<3LK!26g%Qv4Er5aiS8N0#%6o2UCSLota|_R^<^qMe5Ru%q9ln>l>la6M-afs z6sux$C}u;Nnh6TKrXOkzb{!OVU`!APPH`w^B4%{S&}m zw&2rWe)`H=w8UX=qXY!8X8F(asU=OY^)1-OT{9v9yBj@(&xq)$h2D5V;~Kc1^bxDL zHYwa1$i&^5>E(PYWw+)YuYnkB1WHv@7r&P1FMtP0W{m;V#9n}5k@38$qgXzm!M^{- z8zL3z5Or0PbZJhT#vl<9)jiQlvw;D2`z>V@!Ge@ZJz$$OMLaz~h$x_X zLMj)7VAkfe|7HBr6I4I!0+>2gV#s{^gJREjyNJ57fM7=d z?T#)En0t<0K?HUPHUY5^<5&#M$5PypV^WbFa0od878*T6mpyt7e(5Uj-@vo!PP2Uh z%^zC|o3o}qcQ4#2DUw5c#IDpah+5r6xah;odjWL_O`p=x?kmU3&{W2x2_gi)KKq}4 z=Cy5!b2P&#o8S?rkUGOptXvTxIfsDs;Fk8r5e&A!4(C#^AP@EFzdY227R^v2(rXG) z$sd%_wVmLE;W%|@tJOmIfYl8)u#T@ila}CicIAIOAlfn1+uJ9lH@;7PJ~@x*R?ca) z5K)Jyw7i=MvZi#W%G*zKD3|xpb+RcDvd7a=YJ0@L!bCZIFe+*5Q@OG!`|#&}=Ffe9 zdB4T4Zt_{%#*201gWfVe_@8xOGq+{F`ac--b6Te3ePG%9l{VIY^(Rfk{FwnljyN9J z-S|v|c2PP#HoH#G`>^zYDAYg_Vg_=;g11}XzG}7rj*DBOh#L6{10(e8>h}S(O?ebL zZd{kT_e3uB^SGBZ+qa$jat&U;^8+e2E@u^=C&QfumX3PQ^>4IZ5i|nb`h3LUt2SUM z^zzR7q9X__T@clABq&Kb^jH z1$uJ**Sn`qn}cf#jS6~6rb_R$+cm9CmyFm*G*Bb@Jp{7@oE}OwT zABC#;Ps9Zwgo{?t$SP?OSj&D$%w^KHjex4}Ang)73wFh)50?QPUJYVXyJHp_St>uB zYC|-o8gjZtSa#>fh5Y;N3&>U2w1Lj0TV}FF@G!fL-}-r23|L-E7O)^H?CrsZy(`(a zU>(S#L?diAhcwSw2N1KG+R&_re+ahGc>_9EXtd$Zc(VwHn23(t{;2)pq$a{28NN|y z-hBM_H6g;e-RKMF{_8O3H|we9#9g@oOyaATE@`qJu4`Bx5d44p3b*@m*8Yw?a36q~ zYf$OwZb*Xi37Ecg@xMOf^9lXphZApAzpKOcBebYukVuSrdC3sIi2J86UKhspqItvJ zxGfZ4TqAGvQBrIJQiaJ*2Q5c`){cn9pVUrJi zogb@_W+eNvpElD6JD3_mhKFJL~Ov$V@5e&FR^NaEQrRC|A07$DDjXm zQR4lz7~z+Paro6%QeFX#6OA|iO^2?A=^3w(H!}ONF@(rUmfc)*RR=oXiLE2b(*O~R z+E2))xCqJuJ;4a^OKpU20(DO%&j0Bz#Kr@r06+>eRjV+JJY-x0K5Mgx9@n#(h*#2S z@}Po`6y458(ODbU^{L<~V0Q2;Uh?}x!qpE1Q#-T<_gehcv%`n0*ai-jg#Jyqd?tPH z+tp4=+H8m~jYH<`%id@^MZVaK#XoY2XV8QTuKxBX|Edu+tl!H`&oAyH9y6wYG(T*7 za5XOM{SsQSSPuBJO!aRvVmj*Jt51r$&n!oQo03s`t6Z=kCfuAsTMHq}ri1PYewwG^ z+sCa&3d}Y0s@q+rU_`NyV~h}y74+EHa2&W^sx@z(hnG;?l%3D+{e9yYCAbBYs))J< zu-S6#9_dPH8)J2=ozoB6YuUveJoC{CcX3vA6nDl#dg9aj3hHk)99aImH3|#`#)m`~ zeuSHwB=_3iyysR3<3K@r?+aqIUHVtMkZ;Cm!4ObjI!4pd`(2Tz z<$Dh7*xdBn{S^mvZJ0mEXlM+HRF}uVpYw#4%6~N*xJ%fFm=dBj-ic=%jers4W|1E= zPX+seVfH^ar4Hs7AXZJRd*)4r_+SX>A_B(#oc#JfWM7Sg&voDW_q=ikezxv~4TeAd z%1EoKs|OdpUOsddD+AwJ-uSlI3@@_hiV1hd*}(|1Adnt~hYIrSG2LrVu*hYaLHoV7Nwj{Aq!xpbPIn z4bOE&kElGvzCHiLHOKw=Jh8;`_AO@MWcHEQhyJZ$c?P!oFbUFciJzc~ zg>T9DSJ#Mww@sPypLulu;6-!u-MO@K@Xk$h(&0&;s~h_L8P~*v&vO%Zvu`XwD}~&c zC9$%9G3OQD9o$&o&C+XlFZkBf^2f0dqBnur%ztDbaa0FyQ++5h{Q^1sIn2?IjH`C_ z{9ms5e?HIsPHS#@0X}yRE`0RoRlvPnKab47+^ZSdN7`U~C}Oh~g|!rDsDAvh>&E2( zyFMP~oJF5Z1pL`UlLzZK@p|v&@jv65d+<5mm4>-tDdbgf`kwf=BH_%zjfsuiWWazn zFVSn~wx>0!1n+R~&zj~6IhaYhYuU4xK=>O`t|kWKs-N(3>(97i{(R22tvWb>u-lm@ zZ#W!~!VzA0`k&2c=;}v>Wf-5<>8CX$lyL;A(trA}{@)Aw|KC$Qo)BsN;s~1NMPB*M zaMNqKNgQJdWs!*Js$wcaU^q+iMW#PcRi$5Nm6b%cXs$;n9BeNUpTiR29jbRI<=##& z!ROqbq&WQpIs^P%1c{+L5E)%sqGDv%j3j_pSuy`SpC)q#>B9p~B&HQ0V5&l-S9QQa zTzrz@_ z?TbCV*!9Fa;O;aAx=6}R7IO^B7$z|6A#HvSI_&Q&bbKk{nSI2Cr6{G!%XsspBVkO$ zf0=}m^EH!O6cU<@LhLdIte5ZNt$-<8#tI+>3|yqOb#r=NmGM-raP zZL~Fr=gepoqI~y+^pvp$Nbn#0Kmu8Uw*xCEH&y712WN|S>0I*`vfxGb3u1*yGzJVS zrMkiSZJ7f2CvNaX%g2gPPMS5a@V+8_bGkfuyVr7m%Wx2^$tY>VV#jx8B^FyLb)S1uqp?Z)B)b3uyA;$r{6J(-Ab%jV(dwgoU`zl{JJh${vW z`z*p8UPnAzSmnoy{GHuLKsh=_8nB`433DMGQ!|$xISMsarh%sjn`wDZ2)ut(=-iSb z4c*uBeY8mrcKhGo2YBT4;OMVE{CsBvf{LS=J&Wh;5L;1MPyt@0CK5k63PMwfo@IoF zs`&88xgS8Ob%s#+1@lsnumwU!Tp%Na2Dwj&?$`Tm56cqL1UFgk{h|fGf(Q^c4hfzy zI*m92twT~b(!{EGmm7@>qz>_4F~(NbL*2mf_gv3Q=;d+MebT$A8BOpun22%|B(Y;) zR_Z{*j2=MRBO+i)#!nalrePgmG``c0T2Nz|cwLKtdQiHsA7C#yTUNI=+_}exyWc~5 zK9)M|U}OIBE}LXQ6HcT09kFUqO#MPj-c0yGyl~{+sukx(9TYkif!8ZRLgK=-$F>*{ z$|eak9R?e0t-|C_sIN>RDS8DsZJ1WjndZ^xR$*ReT3|B}i7)>ED-q-?wsnW3+yU^A95%xl>F;pAZ@5BdfzOf8rM8ypL%q@FVI&FT|jcMJ*g zl_Mb@pedmGY)*XXZVgQ{YF*^C(@cJl62J9JTc*)svWw`}uLFwgv%!LlJdyn#5GLT{0WBX$YeUCnxq0)3=I4MkM!u5UzV8C@i0 zIBnk5+)a|MU!=qp9K=!`Z!O;MxPjq~fB3@IT?p6OvNoIQ6xexGwUKlq;vnwfE5Go2 zB^Wuk-zYgSKq$sdiN|uj)XX6;KvZ}*8+aI0@J5?@JdYT;8pq*4St7E_l(_3;aRj*x z`u$J;g!$*-|Mm9KlRHR^%4Q4ya=IMv!-RxbYNsGxqY`=eMvr8dHN)9%GEB;QA zCku$BRax_Nb(%AvBwUU7b}>VfJ4djRbw#!=ZSu&IA?#1JGp?%A@`B+~KrDCI^5}|5it`v;y1hXX& zTo}nyJv&ZJwRPpa@7cML3Mb=*>jTppkT{Sw~_dW`IC=I2^(lTggygfEntEc!qJx zp1%Ck@3FHTxS?xLOuRjxrJuzrc3mIZrCZI+W;i+0(2aJHiFW;jqQ`B7@>a|My+{yg zym7KuC?bV#CFx2P7(}Sw+Rhig#j8&2!wTuS_$S9MDtrZpEG-c^CR_owGznX3Nl=Dq zi?)(u^iwR|-ic|)d1;~obHV6=CZC)K`>bNhyq1haes4K;3<9`mpSADTKqX!e0bM&< zM-CTgYM2TQwyyxEwdhW`)1%We*+;Y&Zd`S|Fofm1Uu{yir_{&G@QD@ zX`^jq8!g6gK9&=Ewfo_Zf1Rq7OlMVo1F-kKJ%)Ki4exwH*vbg|izv~Gl`smiiYO3J z#34>rRbY2DG z7MsTsysdfA??5jWMaYHt*-s^_P$G2{+%RNVxocgeqBiw(i4$&ZdV5hY zEOR4=r8M{%XyLok&b$`n2%|A8My`Gm`itVNlA2IK`819u2ryCavfKP3KTe_Pw31c_ zNQy=^1YUdrUgZ%9Fght^@FpQHmH=9}LzsEfm8i{WmQZOX7a`tE=fLQ2MicPZ_--O7 zWMorGgA2r7qQpJXfL$Q(W$nX*loth)y^Q%SJDDn;4E?zwU_W$`ocU~U8*rhmgpKOi z9X6&%ph_#4nwgp$LPDD^O~rBeRN#k=7S_Fq82LRf=_T7V1-#XZ#=0mYs+W%fPy^so zVn&kAyBL&3e&!=Y%Igqtn>N_7Dyu*v`U~g43h{+}aF`QVuDsf``ayTrAx?TGQLjr$ zY2dU7mdpU@QD1ed>0WX1QFbZ9<_3VRwEHG_LPO>tDl4nn7PK&OL0C=(>8>BSQc9Dn z?Y&Wzgui$zk7YaC2x->wYBXqnV<1X=Qa0>k@K*}_j17lD%>Z)cc$UKeU1G$JbsYg^ zo&)_0U(V725hO$6J*!JQeHF0qGn7nnj>J8Xfbn{?;@blXKdNqYu+;@l|%hViRlnmfhGo z%5nq37Nku~@XeQP{9q6=_u)}FX<_yJ^Ac~4M6%gSB(e9u1m>Kek`a34jZ=Ce^Eux& z1Fcc@<#rabFWVt1+fW+4*j|N0>~|F=KeQU&dUcb02^I1w+X^un&Gk87aGd^htKbzl zcCv{sXVo4$>;~{%TI%EkxKCD?YBLL(>_{63-e)fIq&v1QS5i3Xe=p>&{)e3SCzGec z#G-cF$F0XWPx1|d!AD>)MOR4R!^$0NdPQHhXXNB(=V#eo7Pn7os3AS(wC6Z{zGVoy z7)`&~X#d=OwQxe{%9imS~$2qqKWroB-!@}Nf?@OUYM!qqWiBP^=N+-dh%s>6)I$f^=q6^ z`KM-^r)pX6UV%&`Ih^P8yUkJ(+hR|0!M=}^pP?JLi6D&uGg=FQW%0bP?vP#JinYpO z1(2`2cSmA(VmL&>wmyv0=9zDc`DsgY7Ib=k56F-z9lJ`UdZImD zbqL&DE@5`N@5Q%SJ;wJft8?RH-nE8C4KH=9Yz|$TUv;qlMtMV)w&eLz?=m&}^_Fq3 z`M$gpd-&QzzMG8=be8J%p#_yw5j4)JI(RmH%rN!9+cdqts+8+C(Zhu=DRCM|AV@*g ze-FYqA);U+s*_8t=d8JxQ3MMprQk0FM)XBRWM50cn^}U8N`-{x(8-~*tm8__Vp9(# z?Y$1)bq@@ZRA(r<4W=f%9vkcQGp+>0N?|T}Kc~lz7W=YMywOk5!CM&>pWJzf6*gt#Ma+Ler;u z^-a`azDgC_>Cm(vMs!qM-cuc;IQh?z9bSz{<}a@$tPe>WCXC^jh|~_tzVNP>*FWKl zeil%U(zt1bOzaf>eZ{PftOPEjC_daP6kvSt9h16V#5GaTF}m5@NOE^BdrO$-f(q3W zg{@^{ExK`u#U{HqBseurEZK6q;bGJ}?C(_0YhRM>g@5Z5zd+=uT~ERYyZp^A9AZ3t ztLt7$p#J@wJ6~v(irBHDHz>+x8M{@Jsk(~n%)>dcJDG2~0@-F3(3av$xU0u!!xgfL zMH}WeX70ucSf1TTC%^jS%B^Ot0`dyHFeUCeQc2;raEE9cOMtRQ5llAWva-`G@Joji z4K>G!1U~1hEz|;rc<0b& zW|E#SjBd7q@0ApB%{v=+Gj#A1e$xb0rz9#%UHnyUqJ=P-`%A=p;7h5tIseOxR^sMj^o1K=kFNnU_Vt39!l)OSSu6G?;l&VE(tIU#XS>Kd9?HuvMCz zIGjZ`K`&OTFX$+$Al%g#8aLS(rPg#dOrfdn_$6`!^%P~fmA13t>ap5yo0$8f)#F>_ zsa?D$30bB|4v7$n(9iFIeo~Y?*gJmnAvh%Qg!Y2W569-z6oGCs(8d!aR;~XSG3T#>6 zbM~>XcCr??pohgfAN}?1(YaB#hHmH^PHRti@^}5k6N?r*@()VY!Qe^o@^8dh|I9>G z%Y{Cq$IjmS!S<%;hs$tF_Ou1mt?ABLg&i%Kb14b|a>qXwU38Y~en8^__bd6&_<$&doxAA>2X9cq_ZD%Zw|*?`{efDpC=U;!PbiKIy#PO@*EeG zzNvyL#eO!55>2q#<8nTpPcb+{OjWPMX6H~eL3@NI(hR(`nnJN$+$^@2P4I|nlBa9p zP0CThD?7c4c&p2ZE9dTgC9meds*tp6GgH*dS3X{GX9q_S^P(GAh$ZRJP$weOn%-~l z#m$q?D+;1wpFWA72}>%6uNYVpS6u_?!#QV>duARSX9k&Qi*AoWhe!`KcxhiNm! zUoDNT@l~W7p#@XskubduYK;GPP2s9mr6XYhlSf3pHY26M_coa?$V9_V$klUTHeUc#)$H~)V^ut&(8L#$m0torb%W7_>MKk&)(F>3t*jpPSFxZi&+Wz=9eiD z&8l8#TqZddA1DUqx#_fal@FJ@tgo67C5DM)2h zY*Y%fH##j}baVl4B{bpNHgvCj^8~GEYZpX8Hv8I7gc(@)!;S`{9@2Pee2`ps7W&FG zx*2q$3HO@Fp7ydd6y^uxG#<`IG8KMD2KK)3Hr8b+Oe3KNWa`hwYsm7+FP@7I+ zoNm;IeSeE*beGvnnHQ}t5x{_+7!(~X+_7fnj_{gjv!dKkE+g&wg$ikrvRuCgo8@XO zAF05x6CtrzLQ^U9{n>F8-?L~Rru04ixq>3FR4L$1-9i#mOO`h$<8|=(mwrpe?6~`k zK@?`q8YGEU1BG}6Yi$|R`qA5qqeB*Q^?|`@*SO*Qxd|JCu3Do#ZR_cR0MD!JB1y{k zQ|kKcdsaJt;H<`8lU0qh}sMpO2x$Y`bC(2sz??p2jux{~wmaJemBJ}G1di`>Nu+AeBl}p!A zNu_HP81g)7uuARnV0sdS3W-*}qOEeMn68QD>UGX(G>;(f7`@KDu~Hm8VdiRH%g>D^^qJ0A zELy%CEXm$g(WLKL+uB5XPU0xH7Jg!?tvasb#EO6o`PGoFGHafJFjH(wxWZ+?94SZ( zH?Cx44M>bEF#h~@-BUssggyoA#!hg%hLv#&k0M1&nUhh0kLx072e-UvPL?(4w^%B5T)-WqhKm?xz#)vuPlai4HP#lvOy7ytDnAF+!4;merViOZZ6^YX_ABO1Kd^Y9YVhgNTs@Fq?aw;m1tMJG^>e5zf?D( z|Fn~Iv$f7jm^+`qg}*E1>%`rx_E2sT#e39YN(mbgM-vFCPJg61UHl|IWLEIso_$FG ztLR?Ko)=kxZIS)f!*oKskh}F{uT9mLJH_Q+IEfEde0V+OrBhHd4Y5MFCU#xo9IDcb z{i2%CGai&NJN}4KZ4y<(&6ST^S8w={^Vku$xy#K3vi{^taQ76IrvH5E+6^Eln4Ws~ zr!oA2$*BtdCd$r|yW_;Yjk4+t+B;sO@4|zgMBEWIH7ee||I3XiF{dDS_Mo$fI~QK0 zb$KGvvV8NJUukJQ7xD0|iZQ{-6KSd)Z`A$H%#U6gjkoEyyPPXblr$E;&z(nls*G!c zNc?(xym9BC+9SO9Kt$1AWmb-C>#iKI@(j%4QE`2@9+8i0%o;|B>+*(ZJ!`EwtwU_D z*NoH31A=hvyQ4HTv%2sNHfaJ@cq1JIBubkIYc&ZkwsrpE30qNJ(d5dXsnL!g=h; zN$5Vt>ALh(oT)80&r^xL8Pu`HJeT?Vj+NT?kcK?v%MMe+doZP8JLVY%Smiml`Q5eN z9MU+CU9^_r@6=0($x$MV1U8;A`>m2kx`!Vs#U&<8WQo-Mz8`*^Vl}Rb`mm~e-($|r zo)3?dU;G69`8b-~_;N>m#6~JK6_%x3d5V%vGb87dkw=BtD>`gOmD=6QZSO>!c(SiQ zec%yMLC^772uDGlD16|v_E!}^P5$u7VaWWlu87Pu#)JeKQDqHLX%cXR>K-u?#^C9? z28a&p?&+RyHq3e5t0Z*R>~-_j}AUl4rcMH$%2KCC3K?rdbL&jg;0#-E{8N7y%^n0*xLZ zo;vTmn$0fkJf5x63MH;<$yB3ni<+_N92jh>6G*Tbf%#odW=BZL|LNnt%WGzPVOt|L zXx!v9{eiTtk&pE7k=8IB!ZN@aGEC`KzhCd+!d_Q2wmqn7Hxt38@IP*o-HJs*sP)J) zHPib%fqScg3sBLLf2^a z;HPfywPyrn(?6n?YPhk_RSkCkX7|8da>XbB%7h-UxN|F#{oGU-ryz|Tc$dXAzYV)5 ziVGB=IsDb^3>b4n&NOZ*j%pYMRO+fyW1xpb@As3x$3G ztx)N?>6dMROR_7RF$6B{l9h4R(M>)jfLT>RS6W~(xzrf^sR;6@ zDOiS03swI)6-*a2MWvc%T1`F>TD+bKr-QVT@GgLQqvp;{1RfKwPw-nRN0Fr{B5dSQ zpaFaV`?1mwdtqV_yajB1%<5`;TjNDu8>7HB5WGRou*`wT@JFnIP6*;3SozNmg8@!t zH2V_*#SjJd_XLB*R?JinDR0Ea7Ar1UC2azpD|nQ^^VI%vq%}r3n=y9khxgcD%%H<4 zSouL1|DoBG+10kXdxc+G+4}NX&NFQI0qRZDjmyoZx=4tFUWmP?uM(J6?e-iqu5}*g zPWAKJz3Sk}FdDWVR}<4x*h3oe;nty6p;Vhv zrR3S!maoLtAAJC{09XR=Ec(zrV>6!(*KS&`S6?WXpvJ4N2URO=_nFmEWPy;e#f6Gw zs#}OIM^;Fwe>E)Jgzg};2FI5GJ1zI21*OFcgzt_tyeX(ZdWx{G*$BMfr-cmYJqIH# z>GiZ*g>pnKfDggYZ?4|jdR>x;D3PIg0DG|b^ifg@t)}#_@iq9oa58GDM~tD$=-93~ zb<839oeR!q7ZqFSp&TP?kjrHF(4@j=Ie*pddhJ`NrspwfuKsD-2DizEqUOA-&|z2< z`+U~A?NCR1uma+58ctW`b@}GGzDp+%ZWLL0=t03ahXMp8UILsj?1qJ_s`xJgpFF{G zxTaxmwa`0~#1T3+U*%)Rq$i_sx=^z8nEB;NnR@Yw;H!D?_|B8FjXtmeNX6R+(`0ma3tjYGd5Dp zFg97xfAu^naaqvy&a%$N1u)|T*HYDC|yF?Re zg7KGwRroKG3dWqeHk6B`UV)qDfNZn7HP{d{=ETRWhEG(|96aC{o+s*CAaOBpHVCv_ z!eaSTDp50lm)yMlOz4WQ2o^zLI_WN7%ttr4wnzKu)Xm;gAQFJ7Wfs@63fzw#i1^Ov z-u?;n%a^9lCw-PX&Bu{exro8evQ99%e-PG_f+z5cY7jVxn2iGkIFLj>S-=OyW9I~U z>aR<$DoYlOyAPVj1V-pzl?P{F#uLh8^o!6`dSq;TZ%xAXB2y1SHJ1E74z4W8>d`JW z?hCU>kCA01q^K`F9d$;F@CUebIYQU^WEvkJG8xz{r{8#49P!Iv4w{L0K6WX_vtgeJ zj)UDme(q_YBP~1y$)Hr|7rmIduKT|HuHW)qri!m7I8R^EQ-J>JKNvoJMp6NWr$9T&2~;ENTnBj_iciP$yaVybYtb?rgq-iDV4IMeQ95#Vd&OwVGC|D@ z{-~vfEDeu0(KB)Mb+?$yg+9F*)v6YS`EYYVO?BtTz3)BdliP=XemItfP%MuGz9ri$$Wl)Bx{J75L){!XqGddtKh#2V& z)Zm}8!nY6!na!IM!V8AeGWJO4l2KTLzqFI#0jC67Y(lm48}qOOa24IP+K1ee@B$~%ffmUEG*|iTCs$Yiz=M1SJzmr z7^PN74oS6hLeb=9iO`{$wHc$Y|5V_JMFf!|(R8q=39Fv=PX4zqJ|`wH#&CW>I+W9h zyAc1CT*132Ce%`sk1wEoM&4M>As8pVu&u=)Q2suQI&+*;4yHENPX{t>GurNbA47|A z^UGq*f~(P(6a*eMWuc6y@#mb46C;^1z2??J2J?qZ1SIX0+3rgE-6;Gn^^3%$)Kx3Y z1!+#o?ApGAQLAg*2(|cu+y3Yy4HUCdH^Cz%-g|RP@!YWY}3?e)h-NHLQR+ zUu~};3yCC!%bNmp!;`RAH>yFgj~yqW9&$2GcLU7FZ#{L5V#H0Dw$UAs>ITqUh1h`w z^unda(Cu&6(4iB?sL;1$^7mJU7fSJ(_D~Ezos?>dW{zYqpdrf)wllHnU1>lU{8vtA zpn@0SHJ|vJ+EO)?8_vyPr4NJ3@namvQ3%6Gn1;Ko9j3s;&PRA1Sr5vG_6Wv$le;y4 z3DlsWVb?Iw);tdeel}}_Gl&)CQ#UW=CAz3hm}<@;+1em@0x1MvMCQ_bQ8W?VVFo|L zRzXtICfr-H0vB4iY*XTvtlns-cCBv1;E;yH8SDp&i2C=YeODz#JR=XM*dGf{r*D6D z334AaleW5yWlj_Qpe%Qq;g|;NHX0`viQS_3rXD5w$f&Llq5oFKFz__ zaFv>+qs8oHFCfJ^&%4(Bu|>xnL;JWHfHd>AxTXBXx-|gU)p|pzhvz&VS#>&|#=7Cy zIo?G3i({A9e6|40nBK{-D9c+6{C0bW~w3s7dx3(`X2t9+d-^Os^6%6YqJYp64=s)ya59+xeo?Vaq!`gAv*8d>;XD}TrjZG1I82k6#8%&cQ93)@A4(2_(#rq0k-elD`Y zG&)4nQ3)+(9aIyt{&&r1R(Rz$0r}vJ%WZ9t?Vc$ICQ;qx%1b$uOD2=Dq-x$@^i}V? zU8MUAb2=r<3IzTT$vnWe)dS;mtIp+C%Sq5Oz3>pT>ht1Y(u|khH^#jE_o|BF)t=of z|Kx9wZ6}&t#~mUX&(C3S!qFT1N=ToXaR4_#vgc#7<+|NyA12HKQ}3}==|GLN4qGP7 z0{3lMuf>+5q*1Z>p?fx-PuuCnf;6HJ@_U9QthB7QhHb8YvOW>Qhnq0oz%ToOSp1~V zq4&4GPv$d_4;+v5mrVchu{xpT|n2?2$i5^ejpKZ$F&=xZ38FPohxYJZ>;6d)cF(s}dfxcR^H9Rx&Ce&I2{ z6VI-#_hmZHGhTfWtK_pWUor|F|kgGCQI)E{a67`$9Q# z52Nn+LN4Q9Fj1&1$e!gJWthnCk3q7l68jR5@1q}f2EfYLTQ(ctoJ1%!bxlZQk(cIS z)1gwb4SUXq7Af=gk3_TZxkgPF+Yo5=9oSl4UfV&4w!$~D<~2wjIhTBgSRh}7Iqd;> z>3Kw>Lh2QQcy1ppe$Wfh3aMWGT#DIrgeqtI2MOlk-#l!}9uP99A{U6U71z~CW^TU% z_Q{Q2!?kNINZ9H}9V8bVG6R-zc8(j{hZa_iWCkQ$t^kk2Lr0!}_ek!ejr*EAMn738UVUUqPsS0BOim`xXE-Sh%h+&+| z{S3t7FodlbL^0gTgiJk{so0&R?7t}DS%EDm{f5PoojXm00{5DB4zXSRtXG^tu5QGx z_}9ZRw*gZ@0a4yD6su5d)iQzw$6fw({}K$VJC>T+8RyvUV0FEax{FN|*68$?xK=v7?j3(c25V%BI!-3~3;q#L*lC&E9xQt&b(tLL`7DP}gtu7Em)#~`)0cKGUpQ`)-39PCgq$P(XIAm@2RWpGOPPIuMxJw||7 z>|Y^6TXjETbrz8uMLatPtLz$|f4t(++)Jj}ZSjb4Q;^~<08vNCcjjGWMy!Pq6G-z6 zmignG_|^Bs4YG z;g$Yk>K0GhrcdS^^|LCdXU!O+acmMebekce)rU|W;@SH6-Lrs&=O0g20kph+XM1zh z!%F)X@JUk*{McLf`e+$QqZ(>0YJ~1Q_^@^L>2a@%T5TyOvJpvYX|-AyH zTwRV7q`ac4PXvBE5GU>emPTJ9>@^uNWGHJoH-r@~3Xc)K+;etj=}Go>S6jvm=^5v| zhe50+NiTQBm`q=&`0Tk#en)$m@*rVT&cx>~Rakrp!xko;O0*M8uENI;nMmiw<#Wn$oyZCm2*c51fos%ZtRXA{-3F zfqEWVBwecNvbK~IR<_6Qbb0P4HjmFDEC5nO9HXapaD~pKfRI;r-oghUMsVCC&v6sb zmcQrVZ=c|Az_Q7a?1zDJap*JE+&v^28)3FENczOoJfg6YWt#R&S@f{DIpdK+lHXMmpM*KJIfkC81hH0+qG2>ywJoZ1~xC&h$b*Ox4;I ztW<-iWgH0T*E8Sw71h(Q(n3|wf5aXjRp$-~toA-1O+T16vw&qs*bS{I>EeWnlyxj; z#Nn0=g4WQa35TCe$tvehhq(ul(wA0(*Rh3^;~cTAlJbo=s;;XI>u^0A+KKDc8>svh z|Hnw|Y!GrR@Tj9EIaZnEV1{FlWxdxDxZifs9`Q~bRpyOuFGj!>oit%f*-={B_jhD&Z0b(l*>6!{w(gqswH>?5AnInIWJ%ly`0P#j_Ao>{sM zEqvEr|1`XiXJ+ASPxa3iMmc?1Pj&w>*Z7PkGSJWIsgnp>G1lAYlye40jt?RAb%d5f zIC$V@D0f;~-V$%k^8;a7YvT+6E?UnzYtMk9c9*LHs3|^4T>pfo%*1n@EXs{N&B%hy z1QQ&&DOU=podzJ?0S7>7n$2h#x;o?^zQ?T$73J*jKHR4&5IAbRPsZPTLkD!`vHCQ1j{HOKbomAa|}2L?hP!W?oOUwUnd6l^z78*x#>t z`koXqsiDEi(`MWP!4#WEmR0V>(v1DId`;8}-`@2g5?MJ{e;VP`*T^Fg)9RG|6U2h* z)vc#IuQj zUK_Z*d(#QY5o2Ts=LN)CW?u^suKV$LOfW8)i~VwnQ3RSOTX6ZuJ|C5Sr^x6xYZD6uHmTPz%h0c@N*Y|>3k7-Y-3Q-xK~<@H3Fv4`r#i?pL_v3I`=s@xO^=- zi)?=P6eH$!MaQl{|JA28Sl={W^}e)(L1^4#1t_}QD;{O(?uAhnR8nA5QHC1rdz5ps zU~$Uy!9fZRR3!#8LpQ!2m{%`cW?LB%`N7l!OsM&JNR8xX2iTvBTjt@#&4s9xq>tts^udL~dRdCf$?ySEF8~MQcjL^+D-|PcPz{_8t zR)|L#rmPb?l84gwu_!mqPJ*Z3_`7;pS$J0zl>aWW8tL;UHbHIo4bdqFudzp|r=Lwu zW#C%-IV40)kI^fkbdGph)>hY_0C;YuD$=^&LvM&lGdg$XFsvj} zCZ-7FZy@?5s?*gyrom3$vCnZhZT)z0FeOB(`lMqAcJoG7m3J8QfyNQ8NvC{Nx}ep~zT&f; zj47b;K1`9Z`OOb8Z3`K%I$y@#=ri+Rc|CR0wyxfE%T-53^&{{nuF0&3?3EH+(3H(> zXh%ezEo{AYb`u*(r`vj8-aks{Zxo18DQ8^@Z<>-)AuU@Ofv}FslRQ93z2Xaa81mv3 z`3HzziK>55|2T?I5U0D3s4K3E4(!|8%J~QQiUzDOQJ>AoLe$#I@WQN>Hmd|B=GT@1 z2CJJUP*+2BIFJgo$8`3>qza3z*O|V(GnVtd{j=(eW$a(S-4P}wW!Cj2@AcIBZX-|T zDV?Zvp>%&^J{mWBJyFdS>;ySR@#XtS@7fArVU)15wF%*FuD*#}<$r&q*~}yP-p`fA z^F~PID5y%D-n#(>=>8H;lp?m6y}rw%B%6f4Q14e_1H8EM-Zjvo7?XVTkg6YK#Qq~# z^M|94P$hj00b^UY!K#0b`|HNp-VPVgu1p$8egAwfEFRLzv9KGG1N^O2t)4LxYgzN z$sJW=FLE|r;an6dK2+FsMUBDVVmv=fquU>Flsx0s7P7X%-ki;8BpDYY=qX}1>YN`x z1`*{t`6B?|Ys0Ga%5aUY-7hSbV6CndIUB}2u=CoA^l6YxL{9$&x;C6`==$fN>?&X@ zAoM_)^3lTa;=R6QblQ#X6YvqzRh3!0OdIVSF_h~{ z)C;kL=b|-}da5jzI$gQHS}&Hh4)iCQ1I6A|Xs>qk8qKs&JP+ev)?M(BEMj zOG2?Zm%0?NJr~KaCcrdC#P71a%-$C*?|#uTR??#gzNlnfp(hqF*SqVNP%KH`;8KzEO4)hCeJ1ofUB9+I`AA5{eF8%$q zA%b^*XZfFNgMxpD=B0)U@K*vhR(qA3q)UwFc)~QqgF=$)7czc4-#O@XKkPa$@UPPL75D-D77wLwQ5IWKnL4i;d1f(m9jUqjvD}>$z z=>(9_L^?^P_( zFRiv?$8PkF9XlgQ_J9^Oy>ct?veQmYPI5Q);v$8T|=8@&+-~vOPRyH(-!R--tH<)=P!Dj`yy{Q#= z1vxg(?v@gRGWit0#ym+wd08q?ZY&eqW8hBCrrL&zr1n-k;Z z-5k`!$kg@+iu-wvj?H!3hd_Qgt)CYZKE*) z4hNd_n~sLIwg{)+b~8g*ZLV%}FdMM&-&{m!xMFN*^<%3B%)}lj6G3aaxdnv2&5zJs z-V|njWAoPMcsAE(ZMfBWb0u~+3{4PD->%(4j(>kH1QYO`*8X&zJOD}m z1G@50?z@Sh-Hi<$C8#6e!(~HzdsABiID`G+0{9CI5MenshCir={qJo#H@J{+?Vq^k ze}50Z>C>-Tbr}K7ESTUXYd{|M2v=2|fRrS%Ou4o9G{z6fYnb-~|10K>z+Vo4q#<((m>BW9MxX ze;f_195x}r24F2SkAM|GBS|A$@Xnm@&c?vn*vj&oK(^ty(+!xt=@lEp4UBUFL;!T% zhQWq*Hs4IM8O)h*PX6|30IbH|bW@T3S!mn5obP)*-1_YFs~O)?%^TYd_;>31p9e$U zZ}Q%@IX`T`pJ+(AIL--fna^!z@!xJt2^RUXsVxjxEK^$mB?MdiZQVaX(cd+&e1ORl zPVw)qo{g0uoweuFhZ{aP%%*+n3lz#{n|3rYhVq~G=q+%gxrr@Ne z$t7hvAL5?>B%k=H3jUNm{J!3Q8*97T|5QF381wgS$*(5!7c=z_@W?+h_SPN# zM-99H*Kbb@pTIw5;J4=b>+#sMJA!}e@VtVXF2O%aqW-Js$>tijSwLeegfZYDTt?qs zzvn4{r2O|V75*&^+`{nxTBfpTWVh{;iG#H@;OgJ+&);H~wx(tVKlUcy;TsTI`e|H+ zXM;~}gi;6~`b*SBnjpcy%lm%Uu5CIt|2m!9wtHKYYrEI5)%J(uD*uQ*{K3ZX{?^9u z@^1p<7DM}&UF;w3$i20|EsWaI>aAOT+lha|tN#<3;ZH+NTLJ3rTK#JPQu^1Bqs*_Z z|7*gMn|ikWnEh0Uo526CAG1GvehB>Q_v73fQI&sZoMclV+c5DDFcE@P+X$No7+MoT zBXA=-!pje6`d192aDPWmw$A-FJlty9j)z-qTk!CokD~BwCT#xyJ?H;Q82Wb|$*nN- z??M6hX1MDAe=Gp;Btl5(AH@P=1c-8iFz64Y*x(nOAPl=rPybO|_*WF+-xz`Y-qYT~ zg#S9|;NGNH+qQ(D-BHDkFHf5DxY>4j?wdwMj+454M;g zYzbc?tU=-gW=ANs`o4z~4AiQCLK675asHMa0;xGd5^#HF&kANvNOKzl*Pf82ktCQ8 z7^t3+*!;p8W@199_K>tSwS(O>G}@^AAh`6KNn4O2=9gsVmj*3_+6~Z$kbflP(d|H0 zn5C%{!V1iuR~n9h6Gj1rWLDobecRxlmh9YCHPWUYZE@mlmW+GLC*RJ3xB6`X z&R>89Z+dF~8B_La;f>a2WamFrg!9uJ$Y!kQ_ZjXcsQifx_g}Ju-)F+N!pmD)`A?g{ ztsKbzXugJfYr5ZC@!$9Vzs+xJmj6)3W9y-gT7Z&`6r3CY8CSqW4w6IQwn@c-AE zao(+M--Hrg-c84Dn`#LCL^HND=&z$0TV!L~SylKy@g1 z;NVdME+a!*Ti6EDd~+jA4UK`T@IB@Lj{)@E3WeL-8p7>h_Jqe1{ttULwUGC_x{xhv zvrP$qVLIEb($-~vAuZel&28ADVnf($L&C!dCR=Xl_bResiGJ`^|Iai5M+lq}fh`-e z#d5dyeY<{a0oiu_*t+g7q#r-VDt?3kTbLo-d?PUMSD=hrhjB~zTY}y${GafGEjsbn zkv-p!XY(&E`|m?^-2c6@|0DG>e_q<)zq3wz%L)D6Ak0=l_kT+ehF4%yJvK4smnyL> z@c5_W1_TrW5mmr_w+gj4DaZG5pC4}dF9&{pz2(*_{^GUaKP_I_Ql)K#<}VJKKkV$! z*ur1UVEr`ax@oVrMJN8Enbluw7diNUDOc1YM6o%9e);@2o(tW$_UF&Cdfd2H~+3=UR8D7zdNPf-{@9%>@DB!?(2q5)5T)N!!&V&&hxn8m{`$#*LtT^6e(u1 zyCbr}s@BPad!5%~b#eNV43YGO?|=3yee`64lZxE5$RqK+_1zEoeN0Or-7bdjel9!j zqCor5N;>>7n)C&{`oj04koaEc+b55Mc#yR0Bq#g+$4{qw_xCrT+vUSbhtyf5zqrad*KLy3wT0dw@la&}EoJ_%OT+Q}@mD?& zYk$1%*9AFh;-LClL46b_g69rG&}9%ErMKMO_8)S?H1s=So$-F~4%3M2_&0ft9#8>jZmsDQ8Ar#P^6!V=3X0 zrs1zf3T*cVpA>3Zn`<+Ghh0;DmCtbwTKJ_2-seM|`Z|d6(z$nqUzbNGqAXKieO~+| z(d)ht8;vYK*4<{{Ic^pOdmh!Wz$%Yx4ZOta|6A!(1@1_&tAV2J<2}FT^3%{ek0eIq z2~S{33zt79rApm9GyzubWLGcrBHA)Fx7zV=%~+_4*hrrJ2>yAbSrpE%>&~I`m1{FK zoD-|F4R2hX$vWgtxjgH-IhvC6x__}h*y&B-d{q8?r)ARlq8JQ~AUr18Whl~8%mAMQ zH#z^E6d%7ND!|Z)d#p<9ed^3k8f{O1y05!%zO%*}(|32G!#v)h*JC9`xIch*OcOhK zNqF2u@@S(|w?kWQ?r>ZejS~(zKMCgBTgTfVa!8S>Z3%2)x2hb?AUtn`B~$5?+tj;M zr^z1mHnukf`1vk`_(BzHe0xKX=)C4a2`T4lsV#~>%b?h!HJmwIvm_Qw)a-!_#$&cl zkmbUe;aI1*6|gNhBNTLK`OAp0xG>(Av9@LD9<4`Oo?O7uK7sb@yPfKVhR?B=X+C$I z`)n%ybPUrUG(VhPamsG@-b41Y^}=uR@&0VO3lrU}X(jGay?V}lKJZp8bBFciwqjh5 z>*RO>9%8mKk^d%tu2rYoDzk3rknk86x(lAKMZ;OVnh`tOq{x{YD9~Bijy&K-hK~DS z9`8PsS}=1_aA_b^!#%0WR)_yA&FSAB%FFhRiM&w`L5t?oc8Wcw5qe5D z`bq+BU?ovxt{-pj#T72}Si-9ig>&J4_1#Xr-AFMb2t9gfC^{N5*h|s{w!WjUI^dJr zzymp5G`zdE+o@58I(sz6eZFJj)r+|0dziIxbZ_mr%d5n2TOb!q{$$b0Si4h?O9jz_ zhJm}OQA|lTS-Nlc*R>VldeQN$4>%yv37at4RgR)XJUm!*o~zFk+}D-K$(&vf z*Q7h^R}N29`nq+}>z?5BCCJF}~^T%`Sp2`I~zKFZ| zI=#!W%Q|H|=}l1{bca>JbU91%(%M*N1Kir1e@%j^)M|064_>r1lr-WPib>fQaR~BT z*g4H}`y3LrSbE&gp%tZ*9Oc^%7Sfd*t?799+{Yd0I)N@FGIY<3?}ev}&OJ}0?&T@4 z#x@&WE_BA8_4n-pJ5pP+JQ!gRWrIDKXE$8GPKNfqk-7r3QP}9t8BJaCN1R2AMp{Ue zjNs?+fUqy%3qgrXx!?$oyGc97V9L(Y42Kt#>}Pv)u8cuh;#_~Vasd|}a+kN3>yBTC zLz`Y(ki=MyWn2B+r^Zt0Oau1EdUy65sj=$CRZy`?) zK-t1ZIx=7Q6_IG4-JG+? zkGq`s#DbBxMYAo+S$yb`aqZxFaOYIc{ILvAEdD0iguOQQyhBD#9ZwB=>)6v^u}&g3 zc%^2Hd$D`Go8+1Gb@Mova(tvElmeYi@nQNqE8xR?`4TR~4R)It!qsMgb0{2y7^2E3 z7jU}|i@Xm}K^8TH2k7ur(CrZl_CoQL96~``tGf`z-6;T2=JSKazqTjm(z<_nur>zX zbE&TM-J|WCQ!E?r4;F{3tKmF_g3lQyCuI5ERPdKkVkb^IZIl;-89d+P9$D;HHmK_@ zq;8_?{mL~&fdwhp<7_O0L|Jn0%XRbYYb1Q7JslwmlF&${6PU&x#DJA-wc_)Y>=bGcV5~*fE-M=Z#n;H|`@=2nx z%x@{ee&xqFF|;ptawL;(!ovwWl*0#e#_}@3yauTH#f*K7W2Z4a_?!T;`D3@Me3j@M zsk|s3xJ={@Ya6ldH+p2;pLKwhe;T566w zPF?Y0h3UnQo?nMOc?vyvbqn~q-Q$*@O)HBXcRAHNP&P3Bovgbl}_!FsXhCC&<qV8EBB1b#W{JnAPm+9PS`zP z7vo$fxCKY}+jM!Ae1uYE@B$Tgo<3iT zxWzJAHF0-a*Kb2?9*_H;fMYJvGG@OQLebyXv0Xz;ew$DzqBauWcCk(>c%1b{KkG}xAjdP$n0;E@#`u%sAVaj35M$b zCN-Yrk$9-b2yg3=O^*6;x`NipGCBRO^=`ho<|~5CWEq^9G^!CCdMwq|x)19w;vlBa zviucp#)07cqhGgcZ8@>dPsRo z#nPB+jeuCN$c*nO0Q^zF!p3V9g#hESLUe-*yXV8)0qMl~k~1d94#(I9MSCodbYqbH zR=u4%h{6_V-lv3AL~qds*Y#YRi)fK?1w4(f>qEGw0bpuDYp%gULr0welg-yMk8Md` zU=O#fr=R5+UnX!hV6}&X#J@UIt~Te6Wz=+@Do#sCF^_dn??~~M>}GAe$pruK`1Sca zlms*+~3 z(4FUCSvk&v>>{hplULi7a^6Lyx-LF`;~>98$|uFKFX_3CXHVSPeaHroewV2U_r<<@ z@aC7QaIoO7a@{K8Ygq*P)V-fA`QzOK$0$}i8}~mQpGudXEL@1wv3W<#n#YmUrF0aK z&g!dOSG1R)XdFIV|0WDQRYEHGB`@z0!A;CMzEfIzvZru7`d)h}&&4rl$$c*$s-3lh zyHF#*igh!ED)ErBjHEo1Jyi??n8a{TnB-<{g>3m1Owpp2MfCXxsTHEOE#RnpDsAuQ|u{wD%l3`RVRq z+6lr|dn%oj4A#h^3By7u{eibqr~Bc!R=AegF&$-2G|Br)$4}aGh;+Gg1K8EXrZQe&C(QcjHqRygAexH3u1f;$+a1pqLF`qQTJ~KWzLYm?+ zO_jtBX$Nh%K78%2VQ`F%)YXW1C#tBt&u~R>}U@QKD`#6(_!XFBW(-(5^HnHqjV6y&h!+0kvrXSBx8X32-@ut5-B;*mywOLt?96s z1fGi-hoWy9fpHbC&3Db;URx+$x29DU2xHxI1DI?_@vn&fwwA{^-jrFRczB20AMF(R|>gE}&OuC_B( ztof%zZ>~z=)ZT~(6)&+M4;F=;40h%16Uhog1fX#glQnp&X4@JssQyLlC_bUq$cRw)LgPY@BDz1Nj%v*_UL>pp;5t zQA)Ds?LS?o75(}#%KJJK`5q1|iJ;g`jM}uFSVm7>L~pti9k-&doNfY{FGga?OZcc! zaIw~!nHpK3H6E8^bbIk*sLdVBvlexoI($Id9!BXU`IQp6xJN9#hs0KWyFh58mWVHu z>hpyG1~c+flI!Z#NN(e6v33emx5I?A&z8xiALY9Fb-QlP zeI6vZ%b^D;JYIea%si8#I}0a&kXg%vDtzJ8VM>j1A9P;@3Ku;UK#9g2o=qJ(+SBd! zMl+fDl3=PB&NJx+z%k6f%Qjs#>-G@`EIU$>3JcG79B1#7=M(Vnt%ByZt2p$qUy*qlxv|AP6~c&B zd7Lx@i;?1)f>7iK<{`**g&+(>BXXR?dx>?OYL0lbBVTUNP+XuERZvoJS-%?6wffA9 zM>=Uvie?YO5G?80*cvs>qeCa|np2TxxJAH5tJ&UI(TifI7|NGY$=Xmd1ZulBu=2^+ zpS*_sP=6xU2|{h&yBA_Z{+^(GubYc>$trDU;z)PwJMqBDBk%_8G(9$&jwmXH&&wbi zKM%Y&o(0YQNli>1dB8tZmd=nVc^(b7C^k+!aohQWvY;{(yeirE1Uv;VkSD2c%}xf_ zCb43F>vliAodN4!a;SIM77uB&$S}#E*@a@!ygl-Q8ckxBoMPZM?W3&w=uw)o)GG{> z`b3$@?J!%-w2PPeRzVc4C-of@wnA0`2U&;JcMTNYklx^NNqjw+XET?WhC-aKK@v$; zr_M3d5|hH9F=P+e?}tR5)Bc5f22wAkyJo|ObfA*j|tDp5LO6C|Itu_;bZIauLZ59Hi3 z?$3bDXul|eR95mx&>`X8cV=Ui`-qVFzPkI91~E$gMGyztP>k|$k$MW?zxC`l+0&-Q z%4c4Lc<$2286{e`Fd(~LGa96M2uiL}9Z*oNs}nBf>%8}p4o2?f1presJ(avz{;mYY z3DwFSzA_KIWm@1N`gKUm>c@=I^f4c~cf{0|wB!4=nm%e+uUL?yH5HKAWpjScR|Qb4 z3KSd|%*A(m1uzcWk&>VAJ!Qp;dew^EAy30~j7*aJh+nJYdO%QuvQr|Nsw5MNL}`{Q zAG3tI5>cSMPiWX*i&v1(Z9i@FM1EO9DO}~ePyX?VCXyjFBbgjoihTE>Nb{VvD_SP~qUEkBjm&Hw zffMF0M;|;jdj0Bo)D45~%MR3ur~JAS071vpCxc<2VdX@I zxrCcr_1&7LGi$CGPWyPvSL!AGRrj1LMv=a%lJCLsdYhq>XS!?n<1Xbf6`0kP?DPxz zIDDSZeXK2tZjQS&ZfC`kBi9leD?B|Tq`ex^a$%K2hvnmhQUaOL)5=rFpf)H}Al}2Y zSjq@<8dt&o@S`jwFc9y=ejx3l$}SG;y;sL$qvQMeTvP-gzy-Lrf~RPmK4tX40R-DJ zoc59ixiKnWD_p~McQVy7J|N>raVsUccm2tsg%7e3$&37X@p<~^fYa2c`T9B4h8a)S zIhb^Qa#a$2m|8=uUO&rOqs;M5nQ|oz1_udTwIu}1w)T}6K zFFYhSHEAircsrQ9 z>;+CoH(71bP?soVr`M-(UvAb=bus%WzHOHh7D`ID@XR zF0u2SaXG;poX6*=JW?n>+I;@lf(3v*2HWvm8}eLq#_G) zZ+e7g;0K!%2kSU}bS_^5>4j(Vk5feX+4Q1h!U;4h!>-~8Zgg6*Z>=IhFDuQVHCzWS zWcV&E9DdXCG9ybtc2?4{pg={qw>Cis}vrF+&zcK%Jp_gp}8RevX9~z3ggAP zgLH{$1pI3SM)@FPr8lba3L`TRl6Ih6S1?P?7i*n@I71V^Z>Mrks38E?tWLRFwoq09 z_2J`)^DPghEY4ZnQT<sxFwq*jd2}j#ytS8svbmM!fdV4gh{XRryYN%vy)V?VSj3u zua5Wlr^ct<%cE})sol<=hC7ENC7#WXe5jBT+>Zr05GgWK$q1zs zV4$L}OD9B9bU@spn~>A$`hukS%n(5Ot}aIv=`+vxh#b%Uh!EKon3VV^TLzuwNo`2j z2{Al+kAnjJ#P4;{RT2|!7nMZDW7ncKIAdXH`TRbsYj>P)+}KFGz8c765P$4+Xk z6p&v3F`=3$+>a$xk7qc;*=Jbc6{d{g&bcA?a!HB?ny%L_HRH??x<(KVB91mmoY7L> z$z%iU%^6Rk-A|4yd6Q=cKAIAMKb%1_GbbyG9pb{it~@p7d$}6q>kM7j@vrL~9ra@v zNC2To*S=}Vnh?62hLu%(g<&?Y1PM);WR&@Xva>lBXCs!Pd|sVHqE6W0oQ@-_T(6I{ zrw>^K_(TmqSlaKiKtiv@G#D^(hE|SBQ5@fkrr{2DzIy^?s*0>pziLHaxe(Ysz~_&( zK7RF@xy>#urJg%Z45j6U#yOdgPnlH?s}wYU%Y{A8)68bXXj&(_C8dpk)$dep-pxCe)OI(y98? z2K$RE?dz&JLw6)k_na-03#&SxG@{8WZ}zTQsKQ2%g*D6P@HyA^@bKX{*ZTQ}va^GF z$sp^J?RQ9u{W7O@Nr<4;!?Jy$Ww5dCRr66>!VaSDJm^ z59+9XmbeP?4FXCrrRZ2)dXeEl9)sD~fk>1#d`SRb)f|oqyvu$4V0ra%C?=UvJBb`^ z(lgQg>WWs|oEqB1Gmwx7;&zgMDvx_Eam`^5>-$etu2Y9hbPYEGD8~*PL`NX_#MSMd zPF(V3!)2wFbGZyYZ2;lcXEzLIYI{mGV;@QLa$iX96D#|Cj`jl~%EpqHENi?Zv^rIq z!l9np>oQEE(>Np|&E00?%B=oosnk0%%%0ZrE{S=_S#FJTJfu7J#gLIWTx;jt3f!R&WVbq%_k_b z`0?QjyLu5t!?D6nIc1zVF-Fr7GUG1?!*h2{dV!o+Y2b1Gv$0Ik{n^DAp$(6M>Oj7f zb#l&;_l*uSU`{<<8k1}9U(RNH9PW-yYx)>ObtKcRmMj)?Iow~z$eAphO8TWrjt{1+ zvubY?^b9^(#cEVK`4FutdqJ~Y6NlJa->#(8ylNf|{3*YrBWzt*^?sJzri*MH7r86a z`KjW?g9aIua@uZ4yyxk=2ZcDZ8zn{>t$EWnUg&PFP?!6%TTz=IZlE;OkIN-1#2Rs* zDX|DVefzMMGK`ec-(uy%OIOu4g@_&aFxN0*=KbM`^W$Azv-g;5~7)_+|N? z3PFn7Z+V5S+PUnsOblO*%_`cK2Q^c;_2t#vS1~8(SLo$b7$LIyJg+Ub^B1-%c9RcQJMg-A>W+iHEUO znPhr_m_&FHn!SWUS8K=POGVkW0;kL|_5%=4hfg0YmaA?9?Nh!D2_fBBfkk1P7ZNJ2DbD|nH2(9#M zmD4_CW#cqYP;x$;9`yHo&amWT7en+S%)R`IsnO1wQ(`7a67ql+h=x)Di~J3#qdWBX zI@8*4RIGTb&0PXG7vYv=@rE9VqfjnTl+n@yv&hgsC4kqdaq=mnn=rk|H%;5i1W|K2 zm5WZw#xcmdMD!m~wa6XR8aN>%lH^2Fe%^oX(Io9DDH8epsAP5z3IXpyfzG&>Xt@!$ zM@8v3%K1;T+dgm$^5KnhDxofDIS2pJahQ5kAiYFn1*AQ~TaKX{puN_e?=Btj#oq_1 zF(&1fu;aJMLKoC~&e9VVH}6?k*G2e$mM*#HU1g%)#l*#-74U`{tz@x8C&yaq_DqYr zT$>V#VdhnxhHGm&Yaz{e0x-VqW1rKJr1{H=r@C3CzcnI$&Z%_z6Cwr^sO^I1O-v}2JnaY>Iha8Gq$-Ci5f9OD*Ihe}+AVh19OW9as4uQy=BWe; zpL+h5VZif}@D%O>ZrL3^8adTk!nt&#MPCX4ifiz3SJ}ozBKn&@}^VGYEEFudm zle|5RQhf}%?>zh{jaPhhn*QN&t+V4a)ackf5CzCZUk9w0R}EXJX!1$SnveeNPoIS&^#U@*`kE>Wy~_L!rv>-T z;vs@iEM31>7%GKYpx4b#c(0RU`Cb%v$FjhbJ@soSmqdLSuJCrj44dZOlpy2-M?v)oA_dy5Hrnk($PG}Xuh zkUEjqI){9Gs9AavkwYyMZ!Nvr5t4_ikL2t&dUr1;?2;{c9kzpq93EDZsVJEs>)D&& z#>QN18HPMF5A#_IbEBKl@Q9PQd@f6Vx zFEUGRbrvI{+*D4@0Yq|F(NP~s*!m(z|FovBFC{)IF;tK}EkJnJG19BL z?<9SQkD@J?E8<^f*QgwYv9_L0e#zA5%NB+!dW{^GEC31I$sv6jXv_QysM)f+01*fW zm5WZUJ}Neub_b!Q8ZdL$aKg;%c=Oe*qp{XWL2mDs^-!1=uuoKIrPcI@{AwQ=;5Fkk zay<0Xnz`eAHWhPKR|?)*mObjasZS3Kmd*PEWhfnQ>yg$ob??RJB#5Nxgm#L1!oDn| z#9*KitEtU)%r3##?vR)$Ucd|Je9gNVMg)R}ooAn5@ZsUSJLwLEgiU1WJtRjD&IgsQ z@d8blHlaXY6T>jy>wB#m^&pY^0u#FQotv1xx_o~1f~V`y%kW`F z*5HCD`dv_|$0-X2{B*18j?ViR-3|TrBC^#k;mqnDr%Xc^JmA;`Xmz66On&mx$Z{1) zmcu%8QsEMEk7m=mt!!_pr=Ok-CJo2p7Rrc64?Mr-pmW zj!9{s5mJ?pQ-F9jS;=)egxdQZsMVT?r*4xoD)%c)adHiNKW2k&aD_C#cdxf}w=P^y z2!EB6lL1m)6Z||mnP_&clK%31`xTz~XJ+|H1jd}x#Ly*tN@d`XZ@yUXFr|t}W5(mq z<^a*xUpfivx}7qPW@7k3HF_n9W~(W)*pgV3&5xq&eyOy0X_G`F7TcVn&A%ka%*8)q}g3@hT)jYZ}5K z4hy8FGqM&lv~d=dJr+QQRjAV-b!ws=6y0XJJI5F`bsfJ2B$rcC5$Z61YP83Lk>t#JTMSReHOShZiK&U4jJiO7T2Sj`HaA#N)NG zmXqC=Qq)|@%B|96T+fieE?&jmJ(0L3+??L5I;oaAYRvN6k^j9~b}EPa9*E_L}T z%{pD$P?Tj9n_81s^by)F!_vD7IF?(Ha)OuguGB0V8I~TO0;L~Jbd51Do-@Pu9yt1u z3_dpQT6RR^Tr!3bC^Qu@v-O`F0tL@#u3appC#4SXhF|jtdancMT5aNeVt-J|(9DvL zVb4fxx~8FLN~w`7gyEz>D|KU>oJL#gSZW$enBq+I0Z)(5m7E`KYw#9Kt;V!Iu)pUA zsxCPv+moC@=08&ashp~Owd?q{Y+jxwy3VCyO8qfBoTk+u?Q0Zy^T7}UE{wxtt_r=^L87eLr*9F!tc&K(}*o_In?y?1%6 zjMJ>bWQU%`7^$NfeKMI zfY=}Djrxip!jHZw79;mq|5{4%Ig=Tk9TrQCE=nLc>e42OCA{RyUiZqvu~!p)Tw#M@ z8jeiOY}xINslT|Z$HUu!Z_(v+OmF~XZmOlnK~=Tc0$cH9N9M42DiYKfMeRsU6fICp zn0_Mo#o5^v;n-<pk`281NTadXS@7pW_;; z*z@~*e5J5WcRG3Rk&G6jGb76dg@UH)Xjn^|b@|c!i<9CLCph67*w)qf zRIY1n9&EYhDm$qNGVv;ICMNyD0QFbw$Ve@LI@?&|DlQ&k_T z!z1#=J!yG5b?L2{8*TG}CxMh_yFHP9+EQqGl{p365R5PCB{cg|j^DBC3I0CMjv2v` zJ4@6kyHT7qeJKtil4$;_MTkthna~g{2+<5Z_5hTjVEgFX$)0M``*rn8`J;&;s_a6l zNK~aHRxM51Xyk1+MWa8RscoHfbXf`rC1xLep!AN%TeE*TCit8;aVVUKuJtPJ7`ZD( z*GPfo68EyFG%+5MbY|D>ytF8_Pm~Z{X(Bv?%|>XHb^Hk+o}ZkA7U~G4#G3R}FJEN6 zJz(gc&q{20gdv)E4-xk;xrW~bx_a`OqZ7?vmR`hr@SqXP+&A9S=`h_AP7n4y68~v` zjUmOt==(_@^;d5ID|jfSqthWnc`kukqoDR)h8g+tvURuTlQ$aP%S><- z*F|D0ITo?jdyeg-}r#HYK>Pw9}6`Lg|&hJ^rR|HtSiq-}G$Bp*~p#7Tf`Hw34%X z4ov!^K$q2(R@)kB5AZNY_UQF66R)5_Uj=ctRV(70_mZSq6el_1-tb208 z)BQvIva{$O3qg>IqMFwf6$xHY?=u6<$n?r^2z!IC?+wEzw}^MMTWO}+h{|f--**a0 z>gg@G>zwvQN|*E-mDm|jc9fQ!?ZR1S_6jz-Mm>%Iq7R#}t8qs;m4IVpX_TRR2JL=K zT|K_STg9%Lgo6uo#HvaGLc?kWNtp&W`pikxCY%gzu=e(9ca|nlfpB%O z*XeTN^Pepu=hm897l-I|Y{MHwNRMNB+M)%SN2lvT^!VH0qhFJr2bl@W;&VD=`B>Lp zIb?LVis@yyYWULwSBR88$ye#M6g9$u!SQ~3qPC*;xKj0-R*#3L3>pYJ&q6c%%t}bS zZaRet?k><{Q3egCM?`+XLlnuq0>>39tCb`^(FfGzGBaLU^FG$5XOx~c#F0_S04YTs zm0@a{(8fX&a`o~ZCqOZxASoNMC0na5euNB-3~vDOzptG2?!1?>?4U+B+cb>>_xZW` zO37Zn|LERm|K~b~G^TzE4KnRyyfh0%{Hy8Q~iv{6ZkVp#X&heo`Wot z*4CyPoyW1XB^g;Z!Pv3^Jift;v*C~zw|2E&{0hnB4iUDrqZ*+#w*`=gNad`p!ys*^ zirRdgjH+sKHO-(NoyU7^Cx;W{(3RQ}D|m=6{w?T~I8j=lIqA#kxGl#L@=mCrg!4&xA1tGc4+! z(WiRD%@d=aLp-!_Zi(ZhOMMk3KG(de#5f$Vt|2?Kn!dWj0$sGNKAbe55GNllw^REZ zeo_+05I~M#3YMJ0s|DN$v{Pic!aM!SSvDEOf6cOG+SA8L@W&S9ljUO?u*U@(EZEUl z90M0}-Bg&$n<4zHt{Zp`X81L5N{o;b(V#%Ejed!8BEG=jdcD6$I6T=HBhv3KDcpA_ zniXVUwcpiMh%kIgH=@?UJ)eu=vn#~37(0M6C#}rDXz~ZKgwlJE(Cfn2 z#aJ4#yO&JPBfH|CHP)Mvqv?I1N=Hd`b&ny8PHTnqoKaCS0J5j?# z+~;Fc8s+c6cQ&qC3y!Q}g@yDEhikT~fnd=krE2-0J373^4JBk9URsaO*0BVv@IdH9 zPM#ivqO!yHQB@_Ki9H483`g8t z_TDFXflDBPT+>ZWzo=o`D~)S1V^2!-DLaQ{o6e@{QBISvD(#(I36vvQ;7pChuruPx zOTM05erF^hgglWQ)Mm%Y_cav$x!wC6M2tvv^ z=igNjjQF&oHOCAdT#Sx+VsToEZbRtKs{IsWp{@6)?kccK3WAXD6YPp%$KajIuNH#? zR9I%gLrdioy>cZ7)D@9W7$!JVfzU6yzYI4}7tBP^(t1d5q|y41Bse2SdCM}q0)3Xl zC&`ZMeK(hzEA6zcdAcmt4!U!K5`u4v$$lt2dSU96R9etc?vneN=j7^l^l%p#XgxF$ zE0yGo6LL;Ud#u5cPP0UnpfGo~?vAFt@uU&t!)Z;_^z#RpgYLsgMeRb3i#I0)xR2c4 zPXtbRQp(}9kW@w3dXe`Ork+>Cuwr*gMT!4dZs2*WU}b` z>TLK<&5fsE{gocaP3k~<>4>ayT@KG45YcnPb0udKTyZJRxL?V@KSxX(q@`;l z3~Bqe2C`1}v&$|=?-CVdhn$cpLZMn5wcsdzeIJ8=90>>1h10uI1S0EpfNvJ2c$L@LOc?%(q z;MIh2)_x(a9?PYomYIVk3_9?hJNL(1Q=*w)F&?AqyG)`#nKIPvv+wp)mtPrk4x_mQ zpb1fXm*(%EysvqE=D__OD)^l6eC-MrdT=VeohRl!^}mR`(0rQ3Sp8~+JnD%+*n_wD@l}9*`p=rNo&9Jd}rtJXRYTQ3cv%Wc1U=VbP*JXQCY(|^%3+hj#c>u40@o4y7DBvuAj zGwRIvhvsMOKwTmC=xzC4`j_3OJF6`IT{WC&&0 zgrdk$XfUM6xP^>`Z5}cfAw!195Fv`nJZ>|kEtw^=O=X@X^Zc$`=RD8(JN?leiM{Xd{ayFE*ZQpW`7F*rtq6*18QJ#Vho73`4Y+Ab#KRP1QVj5TkynQ$s#H-3!{O?6f{laUPAj|F-q*(7kUT z@>e;_#lKoWk?YmvpqRJM8_$f~`=ua;PaX}gWm>it+Az5GHsw|i3qE81)w;HZSY&kW ziI5AexbnpgoZ9oTWyK|bV-5?ij4{k*_mPpgQAex1DUtVPB)SR(dvihWQ zWy-i1r`IKx0jcJaDNv(xjbw%?Mh903{JVh}ewIzq;KUNuGJm=*@eqq7F;hX8f<7#p z=g&u<@2_xKZ+|;a$h!GDc2tbIu#uW8Rr*@`3Ggh%9^2hn(gGXxg2i3^qDtIvIl~M1ow57TKQFUUK`@ z`#d(+x;+Y}=Icmb6sP?~-!zJE`@&)}C*-@+^91W9_{$DD!N;Xeah{Wm?H6Z2N&;_mPJ`GBu_ zBe+68$QZg@zOOy5j?uilR5jg__^4g!j*h#eGU<%+J!b>x)4P$;h(-H z&2a_>esNy2Wbab4Txb}b>*r85F23`TMj}PJt&&_5WG2Bs*1l93#a|OMp!uOFejJD_ zf$b?$7WXcSsH{CqV|EzqxwhS<8s*K$B;zJEKxfWZtKDVFc+my`$(ZT`HZH(y&}5-n z-&|i7HOExNdSf;=fcb*QJpD}bkn_L~X(E$5C$}PT(R$lFvehgy=JH~c>r)@j+)kAN zygQrD%VRe6dDTh5%9n>`D_f(n@fZsA+f@&;`+fP)A&dRbmiC{mtE@0gky6~1Zyg%YI-VQ;pa{El@_i;$@%1jFpb%guI#O_2468MZzJ)O z*ALqS8q);|)UdziR($r{$nZn8#ILunf^!oFfp$$hd78#$Nn1AGpqNJ^$+A{60NN_8 z)O9Fu1KYsIPiSv1wlk%f^0OnPSP9$U^7l;&8AL^EhebI|b$!WMJ~7H>R_+&6!U+uD zFH3f^f=rZo{UuMjaGjZPi-I#QnlZ7-`8`JXo0}NA!uwcC_U{Qt>3uXGY6J5;mAY^$ zOWvRYP_mB(=l!QS{3&&5Zq{RFNpm^#iU`7=4X%G~J>{{8Kc6-IRb6>8_-#|YIFuvG zLT+5_WpK%5E~)S`$SCcG7Sqe?*`aJgl>0Tiy1XT=x}Bfv=*wN4;P zO6Z(w#doq}P40a#K4O0JJQQK+fE6tic=z6u1gHgW9<`~8xug2gAJqjNps2R+OF;rbsam+xLc$Ixh^~nCE$QQOpv!5~nzcT+alzxO|E2O>~|dhHU$1exD~M zy`Lix3m%)Er#YYQ1H@HuU!Ddd0QAPN{H6jqM9c(gO z56Ev35F(UJDcvEcl%@eRD>h&e4DI;l=nQ&29vyAGAaS=TS@0hYY%8wH2ncw+mm+ysaRb=4wQoDr?=B$C>L`0uZ7S(H+Ifb;0 zAhQI#i@Z6hn$;JGVCZoKiq`%e7%_n2f%pGLELB*{}>z}|uMM-kdoHe}iWc>XqQi0C*oFQJ;7o%AT17W*z zFo&uR@}4;A{3hBUpybm9^2!o^P1`VrLd~n2q{lw&1zyFm4`hPMm$>Nn$?vzxF;ewq z+`kvKXOP#8NfrXXqdzbrYLGGxSonGVCIdFliR+4uPYw@0q=Ob*dY0_tCv0ntm@ph~ z#h_G#;im0S4Lis7l6&LDH&p?zNX0zOcufv&*vArs9OJo<*vy;?TPs!rsg0j9SRFRr zGf4?s-_^c92<8=bPkcbz^T9os(UrJ3G;(y{I=>?`k%dE(SK}x9Thmymwkh(83~%5o zv@Thf$Y2vQ^r}=A&Xrt&ot_(2Yg{yjeqDQ_( zQ21wk&TQE9J%!OY=PMzlu)Iky*qAAu)n?~samCI*(rckPW;|ZEKVE&(>h26sY~C)N zom5v28emwfxaHnW6mC zlD*P+Nys%i@BSDiJ4-a-=1{(ODVADBZ7b-6$pE8$a-cT4ZzeH@1YbmDA$_dOocf>CUh9MG!gq&DD|&e`J8<274}Mk^eum`iwUp(Wml2ix+!;>mZ; zT3TqE-c{xEyz4qO?c!u-RuX`H;nV(pbDWWd{bAmE<(+NL?4k6Ym$gb-%^qYd;~K%{#_$-TYMW1~H!ue!x)0sfOVrtL_B>T|vkIGj81{3&wd*?yDl+*$U(8F6@?h)HFlT2=FX6brgUPS188VpWET+Vg$?Yc(`D~<+^b$z?7FAi4 z)u;|>*DT>=A@thzQD8E|e#>%B(n^=CjQQWR{nnE$saT0*Wp$0aB9Q(ziX@#MSN^2`-A(5RsElupw!)6S?&oc*vvs^8H%APAiKGLi)u@@mbw@Z-Mm z7LPLpXigo~J+rf%1CKLP6$16tqU~>7BtxN8`x@h#NW;!L7VdjzDsNn(!EHeqA@;!& z;qf~=UoPYs_CxOXms^jemgKLf`CQxQ|Cc_l*iFOBYh+4*uIrjk^CjECBt){Flp&7yQ&dH`R$?_Ao9&(5~+@@w4|-C0ihr;MJ- z0KJ0O_BvE#`7y~UXj_i|Uu+AVw{)742^QXo)yMz&I|=c?=;gHOt$USV@J%MLmo_#v^*`vo z#5@FdLUX|2cq!m8H8*d@qO-@kO{(2!8@o+)b3gj=m*>Ee1BKOn%-#`jiM|R!bigC? zB!Y)*Y`pQ74ZWKih~MVJm}_uGa{zy2@U{BCsGUjYp$IJUX@iy&hozket64DGBwFu> ze1&_Co;s_F6y0pELPOKzN392x!isRdH*c|^g}j`DZ)R-P(V#`&XaZPKFW-shc^*5! zN*mL^Xx80D7@3mfdo2g$0TJF`C7Ct_5mLL!+p+`r#ZNjCJD*AkPl0+M&`!0)z+bR-MAg{>0&c?30a5b) z_Q#WKH|XvidWWu`J!w2UJDqZpwI28NaE{oHw%9wkgOv{A%r*FIdV2=5`jN<#bUA$J zBvTQVHtD_mYL$E7J;5m`YTQ7FuvG#1=41_9c=)8 z&pkxwnOFbZv~|v@EIL4N~8JL;7BUTiv1Q8 zdU*hR$w(0fc!0+{fseOb&7h|^cB9f`lL&$9ND7E}1jI3&w|%`1Z45*pnGOVlEXgvG z;{a_qp>xp%ow~g^kQ74=0q-=OvUJOdkYLX^_S`!u3bJJ-kO%Z)prB^l{H{$`uUXf1 zTFpU^zhsQvX?t_IG}fx3c*4&9C0V`@tV8|yCha;Dtk`wbFn{{x^IX=D%9k7z>3Z)y z1p+wZDKTa|%fPcJMQR`@hwX#@3_9$CmW%s}^*mOq<3Q741|HlA)Bs|!2L7C0%SUWV z0qQFP4qyR~&3e{@&3XxFwaNs}iX}+?al2kmUXmy7Wx1qwie#IW)g~>LoepgCoxWP~ zjs+}`8Wm%L!-ui|V4i7J0>gX)Mfu2;tfK37`@FXgW7| zV0{}XYO~}?iv${F`$zi@I~MJ+%KvhCrcG7ZA^f`7yFi*k9iY)H*?fF_*a_Z6?)p%; zG7-9}j2FLbAqAQ{?&UzmP)t`n1T8^G`lA>m&MsJ;m4 z)nT9;aKy_E^g4FvbVr&1m4HaIB77LX65`|1!U}tGkfhHn3WPrjs5BlRSHp^|=QLAZ z>K-S90hz}Kjt;mi&q&-9-dY(vELu&g8hztqJAG6y$oVy6fGWkMNR=t!x@7+wjfwn* znq1NgQCkPd_DvIkMW*y(3*POBiW}fU=Lu(Kssb_iXz^vDU0;xB{}?nZ3gvALTq2gc zI*CGifaq66;bM^O>d7nxDt%1=mM{OX)J8VrY5j-($`eU({g_qwS6c~}1#4*jD)+zJ z)0%l(d>I;-x>&yT7r9tB>)KS)^+7-07?LocSvVPIAGSUcYsI8Ki&XvQm^Pq`l3 zG=XUt$Z<{#dTu%XAKzkseYwJdt6}3ejlQ#&CJ(5KnQ@Q01nXx==s%I2J96g^r4uj-%J1a4EpM- zwjzrT-VaYrP2mD}+V(N__9(bh!z|t%U-c9mCU)qH9ar0Do+s@zi3}KyfC-R0a-GVl zX~DcL_h`tkQ)Pfk}S zAH666YwdR4cl^G7dXYPzRG11U;HYt3=SH1ld8z=PYb5Mka=P8Vz>kAp7YsphS)v99vQg= z#KS5_A3F7H4y__pDKK4n;mK-DZFctYC^&eXSSyLZ(nYUz@SAx{KfIneJio6)!Cyz_ z;@{g~X?nXzAFhV|BKmUoG6}ewm*uC?Rf?{N~be>kcSw(^db=FsR1WiRgZa=(^C%wo}u^I;17M|pv+ z>=mV{LwXmaDfvzG}j^sp(Jxq*Amib`2ga=Fc7=-H#4 zh6azRK4o_!nl-o^gPhMCl}WP%<}6U_!*hlaa9ysq3eF0k>x8*i(G|cUZjakK=sKfN zkdExFI@Bk7uo4llCk5Ht7>JHGrN+VE!!b`^_^Oe z?{4Y;l{2`rvvw<=FwRa=uVtC@%Yb>2zk*C&=LuDE5nuR^B9OteSpI0EvyZ_*RT+bf5RU{L>Yrg{j9;dr_m1AP|ckPHr zCVa?VD}s3#zt7MvM{VF`Ydg13EnJvfgHdntx;sW59hN*j=bs$TS69JmSxfKMcJoRx z^bfWcSbg(IN_hZRS)^Nl)R!s|DI{uF{c)|U7r)l9NR`B4W#Cb@NZDwyk5|aFYfd3+hK{?C5Waus~n-cP?D)aA`4K+KC)q9w*6#Yd+)tD0C%Cj9AupNbsOv zqzr&laVjkJ%D?FfOB=vf_*12lAp5$j1%JVaFN{0tME2URraJyD#zEHS54hz-@Kqo9 z5FSmncPLgnBe7e{GS21aK25;}j@%dr=4qTs@Hz z6%By=Ak{Ox6Q*|&3hGRFWej#wc7L}ggO(8$F!pD=EArX)cZYSWMrAl5;oOo`6W4^B zuP!4uUy?dsdtWhf7%}G1b)ptWBjJbh1V1U@1Qhf(x6JF5p6bYl?L^R=ma5)qe+AI2 zZ%8Q%k<97jx5`RJDUUK&a65g+%+r7H!_lv)$oK0-{h`y@9}gK?Z;XONLshj;qM~0CiFj08)c3u-}9)| z^CU|EY1)JIQ)DSz6FSZD5`Cx=g(SpwgyB$Y87`-qeg-so^BwP8>qlqv=Z}wlpt3q- zS-I)Bex7~|uJNyN03u|%F3*^#0W}9DLHET!o~|L%zhsXK*)R=oXr4qlS(+j8!O%3{I-3z{wFNo%7F;yZU)TU zVenIs9a7}%1!*~@hbokx$nFs)Q3rgJ*d$*K{bitC#y?zz@X7%x2lyKjv)nxh{T$bno4#Lv+sL5%; zNldg~pW}3omWA1iB4erBvyo3)URYJEBB4N{Ja2|k=2yZRib}X5>wzKpbdVTERf=xK zIE4$dP(ZixqT4-**@>B4L0ot$hIYUjZsdC!4{GsvRQI*qk_KstLfaLd>s zojN2GAr6s4*nvPu1tL*qCIlj>!SY0r$imE>#H#TUcxi(W4rtpngYZB7gr?vd=sPEm zlV`UCtu?jIcd93s+cor_YbG;6t!PK@x>LulQFb8G&>H*67JHp|c~bgCCcekaq&+C0 z7ecy5X6yI6=}j3y$R#@-WVY$COqegx*q(C$hIZLJL*csjY5Kc5?{<`KIZ~@I5SJ_C z2Xow9HO39TF`Jc>y;cTGa{q*J-s9gJ$>ivujl3K+r0PmfPJgASt0rD%d?riWg2Gv( z%__X3{1q*VxqJ}ne$fm5Pl;Fol4>UYaz6(onb0bz83Kpt%}+<_5d&>7=(jQ~i4it( znH-impQ&Zm!S;&)?`jBwUcG}5Ql+=|+bTSn*)LFbnoXTS)U+o=L71ONn45vD#!*S< z4Jx(O82qRvX#gEq*~|dQ&d_lvkx_vij-^mnAXYc~E|OOb|e z6NawW9CuCUjAn0&_(pZ242AFTKN==D>xD^ZZ4@@9;e#y}*$muQAq&co(9~LA7#}=z z;iq(7Ll|>UAXe5H2~xKokwPOtp>S;$i*k-!ja=3fLq@)LnD7jqGCKHwn0p1 zR{e5u#l5z3RAeaKXH`^NM2K7oBg&+uNhjDK(UoZXWq1ATz!E4oM!)@_DNsxQqzHq7 z>Oqq6p(URKpHQWo981x=|HrWl82{|u(?Uo5P8b;}-lLb3K!R*eaS~-IBa?kym~8%l-HyO!Iz7~JAcU>%XkO5+^BaK_S<8)+MBlgeen6L z)H%Th#hh5d8tSOPt|yU5Ne_kpmI2%;(PN{2dg-}eju<@H)uARId#FkY|NRbM*uh60{FjgXGt@vqgA9xzD7?%AzoGgeRelFt8b|oaf=v3M9W25O z{gX^@51j%LzqFFJyc46K7|^A5{KD@iL30WIj*t9~XLtVV2d5+rd*VN3+qgA0^?f_} zB?^PyCE}(7;Bs-@Xm>>)`8EpPSox5B{BP)vkTK)=cYFk1-(P+_YfUAeswG~s|38>H@2I;@=z;E1S z=f4IJfK7D#-$g~|<92EoD0FjW{re8Uvi|xb2)pZG{x8r_OgtcDN>YT0muRW4(LH-M zKjU@|0cRdk=^}uBcM)JOBu-4Tv&U#?L+Iak05;*zKb}XBJ)QlRm;a|d893ZPPp@(U zjPqDeM<&381vPr*4Dh?2E^szF6t0i{#-m{ej{nOJ{M+vPNdr27?K}4Om+$%i1RNWG ztjW9sLAJpQO7X=P@J1Hlrsp(fcPv;Jo_aCttMl(|h8@`VFFWvWyZ`^q9~t@o#Qdcx zNbl>t<2Z2|rlc6r$u=Fr6}PW&vBZP|`h7MP*s;>211OQ0`J2JO?<`cv?>&2_@(dw^ zptWhp(>OaA>fv>Rk(}{)d#2Qii^;-$(_v>E<&m>y&rm)=gvPvqc#pLmIpR`;w=_a}hK6jMy%?Ci38Avm7>J*DS|Th7A*v9j zrZ5)*`L0w=U8~vwNO}%HBJ)j~Y$>Bb8%+kCEd9MvPdwI41NGLPIP8H}U`;v{$rPMy z7C_ljbE8nEgA{N_Q72GQMDTIzCMbPdLSvePc~&_d>pUEx)X<><@aoye(jK4++Xo=P zR7$h~MIi)bMF^>KnC>ZvOtrT-L=Hw^apTf__^?R#eQ!zeg%w}RgbC#GJWnC7bNC&6 z;b{QW^W{mP$36#*JiIR-RPtW{X5C9{Nt96<1HF_Ky3C6ydLLe{b7e8-1f2Xz_&4qvgA3Fh@ z)BD}~ir^nO7bho3ejv+v0_tj@3I89h_wSZ-OzAtqeNpoSf2bs-~QY z3Gq23ewRhtZ>bi~4c3&R^Z?H@a9`-cpk+|n+2^quAo&zSV>s-i)Zdf1td>g!Z3j@y zJ-WHjOkcThbdmxV@dFMOF%L~fJ%89Jf?z#9 zs&>#19_-LGJlL!K7Sypbq1J7g=tFM{n3{9ypurBBJ~?D30$4!huM_a+dXm00 z4FLq;x3`HfZ|EVDDP{aWFvv5PARCs<-Zbehbp*O>!&yez6#%voaz2fOEegL}X5_?| z1E_7~2qbG8$fYu%2|;g0Ysls$xJwb9|7QP-h6Mo05HFIW*CH^(PGgz}ATV@- zPEXq%2{izPcjo;dl5*sz3T9V%Rw}33R?$uY_3wiS*eQb{;6PHCWV_uDIEcT#wyRJ_ zi2VV)ynDJSp$_2$GZ>j+`Tf0T@O`5QJRHk*kyr9py#ITbJHAad2Gs7!ws+y;V=o^O zULV+v=j?%$9y9n0;{6em1b0W9RB7Zr3$NVufQI`2;5~ODk2vtN?@n0u>5+Dyxv%E-bK5SDpO5q%-{Jf&dy zMN-JWE;vC`O$p%J?}dcYHo}o>o(X8#Fe+9OIiA>jNTMRK_stp#z**(d1uMftx*({y z$Na0B80bPPc-enEC|U#_X=iNB&R7zFw=7C3Ou7>hNR`Wz$N3Zl+QwX27uIzLL>9<6 z(*zb)xXgo^cIuaPC~SNTVI#}OTiGH?$(m+6qXa;;UYj^oxwR+&eCX1YalPZ>x$tqt z&rRdImwMHnNAE1t?|*9o1l+mERBrf0nZ83dq-ctg6O2JBL9&|oIe_Wzk*4h?^}A^@ z`FbCVTMH^B9V{}i;YX)5hoVtA@;}|ENob8yf3A_fLPcnWTGbWE%q4KOB>&vx*4}p= z#Ml*D9pH%2W{kri0kDJnGV>tr`yaM&CqkJ(8^~<)gMY{BL9W01;87uhl3+EhkNE@P zGb>QM#1nMj;Kvc)pZFscmQ^ag(}rNQ@Kp-C(Q2B`3-_`22}o$+L9d0&_5C48y0$nu z`Uz=czPgpmnGH(&;saLDPg!rl81^d7{8zli=}YtGYW3594N@?A^t1|H?>=0q@+iiR z1|U#f^skr8#*4TkM7U>%P^sq1VK~~f!vcCyw;&Ex zC&9~PEwevi*Ann&qPiRRLi)u;5eRQ%5Iq9rHd;iBCQRuZR{IHj?tD+eSo#k5O zHlq&!D(RzE;QMhu@@mx*Jdn6|M*wf;7@9-i<&&cWNGE7W3bqIFCw^Pj(zXQdfilu& zP;e2QOwa2PF@}qvP;hxb#M0usr2W_pCj!-#A|!*qIMH4oXIsZT^YqH`=laFNqcHUC z6$(|RO)Ku#Pb0uBo}a80seB+PmY>IR8v?dF7`8=RJGP)u)T(waOn_n|3)+Q`r<-&=rFd_mVpvhuzmEAq@a=G}H*>yfJ z0jf-n3d40i>K|x^7$Coy!ZxnZpjQ*z2z9hOhM!2PK{R>-ViPG*{r9`_(oYyYKFnfi zSUs@kI4v;fhouP92(TSE(o8`*1{N|a2?b*E$X*5hVgg=8=DRJ1fcsYAKFw5SS>teG%nI@6b?H?zzhsSouR;22d1u=JVkR$8Uq3u zC!lz0S}8+0SL!?(*Ga$_`t6Fu+V(oxV7|SgQ`h`J%1(caBJeQuP@kUU9-z-*6cy1_ zOmLatUWSv(s*d4qi8e-Q63YfSg+CYi@NBc3) zf2&6B6dnxT1JVp~9F}7+ALe9Q?0P@~Tk5;VduVi0b2h#PYs+Ic%-(6#?0_xD;Cazh zm;G@sKIFA} zEDryi`D4WorblE_FczmFmjxp^(y{%7-~mD@@5Y@Y|C%TFIU4YsPk*$eM|IX@YhA&w zc?2Do=ozSd##!uQa*xG7mXkk>BkG-~ommgAA!%!Dox(G4_>h8Sk6_US7K?4K#h|Kh zMNtD{@{C8a(0I8Z7ODQ7$rC>~M}%T@t`6X*#Zw|@sRknUu<8VG7r73u1(~0V#Ol-x ze%h;mIltE}SES$FPd%4qkf-4ii)9UAARe9v%0T>|HAlID`1u%@@JZr(ja%c{&V0FG zBoz|OCb9w_8WO|^0SD(VLr*H;KNu2b8Li5GTn;*B?4XV{`=No06T6PG z-&Q!)vGm>aSsE-K=__}?$h*c&Q=DU1gXcIv{WUb(r*;VRFtnD{kF)(?hWa{v7^%BdjY=kB zj#JUK*(00odJ5~-e{kJosG?-}c0Eq47@@o4Ex5$@M8i z_I9g1-jvf{UOgti9rkc_08{vw$<}Z1Jm)7TG)f`Eld{&Mft>ey{0ARqPy~#8t;)f2 zVmCe3Z4snX25iqq0X3WZlB{2W9OYn&4(^?gUV+2JIdmOf4TSfsZGMN<6q4AO2~^L=VSbvqN2-%J|B#qYc{9j z|Df2c$ZE$F$Ik^ZDui94QVrZgzocpHZxu9S2WOd3n|s7_R|@6Fpi{vn3L~R_y53xd zT0EAe4wE8m;pa2QHSfMMyq;ocVCg@boc+)*YWGYNpSDfHdcxOW)Igo|yOVda9vWt_ z4kpup9ju*K*~yA1p$M9`&PIpQe6IrQhyert#X->csgvuiy&+%5L~?kNJp9U5Hj4m_ znfEF6!ycCh=(z$pyyt4Mbq?&7;tU4dIiH5*h)Xklz0_az>C6hMM7WFQ*>BWJPKBRw zK4gQorB=E@g1Us&GAH$Encp5tq8OF)0vBIFK(9+pVe9=W z<_|}r+)G-!zAuIJ3K(LQKa>t^7cO&s*H)g~-gZ|@xpb6A%vVK4#rNvf=huUc&RyY< z5#p$Ms+cHq&i8=BCAr~eZpp7`IF0ytR6o-S9j>`yB!8YNY41IcSacAVXB9>*G9yxqd zx_Kj_M8?zT*<%$alZZ$8Wh%O241M8GE)wP2c*vTjy7MxO>mw6$Qc@)P!k;y&4bAaz z+tPXBlV+X6sf$Wt-|5RxP(K@?UUSw{YtHCe?GkOAnipJZS!%3_9{+=c@-zQ#0?rYa zFxJq0fTlXO zbLf@V%9x+NTymK`e|?mIQ&nQDrr}<7Bl5T=@>Fr%dbWmv_TP#r{zA?eZAqpcZ>bf_$k`EoZsY@zuODk z)ZMB4f?~Gq^0$BsJRf+ec$H&jT5Ijn-t+(3du=LHi5MBtB=^DH;3v^c_xzodtEuvK zVbw`jbG85A$+>SCSba$F!mRrgme293*>S-x(RamP_(@rBxIsz3+Vtxw6N1SRpQA~P z-oc*_F`a8UCTclV_Bud5CM#*|4RyMFXW}ji>Ou;>xX{7wX0zY~N& z?3?YYN4_zBgfx}XQxvJ}&x097o0j@rt=7M7Z;i5#@l-$lC1++KKhCE74T7B#bsMa@ z7{lUCp=R9p0w!_fR)nK{jplrq(;Ae?@g)wE>d7u^ZG=OC@DiVb;~_6iF#YTmqgG-XN%D_XI#y7N}MiLr*zvz0&kC;#19)O z!%o_DfaZg?eNbJj3u?=J^}*$+#+lyNfwqxb5V_4{;*0~z+>e=R$t^#Q7< z;#O z>h56rb3Z^`{nA;zJK{S_iDihkC)rI#dk_ryOSDj?fA-N&#vz(l?4T{7h$&$oll4P| zavH-kdc&gaIW@WR;ap0Jx>*0-{A9agh^G7c41=U5lNaWBWbKDg8sc0-7vwrX@W&4Z zi}$>}`HJUBSq7^Sv$$hEOfl<^JKl1SWYc}j+jHu5CC#Qzd>!g0p>3E#Jskt`#nBE# zDW~Sr*F_O;bgxW1$X_Jkv-jV0W-)Vv&^L?Qsqj8e*NY4J01{zTiFNz|Dw&^~#h$CE zUkfakncH7JbN4XXRV}d0^gvm%?4w*>1$U1{)ck;lcM=#0&`dv3dk=H(`e%!O_1HL=lY#bfXNQZH1KmiubN9rmHk`#TDJd;LS z+|~Cyqq#lpH$9RIS~u?{7pT0pVty2LpC4nnxaSkJ=1wI&n`|m=#rp8)NX^PTnT7FZ z`5hV9-t0RciSI&yv0S{eJNJ=Ap8dGWa6E~o^}4#BZN*2|E`Q%{ zGU=BWz&<)I|7jnzK@%Qnx~;Ki%|~=UarJJE`d= zf1Ow-e)8hGx9xzB1!1uG1~b4MOunAa5jOmEKpfiFZr>0lHzYslY!iWh3mA3>9*6zP zJfnmC)!Gju^_pC?(hjUV?L~ zkJDZOD6bIHpv-WzJAyLv?am{tW(rQg(WHFK2Zm*QZLXMyN?$mgI#bbI^UGtp49(@C zQ$8nuLiCPBsRtZT%!WS284`&s`|WejvfIx9K5a>i0aqCQ=(;+bY&;`Yb}Bb z%=OP+CJ|($7}kcj{&6WvM%&3+!Nt$CY?;-r;)k{Z#%;B=3W`1Mv<3#{pO9|-Ix zw8y;EopEpzYIL$lJ3IH8cl=!Dh3my9qMjZWUHQqufrfp%(-sfCx2*|_&?`cXp<{)% zrvx;!-;=Ab*rc7vrB=)BhM?UDMpRXBH-3#4G^{&tD%o#njYQ^>MiTGN*)wp1_RO&L zOBbD{u=#g?c*%|v9tFEcRoJBoP$95wdDy#b(p=oP0xuv;ft#h=xRiCi=g}@$?D5?M zjQI&F5({5{eIP1T>)#EM!~@D$pPUnrzDG}Cj| zl8NG2@nJIbn;X|Gwv`Rc3%HOA{z}NdU+0jyyJuoE_x-Mc>cHFC-2OVun5>zoqk?E} zLziA-P{>0^1$=!;YPR<#(PSOBM0pNJ&ZO1OU-S==etd}8qUGg9embQNdFp0fC~yR} zGxu(9EmzJ&=Ol6R7O6Vx{k*yhO$LnkB#={LR2#DEqV6Q+;j3YH-fJ+*SFo2kar`sw^)RB z1;_ry^azf4jq^6^Fz7eacOC)Q+^Q+h{5AFXo-tVPn8b2?5EydXf4;&X+lg(}M`vID zp50_dk9G}+FgiA^^K~DX55BN==#6s1%nTWi&t6VRk~*K%6VKM1b{>cMV^p@Rk=?h&n*lBfUeNVLLn7R67(?2N$O|Le4x9V$3l44 z!OEedU;iKlH8t2oCI#(F#N53+7r!Hn8)?kOyDw>>b~oOek3u-)q@6YBWaU3pfa@%L zUE*JHTep8xkygC2t7i0y)N0R9g?K>;c+)>1RQs6oniCGi%P>;ZAPBK^{S@%lKj*(q z<;awkbc9JU#m~+R)`Tb6Q)zN?a917A;PmUU*yJH{~R5R4x)?RTZQ;a_S z0SyyXwa#Lb@zva1TXc`%4JauqTYoP2U%nu?Xr$@LF90Cp2+kdiM2-8N#37@9hm@UC+q`Hv^~$#iQB4DQBb#%sU+o5Hqv`X4hMMCY)4GtbVlZ}`SF=x$95aL*X z>+Ohv^?%|X!8O1l5`*LPrUvI*AuDDpnw*EEBfJ!H>jpqhl1!dlywWC(Y-eQ{s4KbgeyMo{(wKfbUTdw zoq{%b0qvWGcfYVh+ORnVeU=4isJ&TxD&0dj$Jp<~ea%b%XsskPp)F_>oT6?WLgMK8 zb?(+jV(bho^BP6+Qj}tWwJ|f{9q*-*0f)$v6ymT~n-C4-j|!V;!f@U{LQ=CEF(tn*5d0s9&b{Az@YtP6YBR>FDCzybH7ld_J ztYxwB>l3%#HBo?ArE@dXAw<*Z3HJ~3pTA~m&ivz<8zQxTu?#tf?W%nQR%2DqkdNZ! zS%AP9=2ih(va5N@UAyyqxPN>;TFM)3!yx$DJ5(D!4U8J=clM55cOIX*oGHkyf#9$^ zKvu%vhN;LmpQLr|`v7mz=O5qVYrluKLtB%2+qqBy_=@;slOR~d>^$@o4}E__4)ids z_jTZy8>)%uQ~<3gA6QY`S%#loOyPWFJp9Mq4=BwPkGwDWUrKzr*(nxLd@trB~{n5P*2(OZ8eyP91AL-@F? zCZXVA3YDuIn)E<=j4jC$5ZJPZ>!Z+NH#uw8mJME450v$2bC}^n5_j|F9op3 z;tQp?p8OotDDe4I@&hs0@9xw8Xk&ZHuYTu=nS-8|7bl~_BuUV3@clQxajLvUYI{{V zN_z}OD>t%wE+{_-Z_oITSM=v2yPa_k8=6iyM6_{grk};F(eD!%ZQ_{eqI<1C?M|_j1((KGm7Tu^S4u@zC6`A5st~ zZv#&&2K@sG!yW5j*%nZ>$+-;$0Dc#tJ&>-x?;*?9%M7Bde2ijdX9#%e@}M-%`rhRB z9!*f??l;X9!h^8`?+!Ih>Mu-F4BO|T`=J>=-9_+UFn9|B!-_Ec6Yc|T>W~O zG$3SGbI3p*x3JePQVN8NoLiFX6OMI2`w_wZ-o^A9L?Z2+@8@6r7tc+tUw*~xi0>Xx zTFURMPf1P)%EF_aB@NIf@{F&9aVn&POMfSLHRr&y#u81w+6z1~4;OWh%De3)p*$71 ztNC!p9@3rLFqP9%YITUl90J60NL2AqIQ3?1-2T=Ix4kK{U$}$ROQ#uh?|%UL(K!fS z;!t%=&2kim$J;Lg^gBYxQE|5b#)6vV$uzpI^gOdwi>BDcc%@Qdb24w%_wy%6<$55* zWc|5Kc1wW>(fD=&mr@J|mn8_8nnsxn!hMs8k9!s{2x`Q6_r9i}Yd)12Ou&U4mmnVD zu#70Qh3=;eTXC(7_a<>_^`>_H>B|;9aauC0q`Tu;*iNM{ph)xe;625D(!&991(zyTD>$?298HFI~X5yt@t8@f6}sdH%7lXJP#7 z?RBZmg>ZzKE`>bUc%|bipj+0}bYWi6bu96*MTo>wb|8PrXECQ43uAw*KM|=cBYp3+ z+t9|Od1aVVKhNZzk^inq355=mOiD6^hoe_CejV%QakXe(}-XI!B?qATL<8CFTPuTNs=p8k9;4gA4&ND zF)G04grGX-_3x1jL=WJcq--IfkWs3RGVWm-cm#+4;t`}z+aL;V4^T#DFBJT0K-2?A z_jH*`RbR6O$k!p&`s^sPz;={v;x&NP1~6$6yt*bk~nI9E3v&xRHl0a zK02%F$*=V**xdGPplhn(<7ckAwd!F-PEa7_WREt-0 z))XinqIS~JtO*@vYwy!^ZW;1#e>#%thb13|AQuq@M%LOlwfY{b^+r$?mb~E=M8g6Q zz1W0TBQqbC-7a?(m7g`{iKrMqJT{n?09=AFvrT*ub1aYct}dT^wfip zcl#Q^DH2HUd(h_CJU>Rt8tO#iABc6d!Aao zQY_`;2P?%|`EoMwv^mB0FF6ac*54b``FCRqiLK6|4E#D{Ww|-~%<39XEHzTvV7n`+ zB{dF^8f?4r?fulSo1|JE%qmAa(it|*hc%SJhF8EMu?{?gzG{jhcb@C;Xhi3^AWrnV zv7gkcqtg^vmnv%}ngU$_0Qd#3p5u3hHAQ*#7Lmtl3bm4|klw7K_CrtUTOgWIt%QD~ zXk1VXd>K5WMQ=hkfRA^`Ie08T9*O{zW9LV6b@OCjrdQS=ocfU1*vt0QOb{gV@6lGP z{Hc1Z005dsaBm4-chugO1NE-; z10q`MUtM%a>(*Rf2panCS4RR4MYl{*rgd>1ZnzO!^UhSYwASzJ9kWdaD!uK51NrFZ5}(P;2A%k?`;eToj=xDhtpC`_raz^ z_|;oyRsmEje*5#)l@L7Y(H!njFA+`_Uhn<3WrESp&wi)+rU#8@MXbEVh?5)88={SS zB43d5-Q8@xMdok;8b%0t?Ff5)0@P0VLkfFJ;I4`r)gaM2{{byv9l9)k_20L=b! z;T0>jb%Q3V)$8B!mVG_`y%24jw@JNaByW>K>_Y+peQxuO{Oo0c=&3EebvN~EqRc*5c+s~@ z&bnW|^47HJ#duPk6KFyP$$4oFhGw_BJ=?}gb&lTBJKq@Lq|-lwAp5t=wD1IOBssIA zU58MS;m^VwwP6sl!W8>Ef>UNxZ?6&~Ep9lebLv7F%NoTr^@|zX8jw>z*&Jlq&PG%v zD05z~cebb(k4%5*P968FtRwSwFZe`5)Dn%@`;MR49(T1SE5x`?^WMyVmp`XKG$T^?!biY8Gck~#A{S7?wSbLkk;X+WjOJVqHe6`3h1nNp&*j2Q~GO`ToF zRPXid&iSv^de^(&Pw$6!t<$GietSQ^=eeJIxbEw|Z@b>+*LxQ1VI?vv2$?*ya z^MFs-mFl3oC((ll)?i25gm;~(ZJTv?d!!PX-O_#4gEcF%!fL>@&JV83Sb$k_X_R@? z4L=w|vvpZ1%MnHoHK$b3hZ#9O;GjQ%f~VDtfwnVT`Dj;j-Z^L8*l!>70=F%!Zr%DJ zv6R*_q&VNd+{s*9J$8e3Y#*R=X%ff}njW6vs$U+|KK*H4{{}C?Uk?#{%&qB)X0VK|Zr$McJq%P=ti$dMSwA#6h`e+f?LGd?8&e_QG zXNFcjqi4Fmt<~+OKe#hKYe@7u1y}#TmnAdoM9ZJT3g70dMyaj*awNDkFJjlMTO+5j zdN)b$CJ_fCC_Yl={pP^gOC*Eo6ijg9@VDRRhZU*ehC(?I!|on`+I_R0eiP$CqINM6 zHK9ztlu)j8Qug$epPXmjiw9%|o#Oh3Q-~V*7V=^pSt%WQ#5BbwZO0@do429czpORs2|0R-ji9t3 zUr;EcJNk08wMV<0;3k7Cu{sIRT`umY+&ZJr2o?lAPB%7(Ac4;*f1ns@E2dU{ceSZ7 zd(M~XtSWb32Qu@!3-g4Mc5b>Ow(}MKH~dZ9hCBaMLxhM_j(Y&ZIk1DWGe%_v z9)nsjfhBN@c&04M*ZKx{S6ZlFGs`vaPun~aT%bZCu`IzL`X0rc*K3M|fE#nm0@AuQ zMGIFIP<^#OOJ+~{J27z&d*TX&Lor8j1hsz`q{K;IQD$M=i^z=QY`7YEjv&^-yl2Oo z8jR9}n{_qAsT$P9D=AKUyK9g0)kd+i+Uj1vp_RZ@zX%~l$KCDs&atpF_6ePKr&eAy zvvyYvICkp>Z_iPqFzudHqdkr0m%_{?c4*|Dj|nlCy#CxhKe^RWR&8m}YJ6pI_2SEb zKgCwDwOs15W3@^069l`N^TZwM|@Xnkx{~aa(kr zXJIeU0BD*`^B(pAgIp@hn17;ME!3NK_f%YPkMalpwIkv-Q+9=1!6C4xvANae5UN55 zmD=&@LiPP&+#3gEd>-HO@*BLL=!0@+SR83oIh%cqgRAM^mZ7u8sk89IahE`lWT%j| z=tScMoAf<9+5kiOmX`F5(oxBh&0PAzcXJ%F7pvZ+xP)!#b~rfL(Lb6DI)5 zf3)wM-t8}#3to+Eab%pM~U7H8ca|G9Ql zwYbbO2FE*tD$D1D_p=U=lA46=N5cd>>VKEPKwTphFJE1-jK>2qTJIgzn{EaVWehLj zGkdrWgy&xbysmw@?=eKLBCJL-c5IYTCJBnmjP~HP=c!&=<@5H6Y$rHl_bzaXxGq=t ztS@`p`U5b!FM36PmL8_<6XIhVRc~-hH-Y|705l<9B%hF)(%&~+ceN9(TvuLZoYuNQ zKX&5hcrTZ@nNk-gk-B%T30^_M_MJaPedE(*Hy_Rg3Q*eI9YTfx`ZaKxEle?*1vsKJ zJ$a)1gD9zt&o!Z%w&hxEcj_rGl9!tYvAC=^5S2P@;9Jr}Q}e(5bv4k-*1Tsj5c!u; z(8fInupw`PtyU?puM~b55Xog84e`n)w`%M!Vb5e-g2*&sN!JNq#G$S(K*BxEyNj>mw@=jn8 z>45}lDy2s9k{}!Bii3(;9GjluM#rr`Q>v5Q@v@SSl&Q-cdc-b2)o!1M*lc0GQv56r z`|2c9jT>oa3`G=}5!|O+Iay(0!cU#3RSSGIOi0t6E>u-H11^Gjub}NNVbn-WoILy_ zPbD{OXLE{TAZ=zamF8~WlP78M`4c>;m%e9B-gP|VwEL=97csl;jM6st_Nx*7T2%NN zGMvP5nwCCsfmjDJN4xl7HJj6`EQ7t9t{n+j$f}mP7lpWw5VJchbtk>T${*@T%uS?E%eO+=Rv;J+QR({`dC?j6yWmLz!St?Ig$A~%NFzLz z>G3iGKOwq8b|;ty)B{xkdou58nZH*}VGH77ml!FN)a$0Ne@yUb@V>(Y!0sgIZd@AI zNY!BVeT3{TjCT!EEGD|$S~Dn)`!F8n+Q5L<+JVUh0}pn$^1tTQB;B}91~Rjm-|4?* zSQZ^Ov0%~_e83vd&B^?uMy@e8Me$S9c4|>3x9Xe%(e1G;1JOJny8M%>@O9BlNNq$i zks;yc{mY%)K3P)zR2JkEX{Zuz$D^%YeH`1oMt-L6PnX6>ao16J!mhR*+1qVJ8DW)K z^43<#Rw2fm_ge=@uqT5|;!B%u;1Q%8S~V_VEj(L|!yqVPyjSW8<|%-T=YhCY(n-xB)Ix&d)C+=6MIN>bm`q zjZ`rU3y-m;%3kAyV^2k7|MM24l@57}=h`(cX-|V{f7O0CJZL<74A{JXjBqdx)#2)y z=1=SXD2&}oRusxyk+Rl_3$rUrSC$-JSkytVzl6hn(z(&J4G-G6><}EOJP0h}b9Tw7u!#!>Cfp%`-Z_ zK~CJNMZmnMVIod~i*0Y7*?wN@mzt+4w|-Sje{83a{>p918ua$*n_UsKVl@G+rOC&| zdjNUZA}~y5xY!uKY85W2e740gsG_(})`ZjgW_JRkZ`J?4CekCV7#$zlO`-8y+Y1Il})O_T!OOug?kGDE=s3;)kM#OqnCpp4^Y6Q$v>q|IiXF!Vgc95fS!?n# z`FQ%hpxu-6j33OQ37;0Wi}0ElK5xUD^|I;2IR<8DCHu&HcS&+-T6XfSp0v323xb$Z z_MY?ZTGbQ6i#S+0ZUwUpw7oRfbkQpJlt;k>udzgi{EsIh@r2OdPZ)DSnV*ka4S43- zNaR_jFtnIvGfeAoZ}1$VjwR5@oY zPcK3)Al|!U5`)Uy=o+FEeBpQI_D5m+4IO0`Vf$6Iu>Fp)$}U=E0n~NQpqwGWZ2o0x z%*OKfkXD3j7Ik3)Pt!?%!Qmceq1RDD|0k(?0s_LCj0(c}DhB1Q>)6dJ-AC7(ii92j zph9qfvFyxb55*MVH~tuuQH){?xrq}mGjx*F&9bAwoEvkD3uUfMH?;Q0%DlipxD-;B z1!0;aQsZ!zcS{<%PeQg@G+TE5kgYJt7G1mc3}h>q$kr7j$d=}HQS@aGmiWWj^W`(3 zSdMqVc1wVpCT}(f8k;~fL_(Ge|abwqxw@H)Z&N%~=f>ja|BVa_&% zH{esyGQeM|p<^S-M4EWg+(=X}k4?uLt^ekx#+;DPwdT>M1OG1}GjBM10O6go%$%p6 zFR-$sq#YtV$E6_xcg8sW%3i>O;VYH8PxEK!Cq!U0v3mtj>Rl&t#_KRZBHahXBvTEQ zy{TTAGG^+6-2^k!8Y&~>n!5!oi)K;#P-@>qD%0%6v_FzAauNX9KP|mSywI2~!?#a-?tB(CQ-XsUPm zw+HhP@v>uZeqkkwIqt4K{Pax|sF51c(6`r5q{@i&26$V~pfafl2D!v~g1R9-i~5Ce z>9ofZf{3xO8}2*vRcuFw-3@%eV)A-dj3EX3Ur!H&X2{K_ZC%Jz&DR{^x5UXPUPr!@ z4H4yOHpy2R##sRh?y5!t0WGm-gUG7urEyT&2`LRYs30OyL931%Nf=ipmujDM1dwzP z@ZKnYP=;UEZLP0`3~J+VvzwTrSfhtU1gzPrjLV?_o10uL@%P7<`IR*OS@FYh4nN^Z zyPaYhKug}j^W#oyVO0k4=l5d`f!leUJ+UOz`K9*8mYUF3?(GB&Kh_1zdR;d{&4=8` zI?-~XNK8B~_7~SaT9%_msN!963 z$mrHt7CSqp_%TBEix`KO`U6-<$}~M|R_2Av;sW5eI>)|&r1Qqm-ggSFw-pfZF}SoP zq;o|PwOOmrUOI=h;*g-v_)v#!=*ox;BFV%ezHlxJ*)9d&e7)LY@_)gwtfe z)NT%`DT~K!>(s-%-VC5({Z+J-4c#%dfO>H#re4UAn|WhGlc6_T5@I>YHzVYnJon?9 z&1`+s?_2u)%SW^bW(xxkzO0OSz zA{duksNV6DNCOL;Z(A`)T4j<5c$!&!Mga5S-_^QLOX=p|a^bFiW>=mVZqbWnC3jx~ z)PgpOD^rXbDb#}oS9ddli;lHCB8g=T|MFJ$fk54Z%hxUklQrI<^--ceGHm(xPbuHM z)MLH%iUC^sfO2Ch<|<+>i`e@5U6=N|sWfES{J(()R{Y^*rWHcZo(XOfKKGHbo!QHn zR;`7?aX&>}@euLxl!W#YyHGg5iF0@$7WX=Qx z@=AFHrN+y+94|Ufvz*)*Vz=;u;d9F?*#)Y}w!UsAk$W`urhDDmHGQwK*r=yjvZ`a+)k@@IJAlGsknybf9qj2tqQsPbEfVL+IiP3V zVtny{bjCp;qHnk9K-Ik*I?BKq9whs91N*g@c4x!wV>ypE^BZm9BH|q<3U9&j({^9% z>K5W{zksuHp8lF*CQa+-y&}GvgS@IX<)C6?;cVP)*eS%yZIok>0OiS%kd5*5kC$q> zm#`B_$$%SvxN}}x5N3rqKAeU635Kb2lQld#-ZQH9ix)8??3Y9-xdezVXXRiAYBtGk z`F~&r{=aTl#9aE!iW!7kU-@syFhxS?;dQ0dH|us^)bS?gWkB4L`13jq6d@YZG_kPp zi9Y;8<-(QbR}hCjA`TiIk-J$91(-TS=-MH+N_B_!8EyQ`p^H-qiTwBBe>d!3A^G=W z{HqM0Q2)F3gjX0yVCthAjS1pr$oPasF8W&u6N~%f zPkW()XGL`b|MMH-MUen65yc`q4f&DLWB%dazh(*YgPS{)u#>QcetJg94TyT5h9lR{ zzeYk(2YHVZ1D^>o^`~d5;gS5gF8zn!`p<&!7xg4t`5@%w+MmCfyu`=vtW!~Xwf4pL R8(HvAQ&m^xw(`NV{{=w{&L02( literal 0 HcmV?d00001