Skip to content

Commit

Permalink
Regroup API in docs + various fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ed-xmos committed Nov 29, 2023
1 parent 7e7bdca commit 54a320d
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 92 deletions.
37 changes: 37 additions & 0 deletions doc/diagram_source/sdm_threads.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<mxfile host="Electron" modified="2023-11-29T10:03:54.733Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.2.8 Chrome/112.0.5615.165 Electron/24.2.0 Safari/537.36" etag="mNXInREwrp4ueu0fMMtq" version="21.2.8" type="device">
<diagram name="Page-1" id="SQhAqpfFYzWNY1yUmR3Z">
<mxGraphModel dx="1114" dy="755" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="A7zdPPGjzwQT9H-tJgH_-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="A7zdPPGjzwQT9H-tJgH_-1" target="A7zdPPGjzwQT9H-tJgH_-2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-1" value="SDM&lt;br&gt;Modulator&lt;br&gt;thread" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="520" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-2" value="PLL Fractional Register" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="680" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="A7zdPPGjzwQT9H-tJgH_-4" target="A7zdPPGjzwQT9H-tJgH_-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-4" value="Audio loop&lt;br&gt;thread with&lt;br&gt;SW_PLL control call" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="360" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-6" value="DCO&lt;br&gt;setting&lt;br&gt;channel" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="440" y="263" width="70" height="60" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-7" value="Loop rate&lt;br&gt;48 kHz" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="360" y="374" width="70" height="40" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-8" value="Loop rate&lt;br&gt;1 MHz" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="525" y="374" width="70" height="40" as="geometry" />
</mxCell>
<mxCell id="A7zdPPGjzwQT9H-tJgH_-9" value="Internal&lt;br&gt;peripheral&lt;br&gt;channel" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="595" y="263" width="80" height="60" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file added doc/rst/images/sdm_threads.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 33 additions & 2 deletions doc/rst/sw_pll.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ There are trade-offs between the two types of DCO which are summarised in the fo
- Moderate - 3 kB
- Low - 1 kB
* - MIPS Usage
- Low - < 5
- Fair - ~50
- Low - ~1
- High - ~50
* - Lock Range PPM
- Moderate - 100-1000
- Wide - 1500-3000
Expand Down Expand Up @@ -340,12 +340,43 @@ To help visualise how these resources work together, please see the below diagra
lib_sw_pll API
--------------

The Application Programmer Interface (API) for the Software PLL is shown below. It is split into common items needed for both LUT and SDM DCOs and items specific to each type of DCO.


WHY DOUBLE INTEGRAL TERM?

Common API
..........

The common API cover initialisation of the entire SW PLL and optional reset of the PI controller only.

.. doxygengroup:: sw_pll_general
:content-only:

LUT Based PLL API
.................

The LUT based API are functions designed to be called from an audio loop. Typically the functions can take up to 210 instruction cycles when control occurs and just a few 10s of cycles when control does not occur. If run at a rate of 48 kHz then it will consume approximately 1 MIPS.

.. doxygengroup:: sw_pll_lut
:content-only:

SDM Based PLL API
.................

All SDM API items are function calls. The SDM API requires a dedicated logical core to perform the SDM calculation and it is expected that the user provide the fork (par) and call the SDM in a loop. A typical idiom is to have it running in a loop with a timing barrier (either 1 us or 2 us depending on profile used) and a non-blocking channel poll which allows new DCO control values to be received periodically. The SDM calculation and register write takes 45 instuction cycles and so with the overheads of the timing barrier and the non-blocking channel receive poll, a minimum 60 MHz logical core should be set aside for the SDM task.

The control part of the SDM SW PLL takes 75 instuction cycles when active and a few 10s of cycles when inactive so you will need to budget around 1 MIPS for this.

An example of how to implement the threading, timing barrier and non-blocking channel poll can be found in ``examples/simple_sdm/simple_sw_pll_sdm.c``. A thread diagram of how this can look is shown below.


.. figure:: ./images/sdm_threads.png
:width: 100%

Example Thread Diagram of SDM SW PLL


.. doxygengroup:: sw_pll_sdm
:content-only:

