diff --git a/lib_xua/src/core/clocking/clockgen.xc b/lib_xua/src/core/clocking/clockgen.xc index 9ccd0e3f..da5f2711 100644 --- a/lib_xua/src/core/clocking/clockgen.xc +++ b/lib_xua/src/core/clocking/clockgen.xc @@ -7,23 +7,7 @@ #include "xua.h" #include "xua_commands.h" #include "xua_clocking.h" - -/* By default we use SW_PLL for Digital Rx configs running on XCORE-AI */ -/* Note: Not yet implemented for Synchronous mode */ -#ifdef __XS3A__ -#ifndef USE_SW_PLL -#define USE_SW_PLL 1 -#endif /* USE_SW_PLL */ -#else -#define USE_SW_PLL 0 -#endif /* __XS3A__ */ - -#if USE_SW_PLL -extern "C" -{ - #include "sw_pll.h" -} -#endif +#include "sw_pll_wrapper.h" #if (XUA_SPDIF_RX_EN) #include "spdif.h" @@ -235,206 +219,7 @@ static inline int validSamples(Counter &counter, int clockIndex) #if USE_SW_PLL /* Pointer to sw_pll struct to allow it to be used in separate SDM thread */ -unsafe -{ - sw_pll_state_t * unsafe sw_pll_ptr = NULL; -} - -#define DISABLE_SDM 0x1000000 /* Control value to disable SDM. Outside of normal range.*/ - -unsigned InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk) -{ - /* Autogenerated SDM App PLL setup by dco_model.py using 22.5792_1M profile */ - /* Input freq: 24000000 - F: 134 - R: 0 - f: 8 - p: 18 - OD: 5 - ACD: 5 - */ - - #define APP_PLL_CTL_REG_22 0x0A808600 - #define APP_PLL_DIV_REG_22 0x80000005 - #define APP_PLL_FRAC_REG_22 0x80000812 - #define SW_PLL_SDM_CTRL_MID_22 498283 - #define SW_PLL_SDM_RATE_22 1000000 - - /* 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_24 0x0A809200 - #define APP_PLL_DIV_REG_24 0x80000005 - #define APP_PLL_FRAC_REG_24 0x8000040A - #define SW_PLL_SDM_CTRL_MID_24 478151 - #define SW_PLL_SDM_RATE_24 1000000 - - - const uint32_t app_pll_ctl_reg[2] = {APP_PLL_CTL_REG_22, APP_PLL_CTL_REG_24}; - const uint32_t app_pll_div_reg[2] = {APP_PLL_DIV_REG_22, APP_PLL_DIV_REG_24}; - const uint32_t app_pll_frac_reg[2] = {APP_PLL_FRAC_REG_22, APP_PLL_FRAC_REG_24}; - const uint32_t sw_pll_sdm_ctrl_mid[2] = {SW_PLL_SDM_CTRL_MID_22, SW_PLL_SDM_CTRL_MID_24}; - const uint32_t sw_pll_sdm_rate[2] = {SW_PLL_SDM_RATE_22, SW_PLL_SDM_RATE_24}; - - const int clkIndex = mClk == MCLK_48 ? 1 : 0; - - sw_pll_sdm_init(&sw_pll, - SW_PLL_15Q16(0.0), - SW_PLL_15Q16(32.0), - SW_PLL_15Q16(0.25), - 0, /* LOOP COUNT Don't care for this API */ - 0, /* PLL_RATIO Don't care for this API */ - 0, /* No jitter compensation needed */ - app_pll_ctl_reg[clkIndex], - app_pll_div_reg[clkIndex], - app_pll_frac_reg[clkIndex], - sw_pll_sdm_ctrl_mid[clkIndex], - 3000 /* PPM_RANGE (FOR PFD) Don't care for this API*/ ); - - /* Reset SDM too */ - sw_pll_init_sigma_delta(&sw_pll.sdm_state); - - printstr("Init sw_pll: "); printuintln(mClk); - - return (XS1_TIMER_HZ / sw_pll_sdm_rate[clkIndex]); -} - -void do_sw_pll_phase_frequency_detector( sw_pll_state_t sw_pll, - unsigned short mclk_time_stamp, - unsigned mclks_per_sample, - chanend c_sigma_delta, - int receivedSamples, - int &reset_sw_pll_pfd) -{ - const unsigned control_loop_rate_divider = 6; /* 300Hz * 2 edges / 6 -> 100Hz loop rate */ - static unsigned control_loop_counter = 0; - static unsigned total_received_samples = 0; - - /* Keep a store of the last mclk time stamp so we can work out the increment */ - static unsigned short last_mclk_time_stamp = 0; - - control_loop_counter++; - - total_received_samples += receivedSamples; - - if(control_loop_counter == control_loop_rate_divider) - { - /* Calculate what the zero-error mclk count increment should be for this many samples */ - const unsigned expected_mclk_inc = mclks_per_sample * total_received_samples / 2; /* divide by 2 because this fn is called per edge */ - - /* Calculate actualy time-stamped mclk count increment is */ - const unsigned short actual_mclk_inc = mclk_time_stamp - last_mclk_time_stamp; - - /* The difference is the raw error in terms of mclk counts */ - short f_error = (int)actual_mclk_inc - (int)expected_mclk_inc; - if(reset_sw_pll_pfd) - { - f_error = 0; /* Skip first measurement as it will likely be very out */ - reset_sw_pll_pfd = 0; - } - printintln(f_error); - - - /* send PFD output to the sigma delta thread */ - outuint(c_sigma_delta, (int) f_error); - - last_mclk_time_stamp = mclk_time_stamp; - control_loop_counter = 0; - total_received_samples = 0; - } -} - -void SigmaDeltaTask(chanend c_sigma_delta, unsigned sdm_interval){ - /* 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. */ - - /* To be extra safe, spin on sw_pll_ptr until it has been initialised by clockgen */ - while(sw_pll_ptr == NULL); - - int f_error = 0; - int dco_setting = SW_PLL_SDM_CTRL_MID_24; // Assume 24.576MHz as initial clock - unsafe - { - sw_pll_init_sigma_delta(&sw_pll_ptr->sdm_state); - } - - tileref_t this_tile = get_local_tile_id(); - - timer tmr; - int32_t time_trigger; - tmr :> time_trigger; - int send_ack_once = 1; - - unsigned rx_word; - while(1) - { - /* Poll for new SDM control value */ - select - { - case inuint_byref(c_sigma_delta, rx_word): - if(rx_word == DISABLE_SDM) - { - f_error = 0; - send_ack_once = 1; - } - else - { - f_error = (int32_t)rx_word; - unsafe - { - sw_pll_sdm_do_control_from_error(sw_pll_ptr, -f_error); - dco_setting = sw_pll_ptr->sdm_state.current_ctrl_val; - } - } - break; - - // Do nothing & fall-through - default: - break; - } - - /* Wait until the timer value has been reached - This implements a timing barrier and keeps - the loop rate constant. */ - select - { - case tmr when timerafter(time_trigger) :> int _: - time_trigger += sdm_interval; - break; - } - - /* Do not write to the frac reg until we get out first - control value. This will avoid the writing of the - frac reg from two different threads which may cause - a channel deadlock. */ - if(rx_word != DISABLE_SDM) - unsafe { - sw_pll_do_sigma_delta(&sw_pll_ptr->sdm_state, this_tile, dco_setting); - send_ack_once = 1; - } - else if(send_ack_once) - { - /* Send ACK once to synchrnoise with clockgen signalling it's OK to reconfig */ - outuint(c_sigma_delta, 0); /* Send ACK to say reg writes have ceased */ - send_ack_once = 0; - } - } -} - - -void disable_sigma_delta(chanend c_sigma_delta) -{ - outuint(c_sigma_delta, DISABLE_SDM); /* Stops SDM */ - inuint(c_sigma_delta); /* Wait for ACK so we know reg write is complete */ -} +extern sw_pll_state_t * unsafe sw_pll_ptr; #endif @@ -856,7 +641,11 @@ void clockGen ( streaming chanend ?c_spdif_rx, timeNextEdge = spdifRxTime + LOCAL_CLOCK_INCREMENT + LOCAL_CLOCK_MARGIN; #if USE_SW_PLL - do_sw_pll_phase_frequency_detector(sw_pll, mclk_time_stamp, mclks_per_sample, c_sigma_delta, spdifCounters.receivedSamples, reset_sw_pll_pfd); + do_sw_pll_phase_frequency_detector_dig_rx( mclk_time_stamp, + mclks_per_sample, + c_sigma_delta, + spdifCounters.receivedSamples, + reset_sw_pll_pfd); #else /* Toggle edge */ i_pll_ref.toggle_timed(1); @@ -969,7 +758,11 @@ void clockGen ( streaming chanend ?c_spdif_rx, timeNextEdge = adatReceivedTime + LOCAL_CLOCK_INCREMENT + LOCAL_CLOCK_MARGIN; #if USE_SW_PLL - do_sw_pll_phase_frequency_detector(sw_pll, mclk_time_stamp, mclks_per_sample, c_sigma_delta, adatCounters.receivedSamples, reset_sw_pll_pfd); + do_sw_pll_phase_frequency_detector_dig_rx( mclk_time_stamp, + mclks_per_sample, + c_sigma_delta, + adatCounters.receivedSamples, + reset_sw_pll_pfd); #else /* Toggle edge */ i_pll_ref.toggle_timed(1); diff --git a/lib_xua/src/core/clocking/sw_pll_wrapper.h b/lib_xua/src/core/clocking/sw_pll_wrapper.h new file mode 100644 index 00000000..74e1d51d --- /dev/null +++ b/lib_xua/src/core/clocking/sw_pll_wrapper.h @@ -0,0 +1,58 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* By default we use SW_PLL for Digital Rx configs running on XCORE-AI */ +/* Note: Not yet implemented for Synchronous mode */ +#ifdef __XS3A__ +#ifndef USE_SW_PLL +#define USE_SW_PLL 1 +#endif /* USE_SW_PLL */ +#else +#define USE_SW_PLL 0 +#endif /* __XS3A__ */ + + +extern "C" +{ + #include "sw_pll.h" +} + +/* Special control value to disable SDM. Outside of normal range which is less than 16b.*/ +#define DISABLE_SDM 0x1000000 + + +/** Task that receives an error term, passes it through a PI controller and periodically + * calclulates a sigma delta output value and sends it to the PLL fractional register. + * + * \param c_sigma_delta Channel connected to the clocking thread to pass raw error terms. + * \param sdm_interval Unisgned value containing the sigma delta period in timer ticks. + */ +void SigmaDeltaTask(chanend c_sigma_delta, unsigned sdm_interval); + +/** Helper function that sends a special disable command and waits for ACK. This is used + * to help prevemt simultaenous access to the PLL register from two different threads, + * + * \param c_sigma_delta Channel connected to the clocking thread to pass raw error terms. + */ +void disable_sigma_delta(chanend c_sigma_delta); + +/** Performs a frequency comparsion between the incoming digital Rx stream and the local mclk. + * + * \param mclk_time_stamp The captured mclk count (using port timer) at the time of sample Rx. + * \param mclks_per_sample The nominal number of mclks per audio sample. + * \param c_sigma_delta Channel connected to the sigma delta and controller thread. + * \param receivedSamples The number of received samples since tha last call to this function. + * \param reset_sw_pll_pfd Reference to a flag which will be used to signal reset of this function's state. + */ +void do_sw_pll_phase_frequency_detector_dig_rx( unsigned short mclk_time_stamp, + unsigned mclks_per_sample, + chanend c_sigma_delta, + int receivedSamples, + int &reset_sw_pll_pfd); + +/** Initilaises the software PLL both hardware and state. Sets the mclk frequency to a nominal point. + * + * \param sw_pll Reference to a software pll state struct to be initialised. + * \param mClk The current nominal mClk frequency. + */ +unsigned InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk); diff --git a/lib_xua/src/core/clocking/sw_pll_wrapper.xc b/lib_xua/src/core/clocking/sw_pll_wrapper.xc new file mode 100644 index 00000000..3ac482a2 --- /dev/null +++ b/lib_xua/src/core/clocking/sw_pll_wrapper.xc @@ -0,0 +1,215 @@ +// Copyright 2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include + +#include "sw_pll_wrapper.h" + +#if USE_SW_PLL +extern "C" +{ + #include "sw_pll.h" +} + +/* Pointer to sw_pll struct to allow it to be used in separate SDM thread */ +unsafe +{ + sw_pll_state_t * unsafe sw_pll_ptr = NULL; +} + + +unsigned InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk) +{ + /* Autogenerated SDM App PLL setup by dco_model.py using 22.5792_1M profile */ + /* Input freq: 24000000 + F: 134 + R: 0 + f: 8 + p: 18 + OD: 5 + ACD: 5 + */ + + #define APP_PLL_CTL_REG_22 0x0A808600 + #define APP_PLL_DIV_REG_22 0x80000005 + #define APP_PLL_FRAC_REG_22 0x80000812 + #define SW_PLL_SDM_CTRL_MID_22 498283 + #define SW_PLL_SDM_RATE_22 1000000 + + /* 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_24 0x0A809200 + #define APP_PLL_DIV_REG_24 0x80000005 + #define APP_PLL_FRAC_REG_24 0x8000040A + #define SW_PLL_SDM_CTRL_MID_24 478151 + #define SW_PLL_SDM_RATE_24 1000000 + + + const uint32_t app_pll_ctl_reg[2] = {APP_PLL_CTL_REG_22, APP_PLL_CTL_REG_24}; + const uint32_t app_pll_div_reg[2] = {APP_PLL_DIV_REG_22, APP_PLL_DIV_REG_24}; + const uint32_t app_pll_frac_reg[2] = {APP_PLL_FRAC_REG_22, APP_PLL_FRAC_REG_24}; + const uint32_t sw_pll_sdm_ctrl_mid[2] = {SW_PLL_SDM_CTRL_MID_22, SW_PLL_SDM_CTRL_MID_24}; + const uint32_t sw_pll_sdm_rate[2] = {SW_PLL_SDM_RATE_22, SW_PLL_SDM_RATE_24}; + + const int clkIndex = mClk == MCLK_48 ? 1 : 0; + + sw_pll_sdm_init(&sw_pll, + SW_PLL_15Q16(0.0), + SW_PLL_15Q16(32.0), + SW_PLL_15Q16(0.25), + 0, /* LOOP COUNT Don't care for this API */ + 0, /* PLL_RATIO Don't care for this API */ + 0, /* No jitter compensation needed */ + app_pll_ctl_reg[clkIndex], + app_pll_div_reg[clkIndex], + app_pll_frac_reg[clkIndex], + sw_pll_sdm_ctrl_mid[clkIndex], + 3000 /* PPM_RANGE (FOR PFD) Don't care for this API*/ ); + + /* Reset SDM too */ + sw_pll_init_sigma_delta(&sw_pll.sdm_state); + + printstr("Init sw_pll: "); printuintln(mClk); + + return (XS1_TIMER_HZ / sw_pll_sdm_rate[clkIndex]); +} + +void do_sw_pll_phase_frequency_detector_dig_rx( unsigned short mclk_time_stamp, + unsigned mclks_per_sample, + chanend c_sigma_delta, + int receivedSamples, + int &reset_sw_pll_pfd) +{ + const unsigned control_loop_rate_divider = 6; /* 300Hz * 2 edges / 6 -> 100Hz loop rate */ + static unsigned control_loop_counter = 0; + static unsigned total_received_samples = 0; + + /* Keep a store of the last mclk time stamp so we can work out the increment */ + static unsigned short last_mclk_time_stamp = 0; + + control_loop_counter++; + + total_received_samples += receivedSamples; + + if(control_loop_counter == control_loop_rate_divider) + { + /* Calculate what the zero-error mclk count increment should be for this many samples */ + const unsigned expected_mclk_inc = mclks_per_sample * total_received_samples / 2; /* divide by 2 because this fn is called per edge */ + + /* Calculate actualy time-stamped mclk count increment is */ + const unsigned short actual_mclk_inc = mclk_time_stamp - last_mclk_time_stamp; + + /* The difference is the raw error in terms of mclk counts */ + short f_error = (int)actual_mclk_inc - (int)expected_mclk_inc; + if(reset_sw_pll_pfd) + { + f_error = 0; /* Skip first measurement as it will likely be very out */ + reset_sw_pll_pfd = 0; + } + printintln(f_error); + + + /* send PFD output to the sigma delta thread */ + outuint(c_sigma_delta, (int) f_error); + + last_mclk_time_stamp = mclk_time_stamp; + control_loop_counter = 0; + total_received_samples = 0; + } +} + +void SigmaDeltaTask(chanend c_sigma_delta, unsigned sdm_interval){ + /* 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. */ + + /* To be extra safe, spin on sw_pll_ptr until it has been initialised by clockgen */ + while(sw_pll_ptr == NULL); + + int f_error = 0; + int dco_setting = SW_PLL_SDM_CTRL_MID_24; // Assume 24.576MHz as initial clock + unsafe + { + sw_pll_init_sigma_delta(&sw_pll_ptr->sdm_state); + } + + tileref_t this_tile = get_local_tile_id(); + + timer tmr; + int32_t time_trigger; + tmr :> time_trigger; + int send_ack_once = 1; + + unsigned rx_word; + while(1) + { + /* Poll for new SDM control value */ + select + { + case inuint_byref(c_sigma_delta, rx_word): + if(rx_word == DISABLE_SDM) + { + f_error = 0; + send_ack_once = 1; + } + else + { + f_error = (int32_t)rx_word; + unsafe + { + sw_pll_sdm_do_control_from_error(sw_pll_ptr, -f_error); + dco_setting = sw_pll_ptr->sdm_state.current_ctrl_val; + } + } + break; + + // Do nothing & fall-through + default: + break; + } + + /* Wait until the timer value has been reached + This implements a timing barrier and keeps + the loop rate constant. */ + select + { + case tmr when timerafter(time_trigger) :> int _: + time_trigger += sdm_interval; + break; + } + + /* Do not write to the frac reg until we get out first + control value. This will avoid the writing of the + frac reg from two different threads which may cause + a channel deadlock. */ + if(rx_word != DISABLE_SDM) + unsafe { + sw_pll_do_sigma_delta(&sw_pll_ptr->sdm_state, this_tile, dco_setting); + send_ack_once = 1; + } + else if(send_ack_once) + { + /* Send ACK once to synchrnoise with clockgen signalling it's OK to reconfig */ + outuint(c_sigma_delta, 0); /* Send ACK to say reg writes have ceased */ + send_ack_once = 0; + } + } +} + + +void disable_sigma_delta(chanend c_sigma_delta) +{ + outuint(c_sigma_delta, DISABLE_SDM); /* Stops SDM */ + inuint(c_sigma_delta); /* Wait for ACK so we know reg write is complete */ +} + +#endif