Merge pull request #360 from ed-xmos/feature/sw_pll

Use SD sw_pll for digital Rx on XS3 targets
This commit is contained in:
danielpieczko
2024-01-16 10:07:53 +00:00
committed by GitHub
19 changed files with 912 additions and 598 deletions

View File

@@ -13,6 +13,12 @@ HEAD
* RESOLVED: Repeated old S/PDIF and ADAT samples when entering underflow state
* CHANGED: QUAD_SPI_FLASH replaced by XUA_QUAD_SPI_FLASH (default: 1)
* CHANGED: UserBufferManagementInit() now takes a sample rate parameter
* CHANGED: xcore.ai targets use sigma-delta software PLL for clock recovery of
digital Rx streams by default.
* Changes to dependencies:
- lib_sw_pll: Added dependency 2.1.0
3.5.1
-----

3
Jenkinsfile vendored
View File

@@ -49,7 +49,8 @@ pipeline {
withVenv {
runWaf('.', "configure clean build --target=xcore200")
viewEnv() {
runPython("TARGET=XCORE200 pytest -s")
runPython("TARGET=XCORE200 pytest -s --junitxml=pytest_unity.xml")
junit "pytest_unity.xml"
}
}
}

View File

@@ -1,9 +1,11 @@
// Copyright 2017-2022 XMOS LIMITED.
// Copyright 2017-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xs1.h>
#include <platform.h>
#include "xua.h"
#include "../../shared/apppll.h"
extern "C"{
#include "sw_pll.h"
}
on tile[0]: out port p_ctrl = XS1_PORT_8D;
@@ -38,19 +40,26 @@ void AudioHwInit()
delay_milliseconds(100);
/* Use xCORE Secondary PLL to generate *fixed* master clock */
AppPllEnable_SampleRate(DEFAULT_FREQ);
if(DEFAULT_FREQ % 22050 == 0)
{
sw_pll_fixed_clock(MCLK_441);
}
else
{
sw_pll_fixed_clock(MCLK_48);
}
delay_milliseconds(100);
/* DAC setup: For basic I2S input we don't need any register setup. DACs will clock auto detect etc.
* It holds DAC in reset until it gets clocks anyway.
* Note, this example doesn't use the ADC's
* Note, this example doesn't use the ADCs
*/
}
/* Configures the external audio hardware for the required sample frequency */
void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC)
{
AppPllEnable_SampleRate(samFreq);
sw_pll_fixed_clock(mClk);
}

View File

@@ -1,9 +1,12 @@
// Copyright 2017-2022 XMOS LIMITED.
// Copyright 2017-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xs1.h>
#include <platform.h>
#include "xua.h"
#include "../../shared/apppll.h"
#include "xassert.h"
extern "C"{
#include "sw_pll.h"
}
on tile[0]: out port p_ctrl = XS1_PORT_8D;
@@ -38,19 +41,26 @@ void AudioHwInit()
delay_milliseconds(100);
/* Use xCORE Secondary PLL to generate *fixed* master clock */
AppPllEnable_SampleRate(DEFAULT_FREQ);
if(DEFAULT_FREQ % 22050 == 0)
{
sw_pll_fixed_clock(MCLK_441);
}
else
{
sw_pll_fixed_clock(MCLK_48);
}
delay_milliseconds(100);
/* DAC setup: For basic I2S input we don't need any register setup. DACs will clock auto detect etc.
* It holds DAC in reset until it gets clocks anyway.
* Note, this example doesn't use the ADC's
* Note, this example doesn't use the ADCs
*/
}
/* Configures the external audio hardware for the required sample frequency */
void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC)
{
AppPllEnable_SampleRate(samFreq);
sw_pll_fixed_clock(mClk);
}

View File

