Refactor sw_pll code into own source file

This commit is contained in:
Ed
2024-01-09 08:56:58 +00:00
parent ccaaf40ab3
commit 3003ce7241
3 changed files with 285 additions and 219 deletions

View File

@@ -7,23 +7,7 @@
#include "xua.h" #include "xua.h"
#include "xua_commands.h" #include "xua_commands.h"
#include "xua_clocking.h" #include "xua_clocking.h"
#include "sw_pll_wrapper.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
#if (XUA_SPDIF_RX_EN) #if (XUA_SPDIF_RX_EN)
#include "spdif.h" #include "spdif.h"
@@ -235,206 +219,7 @@ static inline int validSamples(Counter &counter, int clockIndex)
#if USE_SW_PLL #if USE_SW_PLL
/* Pointer to sw_pll struct to allow it to be used in separate SDM thread */ /* Pointer to sw_pll struct to allow it to be used in separate SDM thread */
unsafe extern sw_pll_state_t * unsafe sw_pll_ptr;
{
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 */
}
#endif #endif
@@ -856,7 +641,11 @@ void clockGen ( streaming chanend ?c_spdif_rx,
timeNextEdge = spdifRxTime + LOCAL_CLOCK_INCREMENT + LOCAL_CLOCK_MARGIN; timeNextEdge = spdifRxTime + LOCAL_CLOCK_INCREMENT + LOCAL_CLOCK_MARGIN;
#if USE_SW_PLL #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 #else
/* Toggle edge */ /* Toggle edge */
i_pll_ref.toggle_timed(1); 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; timeNextEdge = adatReceivedTime + LOCAL_CLOCK_INCREMENT + LOCAL_CLOCK_MARGIN;
#if USE_SW_PLL #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 #else
/* Toggle edge */ /* Toggle edge */
i_pll_ref.toggle_timed(1); i_pll_ref.toggle_timed(1);

View File

@@ -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);

View File

@@ -0,0 +1,215 @@
// Copyright 2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xs1.h>
#include <assert.h>
#include <print.h>
#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