19 changes: 11 additions & 8 deletions examples/simple_sdm/src/simple_sw_pll_sdm.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
void sdm_task(chanend_t c_sdm_control){
printf("sdm_task\n");

const uint32_t sdm_interval = 100;
const uint32_t sdm_interval = 100; // 100 * 10ns ticks = 1MHz

sw_pll_sdm_state_t sdm_state;
sw_pll_init_sigma_delta(&sdm_state);
Expand All @@ -33,7 +33,7 @@ void sdm_task(chanend_t c_sdm_control){
hwtimer_t tmr = hwtimer_alloc();
int32_t trigger_time = hwtimer_get_time(tmr) + sdm_interval;
bool running = true;
int32_t ds_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until
int32_t sdm_in = 0; // Zero is an invalid number and the SDM will not write the frac reg until
// the first control value has been received. This avoids issues with
// channel lockup if two tasks (eg. init and SDM) try to write at the same
// time.
Expand All @@ -48,7 +48,7 @@ void sdm_task(chanend_t c_sdm_control){
{
ctrl_update:
{
ds_in = chan_in_word(c_sdm_control);
sdm_in = chan_in_word(c_sdm_control);
}
break;

Expand All @@ -59,15 +59,18 @@ void sdm_task(chanend_t c_sdm_control){
break;
}

if(ds_in){
if(sdm_in){
// Wait until the timer value has been reached
// This implements a timing barrier and keeps
// the loop rate constant.
hwtimer_wait_until(tmr, trigger_time);

write_frac_reg(this_tile, frac_val);
sw_pll_write_frac_reg(this_tile, frac_val);
trigger_time += sdm_interval;

// calc new ds_out and then wait to write
int32_t ds_out = do_sigma_delta(&sdm_state, ds_in);
frac_val = ds_out_to_frac_reg(ds_out);
// calc new sdm_out and then schedule to write
int32_t sdm_out = sw_pll_do_sigma_delta(&sdm_state, sdm_in);
frac_val = sw_pll_sdm_out_to_frac_reg(sdm_out);
}
}
}
Expand Down
65 changes: 31 additions & 34 deletions lib_sw_pll/api/sw_pll.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,37 @@ void sw_pll_init( sw_pll_state_t * const sw_pll,
const unsigned nominal_lut_idx,
const unsigned ppm_range);

/**
* Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings.
*
* Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset.
*
* \param sw_pll Pointer to the struct to be initialised.
* \param Kp New Kp in sw_pll_15q16_t format.
* \param Ki New Ki in sw_pll_15q16_t format.
* \param Kii New Kii in sw_pll_15q16_t format.
* \param num_lut_entries The number of elements in the sw_pll LUT.
*/
static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries)
{
sw_pll->pi_state.Kp = Kp;
sw_pll->pi_state.Ki = Ki;
sw_pll->pi_state.Kii = Kii;

sw_pll->pi_state.error_accum = 0;
sw_pll->pi_state.error_accum_accum = 0;
if(Ki){
sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT
}else{
sw_pll->pi_state.i_windup_limit = 0;
}
if(Kii){
sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT
}else{
sw_pll->pi_state.ii_windup_limit = 0;
}
}

/**@}*/ // END: addtogroup sw_pll_general


Expand Down Expand Up @@ -123,40 +154,6 @@ sw_pll_lock_status_t sw_pll_do_control(sw_pll_state_t * const sw_pll, const uint
*/
sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error);


/**
* Helper to do a partial init of the PI controller at runtime without setting the physical PLL and LUT settings.
*
* Sets Kp, Ki and the windup limit. Note this resets the accumulator too and so state is reset.
*
* \param sw_pll Pointer to the struct to be initialised.
* \param Kp New Kp in sw_pll_15q16_t format.
* \param Ki New Ki in sw_pll_15q16_t format.
* \param Kii New Ki in sw_pll_15q16_t format.
* \param num_lut_entries The number of elements in the sw_pll LUT.
*/
static inline void sw_pll_reset(sw_pll_state_t *sw_pll, sw_pll_15q16_t Kp, sw_pll_15q16_t Ki, sw_pll_15q16_t Kii, size_t num_lut_entries)
{
sw_pll->pi_state.Kp = Kp;
sw_pll->pi_state.Ki = Ki;
sw_pll->pi_state.Kii = Kii;

sw_pll->pi_state.error_accum = 0;
sw_pll->pi_state.error_accum_accum = 0;
if(Ki){
sw_pll->pi_state.i_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Ki; // Set to twice the max total error input to LUT
}else{
sw_pll->pi_state.i_windup_limit = 0;
}
if(Kii){
sw_pll->pi_state.ii_windup_limit = (num_lut_entries << SW_PLL_NUM_FRAC_BITS) / Kii; // Set to twice the max total error input to LUT
}else{
sw_pll->pi_state.ii_windup_limit = 0;
}
}


/**@}*/ // END: addtogroup sw_pll_lut


Expand Down
16 changes: 1 addition & 15 deletions lib_sw_pll/src/sw_pll.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,7 @@ void sw_pll_init( sw_pll_state_t * const sw_pll,
__attribute__((always_inline))
inline sw_pll_lock_status_t sw_pll_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error)
{
sw_pll->pi_state.error_accum += error; // Integral error.
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;

sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error.
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;

// Use long long maths to avoid overflow if ever we had a large error accum term
int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error);
int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum);
int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum);

