This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
## Source files
file(GLOB_RECURSE LIB_C_SOURCES src/*.c )
file(GLOB_RECURSE LIB_CXX_SOURCES src/*.cc)
file(GLOB_RECURSE LIB_XC_SOURCES src/*.xc)
file(GLOB_RECURSE LIB_ASM_SOURCES src/*.S )
## cmake doesn't recognize .S files as assembly by default
set_source_files_properties(LIB_ASM_SOURCES PROPERTIES LANGUAGE ASM)
## Assume all asm is XS3A for now
set(XCORE_XS3A_SOURCES ${LIB_ASM_SOURCES})
## Set any local library compile options
set(LIB_COMPILE_FLAGS "-Os" "-g")
## Includes files
set(LIB_PUBLIC_INCLUDES api src)
set(LIB_PRIVATE_INCLUDES src)
## Gather library sources
set(LIB_PUBLIC_SOURCES "")
set(LIB_PRIVATE_SOURCES ${LIB_C_SOURCES} ${LIB_CXX_SOURCES} ${LIB_XC_SOURCES})
## Append platform specific sources
list(APPEND LIB_PRIVATE_SOURCES ${${CMAKE_SYSTEM_NAME}_SOURCES})
## Create library target
add_library(lib_sw_pll STATIC)
target_sources(lib_sw_pll
PUBLIC
${LIB_PUBLIC_SOURCES}
PRIVATE
${LIB_PRIVATE_SOURCES}
)
target_include_directories(lib_sw_pll
PUBLIC
${LIB_PUBLIC_INCLUDES}
PRIVATE
${LIB_PRIVATE_INCLUDES}
)
target_compile_options(lib_sw_pll
PRIVATE
${LIB_COMPILE_FLAGS}
)

View File

@@ -0,0 +1,317 @@
// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <xccompat.h>
#ifdef __XC__
#define _Bool uint8_t
#else
#include <xcore/hwtimer.h>
#include <xcore/port.h>
#include <xcore/clock.h>
#include <xcore/channel.h>
#include <xcore/assert.h>
#endif
// SW_PLL Component includes
#include "sw_pll_common.h"
#include "sw_pll_pfd.h"
#include "sw_pll_sdm.h"
/**
* \addtogroup sw_pll_lut sw_pll_lut
*
* The public API for using the Software PLL.
* @{
*/
/**
* 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 for the PFD. Don't care otherwise.
* Used to calculate the expected port timer increment when control is called.
* \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.
*
* 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.
* Eg. 16kHz, 48kHz ...
*
* 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 mclk count value.
*
* 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 reduced output jitter.
*
* \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
* 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 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);
/**
* 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 time and the
* application PLL will be adjusted to minimise the error seen on the input error value.
*
* \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.
* The type is sw_pll_lock_status_t.
*/
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 limits. Note this resets the PFD accumulators too and so PI controller state is reset.
*
* \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.
* \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
/**
* \addtogroup sw_pll_sdm sw_pll_sdm
*
* The public API for using the Software PLL.
* @{
*/
/**
* 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 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 in the PFD. Don't care otherwise.
* Used to calculate the expected port timer increment when 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.
* \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 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
* 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,
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 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_sdm_do_control control function.
*
* 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.
* 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 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 reduced output jitter.
*
* \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
* control loop will assume that 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.
* 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 sw_pll state struct.
* \param error 16b signed input error value
* \returns The controller lock status
*/
sw_pll_lock_status_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error);
/**
* Use to initialise the core sigma delta modulator. Broken out as seperate API as the SDM
* is usually run in a dedicated thread which could be on a remote tile.
*
* \param sw_pll Pointer to the SDM state struct.
*/
void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state);
#ifdef __DOXYGEN__
/**
* 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);
#endif
/**@}*/ // END: addtogroup sw_pll_sdm
/**
* \addtogroup sw_pll_common sw_pll_common
*
* The public API for using the Software PLL.
* @{
*/
/**
* Resets PI controller state
*
* \param sw_pll Pointer to the Software PLL state.
*/
__attribute__((always_inline))
inline void sw_pll_reset_pi_state(sw_pll_state_t * const sw_pll)
{
sw_pll->pi_state.error_accum = 0;
sw_pll->pi_state.error_accum_accum = 0;
}
/**
* Output a fixed (not phase locked) clock between 11.2896 MHz and 49.152 MHz.
* Assumes a 24 MHz XTAL.
*
* \param frequency Frequency in Hz. An incorrect value will assert.
*/
void sw_pll_fixed_clock(const unsigned frequency);
/**@}*/ // END: addtogroup sw_pll_common

View File

@@ -0,0 +1,7 @@
set(LIB_NAME lib_sw_pll)
set(LIB_VERSION 2.2.0)
set(LIB_INCLUDES api src)
set(LIB_COMPILER_FLAGS -Os -g)
set(LIB_DEPENDENT_MODULES "")
XMOS_REGISTER_MODULE()

View File