@@ -1,109 +0,0 @@
// Copyright 2022 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <stdint.h>
#include "xassert.h"
// App PLL setup
#define APP_PLL_CTL_BYPASS (0) // 0 = no bypass, 1 = bypass.
#define APP_PLL_CTL_INPUT_SEL (0) // 0 = XTAL, 1 = sysPLL
#define APP_PLL_CTL_ENABLE (1) // 0 = disabled, 1 = enabled.
// 24MHz in, 24.576MHz out, integer mode
// Found exact solution: IN 24000000.0, OUT 24576000.0, VCO 2457600000.0, RD 5, FD 512, OD 10, FOD 10
#define APP_PLL_CTL_OD_48 (4) // Output divider = (OD+1)
#define APP_PLL_CTL_F_48 (511) // FB divider = (F+1)/2
#define APP_PLL_CTL_R_48 (4) // Ref divider = (R+1)
#define APP_PLL_CTL_48 ((APP_PLL_CTL_BYPASS << 29) | (APP_PLL_CTL_INPUT_SEL << 28) | (APP_PLL_CTL_ENABLE << 27) |\
(APP_PLL_CTL_OD_48 << 23) | (APP_PLL_CTL_F_48 << 8) | APP_PLL_CTL_R_48)
// Fractional divide is M/N
#define APP_PLL_FRAC_EN_48 (0) // 0 = disabled
#define APP_PLL_FRAC_NPLUS1_CYCLES_48 (0) // M value is this reg value + 1.
#define APP_PLL_FRAC_TOTAL_CYCLES_48 (0) // N value is this reg value + 1.
#define APP_PLL_FRAC_48 ((APP_PLL_FRAC_EN_48 << 31) | (APP_PLL_FRAC_NPLUS1_CYCLES_48 << 8) | APP_PLL_FRAC_TOTAL_CYCLES_48)
// 24MHz in, 22.5792MHz out (44.1kHz * 512), frac mode
// Found exact solution: IN 24000000.0, OUT 22579200.0, VCO 2257920000.0, RD 5, FD 470.400 (m = 2, n = 5), OD 5, FOD 10
#define APP_PLL_CTL_OD_441 (4) // Output divider = (OD+1)
#define APP_PLL_CTL_F_441 (469) // FB divider = (F+1)/2
#define APP_PLL_CTL_R_441 (4) // Ref divider = (R+1)
#define APP_PLL_CTL_441 ((APP_PLL_CTL_BYPASS << 29) | (APP_PLL_CTL_INPUT_SEL << 28) | (APP_PLL_CTL_ENABLE << 27) |\
(APP_PLL_CTL_OD_441 << 23) | (APP_PLL_CTL_F_441 << 8) | APP_PLL_CTL_R_441)
#define APP_PLL_FRAC_EN_44 (1) // 1 = enabled
#define APP_PLL_FRAC_NPLUS1_CYCLES_44 (1) // M value is this reg value + 1.
#define APP_PLL_FRAC_TOTAL_CYCLES_44 (4) // N value is this reg value + 1.define APP_PLL_CTL_R_441 (4) // Ref divider = (R+1)
#define APP_PLL_FRAC_44 ((APP_PLL_FRAC_EN_44 << 31) | (APP_PLL_FRAC_NPLUS1_CYCLES_44 << 8) | APP_PLL_FRAC_TOTAL_CYCLES_44)
#define APP_PLL_DIV_INPUT_SEL (1) // 0 = sysPLL, 1 = app_PLL
#define APP_PLL_DIV_DISABLE (0) // 1 = disabled (pin connected to X1D11), 0 = enabled divider output to pin.
#define APP_PLL_DIV_VALUE (4) // Divide by N+1 - remember there's a /2 also afterwards for 50/50 duty cycle.
#define APP_PLL_DIV ((APP_PLL_DIV_INPUT_SEL << 31) | (APP_PLL_DIV_DISABLE << 16) | APP_PLL_DIV_VALUE)
/* TODO support more than two freqs..*/
void AppPllEnable(int32_t clkFreq_hz)
{
switch(clkFreq_hz)
{
case 44100*512:
// Disable the PLL
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_441 & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
// Must write the CTL register twice so that the F and R divider values are captured using a running clock.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
// Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_441 & 0xF7FFFFFF));
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_44);
break;
case 48000*512:
// Disable the PLL
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_48 & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
// Must write the CTL register twice so that the F and R divider values are captured using a running clock.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
// Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values.
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_48 & 0xF7FFFFFF));
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_48);
break;
default:
assert(0);
break;
}
// Wait for PLL output frequency to stabilise due to fractional divider enable
delay_microseconds(100);
// Turn on the clock output
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, APP_PLL_DIV);
}
void AppPllEnable_SampleRate(int32_t sampleRate_hz)
{
assert(sampleRate_hz >= 22050);
if(sampleRate_hz % 22050 == 0)
{
AppPllEnable(44100*512);
}
else
{
AppPllEnable(48000*512);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2011-2023 XMOS LIMITED.
// Copyright 2011-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef _XUA_AUDIOHUB_H_
#define _XUA_AUDIOHUB_H_
@@ -38,6 +38,8 @@
*
* \param c_dig Channel connected to the clockGen() thread for
* receiving/transmitting samples
*
* \param c_mclk_change Channel notifying clockgen of an mclk frequency change
*/
void XUA_AudioHub(chanend ?c_aud,
clock ?clk_audio_mclk,
@@ -52,6 +54,7 @@ void XUA_AudioHub(chanend ?c_aud,
#endif
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN || defined(__DOXYGEN__))
, chanend c_dig
, chanend c_mclk_change
#endif
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
, server interface i_dfu ?dfuInterface

View File

@@ -1,4 +1,4 @@
// Copyright 2011-2022 XMOS LIMITED.
// Copyright 2011-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef _CLOCKING_H_
@@ -25,8 +25,18 @@ void PllRefPinTask(server interface pll_ref_if i_pll_ref, out port p_sync);
* \param c_clk_ctl channel connected to Endpoint0() for configuration of the
* clock
* \param c_clk_int channel connected to the decouple() thread for clock
interrupts
* interrupts
* \param p_for_mclk_count_aud port used for counting mclk and providing a timestamp
*
* \param c_mclk_change channel to notify of master clock change
*/
void clockGen(streaming chanend ?c_spdif_rx, chanend ?c_adat_rx, client interface pll_ref_if i_pll_ref, chanend c_audio, chanend c_clk_ctl, chanend c_clk_int);
void clockGen( streaming chanend ?c_spdif_rx,
chanend ?c_adat_rx,
client interface pll_ref_if i_pll_ref,
chanend c_audio,
chanend c_clk_ctl,
chanend c_clk_int,
port ?p_for_mclk_count_aud,
chanend c_mclk_change);
#endif

View File

@@ -54,7 +54,9 @@ In addition :ref:`usb_audio_optional_components` shows optional components that
* - Clockgen
- Drives an external frequency generator (PLL) and manages
changes between internal clocks and external clocks arising
from digital input.
from digital input. On xcore.ai Clockgen may also work in
conjunction with lib_sw_pll to produce a local clock from
the XCORE which is locked to the incoming digital stream.
* - MIDI
- Outputs and inputs MIDI over a serial UART interface.

View File

@@ -29,10 +29,11 @@ The S/PDIF receiver should be called on the appropriate tile::
With the steps above an S/PDIF stream can be captured by the xCORE. To be functionally useful the audio
master clock must be able to synchronise to this external digital stream. Additionally, the host can be
notified regarding changes in the validity of this stream, it's frequency etc. To synchronise to external
streams the codebase assumes the use of an external Cirrus Logic CS2100 device.
streams the codebase assumes the use of an external Cirrus Logic CS2100 device or lib_sw_pll on xcore.ai designs.
The ``ClockGen()`` task from ``lib_xua`` provides the reference signal to the CS2100 device and also handles
recording of clock validity etc. See :ref:`usb_audio_sec_clock_recovery` for full details regarding ``ClockGen()``.
The ``ClockGen()`` task from ``lib_xua`` provides the reference signal to the CS2100 device or timing information
to lib_sw_pll and also handles recording of clock validity etc.
See :ref:`usb_audio_sec_clock_recovery` for full details regarding ``ClockGen()``.
It also provides a small FIFO for S/PDIF samples before they are forwarded to the ``AudioHub`` core.
As such it requires to be inserted in the communication path between the S/PDIF receiver and the

View File

@@ -52,11 +52,11 @@ Three methods of generating an audio master clock are provided on the board:
* A Skyworks Si5351B PLL device. The Si5351 is an I2C configurable clock generator that is ideally suited for replacing crystals, crystal oscillators, VCXOs, phase-locked loops (PLLs), and fanout buffers.
* xCORE.ai devices are equipped with a secondary (or 'application') PLL which can be used to generate audio clocks
* xCORE.ai devices are equipped with a secondary (or 'application') PLL which can be used to generate fixed audio clocks or recover external clocks using lib_sw_pll.
Selection between these methods is done via writing to bits 6 and 7 of PORT 8D on tile[0].
Either the locally generated clock (from the PL611) or the recovered low jitter clock (from the CS2100) may be selected to clock the audio stages; the xCORE-200, the ADC/DAC and Digital output stages. Selection is controlled via an additional I/O, bit 5 of PORT 8C, see :ref:`hw_316_ctrlport`.
Either the locally generated clock (from the PL611) or the recovered low jitter clock (from the CS2100) may be selected to clock the audio stages; the xcore.ai, the ADC/DAC and Digital output stages. Selection is controlled via an additional I/O, bit 5 of PORT 8C, see :ref:`hw_316_ctrlport`.
.. _hw_316_ctrlport:

View File

@@ -33,8 +33,8 @@ This must be a 1-bit port, for example::
<Port Location="XS1_PORT_1A" Name="PORT_SPDIF_IN"/>
When S/PDIF receive is enabled the codebase expects to drive a synchronisation signal to an external
Cirrus Logic CS2100 device for master-clock generation.
When S/PDIF receive is enabled the codebase expects to either drive a synchronisation signal to an external
Cirrus Logic CS2100 device or use lib_swp_pll (xcore.ai only) for master-clock generation.
The programmer should ensure the define in :ref:`opt_spdif_rx_ref_defines` is set appropriately.

View File

@@ -15,32 +15,35 @@ the xCORE.
Using an external PLL/Clock Multiplier allows an Asynchronous mode design to lock to an external
clock source from a digital stream (e.g. S/PDIF or ADAT input). The codebase supports the Cirrus
Logic CS2100 device for this purpose. Other devices may be supported via code modification.
Logic CS2100 device or use of lib_sw_pll (xcore.ai only) for this purpose. Other devices may be
supported via code modification.
.. note::
It is expected that in a future release the secondary PLL in xCORE.ai devices, coupled with
associated software changes, will be capable of replacing the CS2100 part for most designs.
The Clock Recovery core (Clock Gen) is responsible for either generating the reference frequency
to the CS2100 device or driving lib_sw_pll from time measurements based on the local master clock
and the time of received samples. Clock Gen (via CS2100 or lib_sw_pll) generates the master clock
used over the whole design. This core also serves as a smaller buffer between ADAT and S/PDIF
receiving cores and the Audio Hub core.
The Clock Recovery core (Clock Gen) is responsible for generating the reference frequency
to the CS2100 device. This, in turn, generates the master clock used over the whole design.
This core also serves as a smaller buffer between ADAT and S/PDIF receiving cores and the Audio Hub
core.
When using lib_sw_pll (xcore.ai only) an further core is instantiated which performs the sigma-delta
modulation of the xCORE PLL to ensure the lowest jitter over the audio band. See lib_sw_pll
documentation for further details.
When running in *Internal Clock* mode this core simply generates this clock using a local
timer, based on the XMOS reference clock.
When running in an external clock mode (i.e. S/PDIF Clock" or "ADAT Clock" mode) samples are
received from the S/PDIF and/or ADAT receive core. The external frequency is calculated through
counting samples in a given period. The reference clock to the CS2100 is then generated based on
the reception of these samples.
received from the S/PDIF and/or ADAT receive core. The external frequency is calculated through
counting samples in a given period. Either the reference clock to the CS2100 is then generated based on
the reception of these samples or the timing information is provided to lib_sw_pll to generate
the phase-locked clock on-chip (xcore.ai only).
If an external stream becomes invalid, the *Internal Clock* timer event will fire to ensure that
valid master clock generation continues regardless of cable unplugs etc. Efforts are made to
ensure the transition between these clocks are relatively seamless. Additionally efforts are also
made to try and keep the jitter on the reference clock as low as possibly, regardless of activity
made to try and keep the jitter on the reference clock as low as possible, regardless of activity
level of the Clock Gen core. The is achieved though the use of port times to schedule pin toggling
rather than directly outputting to the port.
rather than directly outputting to the port in the case of using the CS2100. For lib_sw_pll cases the
last setting is kept for the sigma-delta modulator ensuring clock continuity.
The Clock Gen core gets clock selection Get/Set commands from Endpoint 0 via the ``c_clk_ctl``
channel. This core also records the validity of external clocks, which is also queried

View File

@@ -25,7 +25,8 @@ set(LIB_DEPENDENT_MODULES "lib_locks"
"lib_spdif"
"lib_xassert"
"lib_xud"
"lib_adat")
"lib_adat"
"lib_sw_pll(develop)")
set(LIB_COMPILER_FLAGS -O3 -DREF_CLK_FREQ=100 -fasm-linenum -fcomment-asm)

View File

@@ -14,7 +14,8 @@ DEPENDENT_MODULES = lib_locks(>=2.1.0) \
lib_spdif(>=5.0.0) \
lib_xassert(>=4.1.0) \
lib_xud(>=2.2.3) \
lib_adat(>=1.0.0)
lib_adat(>=1.0.0) \
lib_sw_pll(>=2.1.0)
MODULE_XCC_FLAGS = $(XCC_FLAGS) \
-O3 \

View File

@@ -1,4 +1,4 @@
// Copyright 2011-2023 XMOS LIMITED.
// Copyright 2011-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/**
* @file xua_audiohub.xc
@@ -640,6 +640,7 @@ void XUA_AudioHub(chanend ?c_aud, clock ?clk_audio_mclk, clock ?clk_audio_bclk,
#endif
#if (XUA_ADAT_RX_EN || XUA_SPDIF_RX_EN)
, chanend c_dig_rx
, chanend c_mclk_change
#endif
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
, server interface i_dfu ?dfuInterface
@@ -800,6 +801,14 @@ void XUA_AudioHub(chanend ?c_aud, clock ?clk_audio_mclk, clock ?clk_audio_bclk,
#endif
/* Configure Clocking/CODEC/DAC/ADC for SampleFreq/MClk */
AudioHwConfig(curFreq, mClk, dsdMode, curSamRes_DAC, curSamRes_ADC);
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
/* Notify clockgen of new mCLk */
c_mclk_change <: mClk;
c_mclk_change <: curFreq;
/* Wait for ACK back from clockgen to signal clocks all good */
c_mclk_change :> int _;
#endif
}
if(!firstRun)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
// Copyright 2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef _SW_PLL_WRAPPPER_H_
#define _SW_PLL_WRAPPPER_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"
}
/* Special control value to disable SDM. Outside of normal range which is less than 16b.*/
#define DISABLE_SDM 0x10000000
/** 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 selected_mclk_rate_ptr Pointer to the mclk rate variable declared in clockgen.
*/
void SigmaDeltaTask(chanend c_sigma_delta, unsigned * unsafe selected_mclk_rate_ptr);
/** Helper function that sends a special restart command. It causes the SDM task
* to quit and restart using the new mclk.
*
* \param c_sigma_delta Channel connected to the clocking thread to pass raw error terms.
*/
void restart_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.
*
* returns The SDM update interval in ticks and the initial DCO setting for nominal frequency */
{unsigned, unsigned} InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk);
#endif /* USE_SW_PLL */
#endif /* _SW_PLL_WRAPPPER_H_ */

View File

@@ -0,0 +1,192 @@
// 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"
#include "xua.h"
#if USE_SW_PLL
{unsigned, 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);
return {XS1_TIMER_HZ / sw_pll_sdm_rate[clkIndex], sw_pll_sdm_ctrl_mid[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;
}
/* 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 * unsafe selected_mclk_rate_ptr){
/* 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. */
int f_error = 0;
int dco_setting = 0; /* gets set at InitSWPLL */
unsigned sdm_interval = 0; /* gets set at InitSWPLL */
sw_pll_state_t sw_pll;
unsafe
{
/* initialse the SDM and gather SDM initial settings */
{sdm_interval, dco_setting} = InitSWPLL(sw_pll, (unsigned)*selected_mclk_rate_ptr);
}
tileref_t this_tile = get_local_tile_id();
timer tmr;
int32_t time_trigger;
tmr :> time_trigger;
int running = 1;
outuint(c_sigma_delta, 0); /* Signal back via clockgen to audio to start I2S */
unsigned rx_word = 0;
while(running)
{
/* Poll for new SDM control value */
select
{
case inuint_byref(c_sigma_delta, rx_word):
if(rx_word == DISABLE_SDM)
{
f_error = 0;
running = 0;
}
else
{
f_error = (int32_t)rx_word;
unsafe
{
sw_pll_sdm_do_control_from_error(&sw_pll, -f_error);
dco_setting = sw_pll.sdm_state.current_ctrl_val;
}
}
break;
/* Do nothing & fall-through. Above case polls only once per loop */
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;
}
unsafe {
sw_pll_do_sigma_delta(&sw_pll.sdm_state, this_tile, dco_setting);
}
} /* if running */
}
void restart_sigma_delta(chanend c_sigma_delta)
{
outuint(c_sigma_delta, DISABLE_SDM); /* Resets SDM */
}
#endif /* USE_SW_PLL */

View File

@@ -1,4 +1,4 @@
// Copyright 2012-2023 XMOS LIMITED.
// Copyright 2012-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "xua.h" /* Device specific defines */
@@ -144,6 +144,11 @@ on tile[XUD_TILE] : in port p_spdif_rx = PORT_SPDIF_IN;
#if (XUA_SPDIF_RX_EN) || (XUA_ADAT_RX_EN) || (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
/* Reference to external clock multiplier */
on tile[PLL_REF_TILE] : out port p_pll_ref = PORT_PLL_REF;
#ifdef __XS3A__
on tile[AUDIO_IO_TILE] : port p_for_mclk_count_audio = PORT_MCLK_COUNT_2;
#else /* __XS3A__ */
#define p_for_mclk_count_audio null
#endif /* __XS3A__ */
#endif
#ifdef MIDI
@@ -309,6 +314,7 @@ void usb_audio_io(chanend ?c_aud_in,
#endif
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
, client interface pll_ref_if i_pll_ref
, port ?p_for_mclk_count_aud
#endif
)
{
@@ -318,9 +324,16 @@ void usb_audio_io(chanend ?c_aud_in,
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
chan c_dig_rx;
#else
#define c_dig_rx null
#endif
chan c_mclk_change; /* Notification of new mclk freq to clockgen */
/* Connect p_for_mclk_count_aud to clk_audio_mclk so we can count mclks/timestamp in digital rx*/
if(!isnull(p_for_mclk_count_aud))
{
unsigned x = 0;
asm("ldw %0, dp[clk_audio_mclk]":"=r"(x));
asm("setclk res[%0], %1"::"r"(p_for_mclk_count_aud), "r"(x));
}
#endif /* (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) */
#if (XUA_NUM_PDM_MICS > 0) && (PDM_TILE == AUDIO_IO_TILE)
/* Configure clocks ports - sharing mclk port with I2S */
@@ -365,6 +378,7 @@ void usb_audio_io(chanend ?c_aud_in,
#endif
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
, c_dig_rx
, c_mclk_change
#endif
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
, dfuInterface
@@ -385,12 +399,18 @@ void usb_audio_io(chanend ?c_aud_in,
* However, due to the use of an interface the pll reference signal port can be on another tile
*/
thread_speed();
clockGen(c_spdif_rx, c_adat_rx, i_pll_ref, c_dig_rx, c_clk_ctl, c_clk_int);
clockGen( c_spdif_rx,
c_adat_rx,
i_pll_ref,
c_dig_rx,
c_clk_ctl,
c_clk_int,
p_for_mclk_count_aud,
c_mclk_change);
}
#endif
//:
}
} // par
}
#ifndef USER_MAIN_DECLARATIONS
@@ -437,7 +457,7 @@ int main()
#define c_adat_rx null
#endif
#if (XUA_SPDIF_TX_EN) //&& (SPDIF_TX_TILE != AUDIO_IO_TILE)
#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
chan c_spdif_tx;
#endif
@@ -575,6 +595,7 @@ int main()
on tile[AUDIO_IO_TILE]:
{
/* Audio I/O task, includes mixing etc */
usb_audio_io(c_mix_out
#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
@@ -595,6 +616,7 @@ int main()
#endif
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
, i_pll_ref
, p_for_mclk_count_audio
#endif
);
}