// Convert back to 32b since we are handling LUTs of around a hundred entries
int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS);
int32_t total_error = sw_pll_do_pi_ctrl(sw_pll, error);
sw_pll->lut_state.current_reg_val = lookup_pll_frac(sw_pll, total_error);

write_sswitch_reg_no_ack(get_local_tile_id(), XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, (0x80000000 | sw_pll->lut_state.current_reg_val));
Expand Down
24 changes: 24 additions & 0 deletions lib_sw_pll/src/sw_pll_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,27 @@ typedef struct sw_pll_state_t{
sw_pll_sdm_state_t sdm_state; // Sigma Delta Modulator base DCO

}sw_pll_state_t;


// This is the core PI controller code used by both SDM and LUT SW PLLs
__attribute__((always_inline))
inline int32_t sw_pll_do_pi_ctrl(sw_pll_state_t * const sw_pll, int16_t error)
{
sw_pll->pi_state.error_accum += error; // Integral error.
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;

sw_pll->pi_state.error_accum_accum += sw_pll->pi_state.error_accum; // Double integral error.
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum > sw_pll->pi_state.ii_windup_limit ? sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;
sw_pll->pi_state.error_accum_accum = sw_pll->pi_state.error_accum_accum < -sw_pll->pi_state.ii_windup_limit ? -sw_pll->pi_state.ii_windup_limit : sw_pll->pi_state.error_accum_accum;

// Use long long maths to avoid overflow if ever we had a large error accum term
int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error);
int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum);
int64_t error_ii = ((int64_t)sw_pll->pi_state.Kii * (int64_t)sw_pll->pi_state.error_accum_accum);

// Convert back to 32b since we are handling LUTs of around a hundred entries
int32_t total_error = (int32_t)((error_p + error_i + error_ii) >> SW_PLL_NUM_FRAC_BITS);

return total_error;
}
19 changes: 1 addition & 18 deletions lib_sw_pll/src/sw_pll_sdm.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,6 @@ void sw_pll_init_sigma_delta(sw_pll_sdm_state_t *sdm_state){
}


__attribute__((always_inline))
int32_t sw_pll_sdm_do_control_from_error(sw_pll_state_t * const sw_pll, int16_t error)
{
sw_pll->pi_state.error_accum += error; // Integral error.
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum > sw_pll->pi_state.i_windup_limit ? sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;
sw_pll->pi_state.error_accum = sw_pll->pi_state.error_accum < -sw_pll->pi_state.i_windup_limit ? -sw_pll->pi_state.i_windup_limit : sw_pll->pi_state.error_accum;

// Use long long maths to avoid overflow if ever we had a large error accum term
int64_t error_p = ((int64_t)sw_pll->pi_state.Kp * (int64_t)error);
int64_t error_i = ((int64_t)sw_pll->pi_state.Ki * (int64_t)sw_pll->pi_state.error_accum);

// Convert back to 32b since we are handling LUTs of around a hundred entries
int32_t total_error = (int32_t)((error_p + error_i) >> SW_PLL_NUM_FRAC_BITS);

return total_error;
}

__attribute__((always_inline))
int32_t sw_pll_sdm_post_control_proc(sw_pll_state_t * const sw_pll, int32_t error)
{
Expand Down Expand Up @@ -122,7 +105,7 @@ bool sw_pll_sdm_do_control(sw_pll_state_t * const sw_pll, const uint16_t mclk_pt
else
{
sw_pll_calc_error_from_port_timers(&(sw_pll->pfd_state), &(sw_pll->first_loop), mclk_pt, ref_clk_pt);
int32_t error = sw_pll_sdm_do_control_from_error(sw_pll, -sw_pll->pfd_state.mclk_diff);
int32_t error = sw_pll_do_pi_ctrl(sw_pll, -sw_pll->pfd_state.mclk_diff);
sw_pll->sdm_state.current_ctrl_val = sw_pll_sdm_post_control_proc(sw_pll, error);

// Save for next iteration to calc diff
Expand Down
Loading

0 comments on commit 54a320d

Please sign in to comment.