@@ -0,0 +1,14 @@
VERSION = 2.2.0
DEPENDENT_MODULES =
MODULE_XCC_FLAGS = $(XCC_FLAGS)
OPTIONAL_HEADERS +=
EXPORT_INCLUDE_DIRS = api \
src
INCLUDE_DIRS = $(EXPORT_INCLUDE_DIRS)
SOURCE_DIRS = src

View File

@@ -0,0 +1,138 @@
// Copyright 2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifdef __XS3A__
#include "sw_pll.h"
// 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.
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);
// Wait for PLL to settle.
blocking_delay(500 * XS1_TIMER_MHZ);
// 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);
}
//Found solution: IN 24.000MHz, OUT 49.151786MHz, VCO 3145.71MHz, RD 1, FD 131.071 (m = 1, n = 14), OD 8, FOD 2, ERR -4.36ppm
// Measure: 100Hz-40kHz: ~7ps
// 100Hz-1MHz: 70ps.
// 100Hz high pass: 118ps.
#define APP_PLL_CTL_49M 0x0B808200
#define APP_PLL_DIV_49M 0x80000001
#define APP_PLL_FRAC_49M 0x8000000D
//Found solution: IN 24.000MHz, OUT 45.157895MHz, VCO 2709.47MHz, RD 1, FD 112.895 (m = 17, n = 19), OD 5, FOD 3, ERR -11.19ppm
// Measure: 100Hz-40kHz: 6.5ps
// 100Hz-1MHz: 67ps.
// 100Hz high pass: 215ps.
#define APP_PLL_CTL_45M 0x0A006F00
#define APP_PLL_DIV_45M 0x80000002
#define APP_PLL_FRAC_45M 0x80001012
// Found solution: IN 24.000MHz, OUT 24.576000MHz, VCO 2457.60MHz, RD 1, FD 102.400 (m = 2, n = 5), OD 5, FOD 5, ERR 0.0ppm
// Measure: 100Hz-40kHz: ~8ps
// 100Hz-1MHz: 63ps.
// 100Hz high pass: 127ps.
#define APP_PLL_CTL_24M 0x0A006500
#define APP_PLL_DIV_24M 0x80000004
#define APP_PLL_FRAC_24M 0x80000104
// Found solution: IN 24.000MHz, OUT 22.579186MHz, VCO 3522.35MHz, RD 1, FD 146.765 (m = 13, n = 17), OD 3, FOD 13, ERR -0.641ppm
// Measure: 100Hz-40kHz: 7ps
// 100Hz-1MHz: 67ps.
// 100Hz high pass: 260ps.
#define APP_PLL_CTL_22M 0x09009100
#define APP_PLL_DIV_22M 0x8000000C
#define APP_PLL_FRAC_22M 0x80000C10
#define APP_PLL_CTL_12M 0x0A006500
#define APP_PLL_DIV_12M 0x80000009
#define APP_PLL_FRAC_12M 0x80000104
#define APP_PLL_CTL_11M 0x09009100
#define APP_PLL_DIV_11M 0x80000019
#define APP_PLL_FRAC_11M 0x80000C10
// Setup a fixed clock (not phase locked)
void sw_pll_fixed_clock(const unsigned frequency)
{
unsigned ctrl = 0;
unsigned div = 0;
unsigned frac = 0;
switch(frequency)
{
case 44100*256:
ctrl = APP_PLL_CTL_11M;
div = APP_PLL_DIV_11M;
frac = APP_PLL_FRAC_11M;
break;
case 48000*256:
ctrl = APP_PLL_CTL_12M;
div = APP_PLL_DIV_12M;
frac = APP_PLL_FRAC_12M;
break;
case 44100*512:
ctrl = APP_PLL_CTL_22M;
div = APP_PLL_DIV_22M;
frac = APP_PLL_FRAC_22M;
break;
case 48000*512:
ctrl = APP_PLL_CTL_24M;
div = APP_PLL_DIV_24M;
frac = APP_PLL_FRAC_24M;
break;
case 44100*1024:
ctrl = APP_PLL_CTL_45M;
div = APP_PLL_DIV_45M;
frac = APP_PLL_FRAC_45M;
break;
case 48000*1024:
ctrl = APP_PLL_CTL_49M;
div = APP_PLL_DIV_49M;
frac = APP_PLL_FRAC_49M;
break;
default:
xassert(0);
break;
}
sw_pll_app_pll_init(get_local_tile_id(), ctrl, div, frac);
}
#endif // __XS3A__

View File

@@ -0,0 +1,123 @@
// Copyright 2023-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#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
#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]))
// This is just here to catch an error and provide useful info if you happen to forget to include from XC properly
typedef struct xc_check{
int *xc_check; // If you see this error, then you need to extern "C"{} the sw_pll include in your XC file.
} xc_check;
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
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;
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 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
int32_t ds_x3; // Sigma delta modulator state
} sw_pll_sdm_state_t;
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;
/**
* This is the core PI controller code used by both SDM and LUT SW PLLs.
*
* \param sw_pll Pointer to the Software PLL state.
* \param error The error input to the PI controller.
*/
__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;
}
/**
* Initialise the application (secondary) PLL.
*
* \param tileid The resource ID of the tile that calls this function.
* \param app_pll_ctl_reg_val The App PLL control register setting.
* \param app_pll_div_reg_val The App PLL divider register setting.
* \param frac_val_nominal The App PLL initial fractional register setting.
*/ 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);

