From 1f0289ff3921032182de85b66b662171006bc0ee Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 15 Nov 2023 15:27:55 +0000 Subject: [PATCH 01/51] 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 02/51] 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 03/51] 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 04/51] 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 05/51] 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 06/51] 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 07/51] 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 08/51] 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 09/51] 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 10/51] 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 11/51] 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 12/51] 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 13/51] 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 14/51] 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 15/51] 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 16/51] 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 17/51] 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 18/51] 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 19/51] 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 20/51] 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 21/51] 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 22/51] 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 23/51] 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 24/51] 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 25/51] 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 26/51] 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 27/51] 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 28/51] 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 29/51] 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 30/51] 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 31/51] 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 32/51] 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 33/51] 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 34/51] 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 35/51] 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 36/51] 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 37/51] 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 38/51] 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 39/51] 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 40/51] 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 41/51] 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 42/51] 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 43/51] 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 44/51] 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 45/51] 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 46/51] 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 47/51] 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 48/51] 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 49/51] 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 50/51] 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 51/51] 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(