View File

@@ -0,0 +1,129 @@
// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifdef __XS3A__
#include "sw_pll.h"
__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->lut_state.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->lut_state.num_lut_entries)
{
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;
}
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_state.lut_table_base[frac_index];
}
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(),
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_lut_reset(sw_pll, Kp, Ki, Kii, num_lut_entries);
// 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;
sw_pll_reset_pi_state(sw_pll);
// 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 PFD state
sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range);
}
__attribute__((always_inline))
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);
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;
}
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)
{
sw_pll->loop_counter = 0;
if (sw_pll->first_loop) // First loop around so ensure state is clear
{
sw_pll->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data
sw_pll_reset_pi_state(sw_pll);
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->pfd_state, &sw_pll->first_loop, mclk_pt, ref_clk_pt);
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;
}
}
return sw_pll->lock_status;
}
#endif // __XS3A__

View File

@@ -0,0 +1,35 @@
// Copyright 2023-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifdef __XS3A__
#include <xcore/assert.h>
#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);
}
#endif // __XS3A__

View File

@@ -0,0 +1,85 @@
// Copyright 2023-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <stdint.h>
#include <stddef.h>
#include "sw_pll_common.h"
#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
/**
* Initialises the Phase Frequency Detector.
*
* Sets how often the PFD and control loop runs and error limits for resetting
*
* \param pfd_state Pointer to the struct to be initialised.
* \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 for the PFD. Don't care otherwise.
* Used to calculate the expected port timer increment when control is called.
* \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 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_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);
/**
* Helper to perform the maths needed on the port count, accounting for wrapping, to determine the frequency difference.
*
* \param pfd_state Pointer to the PFD state.
* \param first_loop Pointer to the first_loop var used for resetting when out of range.
* \param mclk_pt The clock count from the port counter.
* \param ref_clk_pt The clock count from the ref clock counter (optional).
*/
__attribute__((always_inline))
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(pfd->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);
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)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 = 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 = 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
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(pfd->mclk_diff) > pfd->mclk_max_diff)
{
*first_loop = 1;
}
}

View File

@@ -0,0 +1,150 @@
// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifdef __XS3A__
#include "sw_pll.h"
void sw_pll_sdm_controller_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 int32_t ctrl_mid_point)
{
// Setup sw_pll with supplied user paramaters
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;
sw_pll->pi_state.iir_y = 0;
sw_pll->sdm_state.current_ctrl_val = ctrl_mid_point;
sw_pll_reset_pi_state(sw_pll);
// 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;
}
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,
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
sw_pll_app_pll_init(get_local_tile_id(),
app_pll_ctl_reg_val,
app_pll_div_reg_val,
(uint16_t)(app_pll_frac_reg_val & 0xffff));
// Setup SDM controller state
sw_pll_sdm_controller_init( sw_pll,
Kp,
Ki,
Kii,
loop_rate_count,
ctrl_mid_point);
// Setup PFD state
sw_pll_pfd_init(&(sw_pll->pfd_state), loop_rate_count, pll_ratio, ref_clk_expected_inc, ppm_range);
}
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;
}
__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_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;
}
__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;
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->pfd_state.mclk_pt_last = mclk_pt; // load last mclk measurement with sensible data
sw_pll->pi_state.iir_y = 0;
sw_pll_reset_pi_state(sw_pll);
sw_pll->lock_counter = SW_PLL_LOCK_COUNT;
sw_pll->lock_status = SW_PLL_UNLOCKED_LOW;
sw_pll->first_loop = 0;
// 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);
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;
}
} else {
control_done = false;
}
return control_done;
}
#endif // __XS3A__

View File

@@ -0,0 +1,133 @@
// Copyright 2023-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "sw_pll.h"
#pragma once
#define SW_PLL_SDM_UPPER_LIMIT 980000
#define SW_PLL_SDM_LOWER_LIMIT 60000
typedef int tileref_t;
/**
* sw_pll_sdm_controller_init initialisation function.
*
* This sets up the PI controller and post processing for the SDM sw_pll. It is provided to allow
* cases where the PI controller may be separated from the SDM on a different tile and so we want
* to init the SDM without the sigma delta modulator code and physical writes to the app PLL.
*
* \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.
* \param ctrl_mid_point The nominal control value for the Sigma Delta Modulator output. Normally
* close to halfway to allow symmetrical range.
*
*/
void sw_pll_sdm_controller_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 int32_t ctrl_mid_point);
/**
* low level sw_pll_calc_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 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){
sdm_out = 8;
}
if (sdm_out < 0){
sdm_out = 0;
}
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 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 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 (sdm_out == 0){
frac_val = 0x00000007; // step 0/8
}
else{
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 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 32b register value
*/
__attribute__((always_inline))
static inline void sw_pll_write_frac_reg(tileref_t this_tile, uint32_t frac_val){
write_sswitch_reg_no_ack(this_tile, XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac_val);
}
/**
* 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);
}