diff --git a/.gitignore b/.gitignore index d23fc84..07cf174 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ tests/trace.txt # editor **/.vscode */trace.gtkw +tests/*.gtkw diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7661222..a32d09f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,11 +1,13 @@ lib_sw_dac change log ===================== -0.2.0 +1.0.0 ----- - * ADDED: Support for 44.1, 88.2 and 176.4 kHz sample rates - * RESOLVED: Timing test now uses true 24 MHz clock + * First public release + * ADDED: 44.1, 88.2, 176.4 kHz support + * FIXED: PWM outputs zero level when not fed with samples + * FIXED: PWM left and right outputs remain synchronised 0.1.0 ----- diff --git a/Jenkinsfile b/Jenkinsfile index 97dc7e7..5a3ab5e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ // This file relates to internal XMOS infrastructure and should be ignored by external users -@Library('xmos_jenkins_shared_library@v0.42.0') _ +@Library('xmos_jenkins_shared_library@v0.43.3') _ getApproval() pipeline { @@ -20,7 +20,7 @@ pipeline { ) string( name: 'INFR_APPS_VERSION', - defaultValue: 'v3.2.1', + defaultValue: 'v3.2.0', description: 'The infr_apps version' ) } diff --git a/README.rst b/README.rst index 4526f2f..ef671fe 100644 --- a/README.rst +++ b/README.rst @@ -5,35 +5,31 @@ lib_sw_dac: Software DAC ######################## :vendor: XMOS -:version: 0.2.0 -:scope: Demo +:version: 1.0.0 +:scope: General Use :description: Software DAC for xcore.ai :category: Audio -:keywords: DAC, DSP +:keywords: DAC :devices: xcore.ai ******* Summary ******* -Software DAC for xcore comprising of upsampling, sigma-delta modulator and PWM generation. +Software DAC for ``xcore.ai`` comprising of upsampling, sigma-delta modulator and PWM generation. ******** Features ******** -* Supports 48000, 96000 and 192000 Hz sample rate, stereo output -* Uses two hardware threads, two one-bit ports, one clock-block and 32 kB of RAM +* Supports 44100, 48000, 88200, 96000 and 176400 & 192000 Hz sample rate, stereo output +* Uses two hardware threads, two one-bit ports, one timer, one clock-block and 32 kB of RAM ************ Known issues ************ * Fixed configuration only currently (SF = Standard Fidelity) -* The API requires that the software DAC is continually fed samples at the input - sample rate. Failure to do so will result in full scale outputs glitches. Please - ensure your application disables the hardware output stage before stopping - feeding samples. **************** Development repo diff --git a/doc/rst/lib_sw_dac.rst b/doc/rst/lib_sw_dac.rst index 5ad1172..de238c8 100644 --- a/doc/rst/lib_sw_dac.rst +++ b/doc/rst/lib_sw_dac.rst @@ -104,11 +104,6 @@ latency) as needed. The number of filters can be changed too, although for pragm reasons it is good to start with 2x upsamplers as that creates natural input points for 96 and 192 kHz sample rates if multiple input sample rate is required. -In order to support the 44,100 family, one can either support: - -* A dual master clock configuration. -* A fractional sample rate converter at the input stage. -* A design the final filters to perform the fractional conversion at that stage. PWM pulse rate and levels ------------------------- diff --git a/examples/app_sf_sine/src/main.c b/examples/app_sf_sine/src/main.c index c5049ad..899b368 100644 --- a/examples/app_sf_sine/src/main.c +++ b/examples/app_sf_sine/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/examples/app_sf_sine/src/sw_dac_conf.h b/examples/app_sf_sine/src/sw_dac_conf.h index f616cc2..94074ae 100644 --- a/examples/app_sf_sine/src/sw_dac_conf.h +++ b/examples/app_sf_sine/src/sw_dac_conf.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. diff --git a/examples/app_sf_sine/src/toplevel.xc b/examples/app_sf_sine/src/toplevel.xc index 8e541e3..71c9b0f 100644 --- a/examples/app_sf_sine/src/toplevel.xc +++ b/examples/app_sf_sine/src/toplevel.xc @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/lib_sw_dac/api/sw_dac.h b/lib_sw_dac/api/sw_dac.h index 97303a0..5abc66c 100644 --- a/lib_sw_dac/api/sw_dac.h +++ b/lib_sw_dac/api/sw_dac.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "sw_dac_conf_default.h" diff --git a/lib_sw_dac/api/sw_dac_conf_default.h b/lib_sw_dac/api/sw_dac_conf_default.h index 14c29d4..bea8c37 100644 --- a/lib_sw_dac/api/sw_dac_conf_default.h +++ b/lib_sw_dac/api/sw_dac_conf_default.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #if !defined(__sw_dac_config_h__) #define __sw_dac_config_h__ diff --git a/lib_sw_dac/lib_build_info.cmake b/lib_sw_dac/lib_build_info.cmake index 0f63fd9..d903667 100644 --- a/lib_sw_dac/lib_build_info.cmake +++ b/lib_sw_dac/lib_build_info.cmake @@ -1,6 +1,6 @@ set(LIB_NAME lib_sw_dac) -set(LIB_VERSION 0.2.0) +set(LIB_VERSION 1.0.0) set(LIB_INCLUDES api src diff --git a/lib_sw_dac/src/filters/filter_banks.c b/lib_sw_dac/src/filters/filter_banks.c index 2622f05..bdf38e7 100644 --- a/lib_sw_dac/src/filters/filter_banks.c +++ b/lib_sw_dac/src/filters/filter_banks.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "filter_banks.h" diff --git a/lib_sw_dac/src/filters/filter_banks.h b/lib_sw_dac/src/filters/filter_banks.h index bdd7eaf..98fc0ed 100644 --- a/lib_sw_dac/src/filters/filter_banks.h +++ b/lib_sw_dac/src/filters/filter_banks.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _filter_banks_h_ #define _filter_banks_h_ diff --git a/lib_sw_dac/src/filters/filter_x125_64_i4_o8_n16.S b/lib_sw_dac/src/filters/filter_x125_64_i4_o8_n16.S index 1c2bc90..830013d 100644 --- a/lib_sw_dac/src/filters/filter_x125_64_i4_o8_n16.S +++ b/lib_sw_dac/src/filters/filter_x125_64_i4_o8_n16.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.S b/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.S index 16713c1..318d26d 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.S +++ b/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.h b/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.h index a9f4c2b..b91c546 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.h +++ b/lib_sw_dac/src/filters/filter_x250_147_i10_o17_n2000.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.S b/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.S index 5823d78..ce9bf3a 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.S +++ b/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.h b/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.h index 88f857a..89d80b1 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.h +++ b/lib_sw_dac/src/filters/filter_x250_147_i20_o34_n2000.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.S b/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.S index e515672..5614026 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.S +++ b/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.h b/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.h index 73beb27..e8cb33b 100644 --- a/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.h +++ b/lib_sw_dac/src/filters/filter_x250_147_i5_o8_n2000.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // DO NOT EDIT, generated by lib_sigma_delta/host/generate_44100_assembly_and_tables.py lib_sigma_delta_git_directory diff --git a/lib_sw_dac/src/filters/filter_x2_i1_o2_n16.S b/lib_sw_dac/src/filters/filter_x2_i1_o2_n16.S index 0465407..51b6c6c 100644 --- a/lib_sw_dac/src/filters/filter_x2_i1_o2_n16.S +++ b/lib_sw_dac/src/filters/filter_x2_i1_o2_n16.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i1_o2_n32.S b/lib_sw_dac/src/filters/filter_x2_i1_o2_n32.S index c4c54c6..32dc84d 100644 --- a/lib_sw_dac/src/filters/filter_x2_i1_o2_n32.S +++ b/lib_sw_dac/src/filters/filter_x2_i1_o2_n32.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i1_o2_n80.S b/lib_sw_dac/src/filters/filter_x2_i1_o2_n80.S index 7bd10de..3721b64 100644 --- a/lib_sw_dac/src/filters/filter_x2_i1_o2_n80.S +++ b/lib_sw_dac/src/filters/filter_x2_i1_o2_n80.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i1_o2_n80_bespoke_abi.S b/lib_sw_dac/src/filters/filter_x2_i1_o2_n80_bespoke_abi.S index f5974bb..6a27984 100644 --- a/lib_sw_dac/src/filters/filter_x2_i1_o2_n80_bespoke_abi.S +++ b/lib_sw_dac/src/filters/filter_x2_i1_o2_n80_bespoke_abi.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i2_o4_n16.S b/lib_sw_dac/src/filters/filter_x2_i2_o4_n16.S index 8ab3ccb..e3bcb49 100644 --- a/lib_sw_dac/src/filters/filter_x2_i2_o4_n16.S +++ b/lib_sw_dac/src/filters/filter_x2_i2_o4_n16.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i2_o4_n32.S b/lib_sw_dac/src/filters/filter_x2_i2_o4_n32.S index 387a990..baef0d1 100644 --- a/lib_sw_dac/src/filters/filter_x2_i2_o4_n32.S +++ b/lib_sw_dac/src/filters/filter_x2_i2_o4_n32.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i2_o4_n32_bespoke_abi.S b/lib_sw_dac/src/filters/filter_x2_i2_o4_n32_bespoke_abi.S index 9118983..fe08489 100644 --- a/lib_sw_dac/src/filters/filter_x2_i2_o4_n32_bespoke_abi.S +++ b/lib_sw_dac/src/filters/filter_x2_i2_o4_n32_bespoke_abi.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i4_o8_n16.S b/lib_sw_dac/src/filters/filter_x2_i4_o8_n16.S index b2cd392..49aa66e 100644 --- a/lib_sw_dac/src/filters/filter_x2_i4_o8_n16.S +++ b/lib_sw_dac/src/filters/filter_x2_i4_o8_n16.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i4_o8_n16_bespoke_abi.S b/lib_sw_dac/src/filters/filter_x2_i4_o8_n16_bespoke_abi.S index 7d8e418..7b467a5 100644 --- a/lib_sw_dac/src/filters/filter_x2_i4_o8_n16_bespoke_abi.S +++ b/lib_sw_dac/src/filters/filter_x2_i4_o8_n16_bespoke_abi.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i8_o16_n16.S b/lib_sw_dac/src/filters/filter_x2_i8_o16_n16.S index a943546..4c1e4f3 100644 --- a/lib_sw_dac/src/filters/filter_x2_i8_o16_n16.S +++ b/lib_sw_dac/src/filters/filter_x2_i8_o16_n16.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x2_i8_o16_n16_bespoke_abi.S b/lib_sw_dac/src/filters/filter_x2_i8_o16_n16_bespoke_abi.S index cb06aa2..04031d4 100644 --- a/lib_sw_dac/src/filters/filter_x2_i8_o16_n16_bespoke_abi.S +++ b/lib_sw_dac/src/filters/filter_x2_i8_o16_n16_bespoke_abi.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/filters/filter_x5_2_n40.S b/lib_sw_dac/src/filters/filter_x5_2_n40.S index 7ebd460..2856b81 100644 --- a/lib_sw_dac/src/filters/filter_x5_2_n40.S +++ b/lib_sw_dac/src/filters/filter_x5_2_n40.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/pre_distort.S b/lib_sw_dac/src/pre_distort.S index 89a80e9..9a1e6e6 100644 --- a/lib_sw_dac/src/pre_distort.S +++ b/lib_sw_dac/src/pre_distort.S @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .issue_mode dual diff --git a/lib_sw_dac/src/pre_distort.h b/lib_sw_dac/src/pre_distort.h index e923927..5c03396 100644 --- a/lib_sw_dac/src/pre_distort.h +++ b/lib_sw_dac/src/pre_distort.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /** diff --git a/lib_sw_dac/src/sigma_delta_modulators.c b/lib_sw_dac/src/sigma_delta_modulators.c index 44cdfac..f97a857 100644 --- a/lib_sw_dac/src/sigma_delta_modulators.c +++ b/lib_sw_dac/src/sigma_delta_modulators.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "sigma_delta_modulators.h" diff --git a/lib_sw_dac/src/sigma_delta_modulators.h b/lib_sw_dac/src/sigma_delta_modulators.h index 626927e..90b6ebb 100644 --- a/lib_sw_dac/src/sigma_delta_modulators.h +++ b/lib_sw_dac/src/sigma_delta_modulators.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _sigma_delta_modulators_h_ #define _sigma_delta_modulators_h_ diff --git a/lib_sw_dac/src/standard_fidelity/run_sf.S b/lib_sw_dac/src/standard_fidelity/run_sf.S index e02d68c..f71cbae 100644 --- a/lib_sw_dac/src/standard_fidelity/run_sf.S +++ b/lib_sw_dac/src/standard_fidelity/run_sf.S @@ -1,4 +1,4 @@ -// Copyright 2024-2026 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -6,7 +6,7 @@ #include "sdac_sf.h" -#define NSTACKWORDS 18 +#define NSTACKWORDS 20 #define FUNCTION_NAME sigma_delta_1_5 .cc_top FUNCTION_NAME.function @@ -31,8 +31,19 @@ FUNCTION_NAME: stw r10, sp[6] stw dp, sp[7] - // Initialise registers for sigma_delta_1_5 + // Now store select info for the timeout and copy to stack for easier access + stw r0, sp[SDAC_STACK_SD_STRUCT_BASE] // Store sd struct addr + ldw r4, r0[SDAC_TIMEOUT_PERIOD] + stw r4, sp[SDAC_STACK_TIMEOUT_PERIOD] + ldw r4, r0[SDAC_TIMEOUT_RESID] + stw r4, sp[SDAC_STACK_TIMEOUT_RESID] + ldw r4, r0[SDAC_TIMEOUT_WORD] + stw r4, sp[SDAC_STACK_TIMEOUT_WORD] + ldw r4, r0[SDAC_CLOCK_BLOCK] + stw r4, sp[SDAC_STACK_TIMEOUT_CLKBLK] + + // Initialise registers for sigma_delta_1_5 ldw r10, r0[SDAC_PWM_LOOKUP] ldw r4, r0[SDAC_SD_COEFFS] ldw r5, r0[SDAC_SD_STATE_L] @@ -41,9 +52,10 @@ FUNCTION_NAME: ldw r7, r0[SDAC_OUT_PORTS_L] ldw r8, r0[SDAC_OUT_PORTS_R] - // Fill the ports with data - out res[r7], r7 - out res[r8], r7 + // Fill the ports with data, but clock not started yet + ldw r11, r0[SDAC_TIMEOUT_WORD] + out res[r7], r11 + out res[r8], r11 // Now start the clock block, it is on already and linked to the ports ldw r11, r0[SDAC_CLOCK_BLOCK] @@ -52,7 +64,7 @@ FUNCTION_NAME: // Finally initialise channel register for sigma_delta add r0, r1, 0 - bl sigma_delta_1_5_bespoke_abi + bl sigma_delta_1_5_bespoke_abi .exit2: ldd r4, r5, sp[0] diff --git a/lib_sw_dac/src/standard_fidelity/sdac_sf.h b/lib_sw_dac/src/standard_fidelity/sdac_sf.h index b5e49f6..323b9ef 100644 --- a/lib_sw_dac/src/standard_fidelity/sdac_sf.h +++ b/lib_sw_dac/src/standard_fidelity/sdac_sf.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // These are offsets to the sw_dac_sf_t struct @@ -16,6 +16,10 @@ #define SDAC_OUT_PORTS_L 5 // Two ports #define SDAC_OUT_PORTS_R 6 +#define SDAC_TIMEOUT_PERIOD 7 // Used for safe timeout in SD/PWM +#define SDAC_TIMEOUT_WORD 8 // Used for safe timeout in SD/PWM +#define SDAC_TIMEOUT_RESID 9 // Used for safe timeout in SD/PWM +#define SDAC_TIMEOUT_OCCURED 10 // Used for safe timeout in SD/PWM // These are offsets to the array exchanged between // the filter thread the sigma-delta thread @@ -27,3 +31,11 @@ #define SDAC_BUF_R (SDAC_BUF_L + SDAC_FILTER_MAX) #define SDAC_BUF_STEP 1 #define SDAC_BUF_TOTAL (SDAC_BUF_R + SDAC_FILTER_MAX) + +// These are stack offsets for storing information for the select and timeout in SD +// DO NOT MODIFY! +#define SDAC_STACK_SD_STRUCT_BASE 8 +#define SDAC_STACK_TIMEOUT_PERIOD 9 +#define SDAC_STACK_TIMEOUT_WORD 10 +#define SDAC_STACK_TIMEOUT_RESID 11 +#define SDAC_STACK_TIMEOUT_CLKBLK 12 diff --git a/lib_sw_dac/src/standard_fidelity/sigma_delta_1_5.S b/lib_sw_dac/src/standard_fidelity/sigma_delta_1_5.S index dc82397..f788270 100644 --- a/lib_sw_dac/src/standard_fidelity/sigma_delta_1_5.S +++ b/lib_sw_dac/src/standard_fidelity/sigma_delta_1_5.S @@ -1,7 +1,8 @@ -// Copyright 2024-2026 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include #include "sdac_sf.h" - + #define FUNCTION_NAME sigma_delta_1_5_bespoke_abi #define FRACTIONAL_BITS 28 // TODO: modified software_dac_sf.c scale2 in sync @@ -11,7 +12,6 @@ .issue_mode dual // Used in loop r0, dp, r9 - // Not in use r2 // Scratch registers: r11 r6 // Channel independent // PWM table r10 @@ -27,14 +27,31 @@ .linkset FUNCTION_NAME.nstackwords, 0 .align 16 FUNCTION_NAME: - { ldc r11, 0 ; nop } - { in r11, res[r0] ; vsetc r11 } +// Init VPU and the select statement + { ldw r6, sp[SDAC_STACK_TIMEOUT_RESID]; ldc r11, 0 } + { vsetc r11 ; ldap r11, .sd_timeout } + + { setv res[r6], r11 ; ldap r11, .input_handler} + eeu res[r6]; // enable + + setv res[r0], r11; + eeu res[r0]; // enable + + waiteu; // This does the initial IN or timeout + +// Main loop .Louter_loop: - { set dp, r11 ; nop } - { ldw r9, dp[SDAC_BUF_N] ; nop } - nop +.input_handler: + // This section inits the DP, loads nsamp and moves on the timeout timer according to nsamp + // We have r11, r6 and also r1 is available in this stage until the inner loop + { in r11, res[r0] ; ldw r1, sp[SDAC_STACK_TIMEOUT_PERIOD]} // Grab input data pointer from filter + load timeout time period + { ldw r9, r11[SDAC_BUF_N] ; gettime r6} // Get number of samples, Read timer value (we know all timers are same val) + mul r1, r1, r9 // mul timeout time by num samples to receive (inner loop count) + { ldw r6, sp[SDAC_STACK_TIMEOUT_RESID] ; add r1, r1, r6;} // Copy input token (sf ptr) to DP for later use & move on timer time + { set dp, r11 ; setd res[r6], r1} // Move time trigger forwards, and init the DP for later use + .Linner_loop: - { ldw r1, dp[SDAC_BUF_L] ; sub r9, r9, 1 } + { ldw r1, dp[SDAC_BUF_L] ; sub r9, r9, 1 } // Left channel: { stw r1, r5[0] ; ldc r11, 31 } // Store input value into state vector @@ -42,7 +59,7 @@ FUNCTION_NAME: { vclrdr ; nop } { ldw r1, r11[r1] ; ldc r11, FRACTIONAL_BITS} // clip value to [-5..5] and quantise ashr r6, r1, r11 // Make into a fixed point value and sign extend for PWM - { stw r1, r5[1] ; ldc r1, 8 } // Store feedback + { stw r1, r5[1] ; ldc r1, 8 } // Store feedback { vldc r5[0] ; add r1, r5, r1 } { vlmaccr r4[0] ; add r4, r4, r3 } { vlmaccr r4[0] ; add r4, r4, r3 } @@ -53,7 +70,7 @@ FUNCTION_NAME: { vlsat r11[0] ; shl r11, r3, 2 } { vstr r1[0] ; sub r4, r4, r11 } { ldw r1, r10[r6] ; sub r4, r4, r3 } -#if defined(SW_DAC_SD_TEST_MODE) +#if SW_DAC_SD_TEST_MODE out res[r7], r1 #else outpw res[r7], r1, 16 @@ -77,14 +94,50 @@ FUNCTION_NAME: { vlsat r11[0] ; shl r11, r3, 2 } { vstr r1[0] ; sub r4, r4, r11 } { ldw r1, r10[r6] ; sub r4, r4, r3 } -#if defined(SW_DAC_SD_TEST_MODE) +#if SW_DAC_SD_TEST_MODE out res[r8], r1 #else outpw res[r8], r1, 16 #endif { bt r9, .Linner_loop ; ldaw dp, dp[SDAC_BUF_STEP] } - { bu .Louter_loop ; in r11, res[r0] } + waiteu + + // Select handler for timeout. This does not need to speed optimised + .align 8 +.sd_timeout: + // Get the out dummy word, output it and then invert it to avoid DC output content + ldw r11, sp[SDAC_STACK_TIMEOUT_WORD] + ldw r6, sp[SDAC_STACK_TIMEOUT_CLKBLK] +#if SW_DAC_SD_TEST_MODE + out res[r7], r11 + out res[r8], r11 +#else + // Note this isn't perfect - the port buffer will empty before next timeout + // However since this is a bad situation anyway (output should have been muted if expected) + // and the timeout word is inverted so we will have continuous activity on the port and near 50% duty + // so it will avert issues where the SD is not fed which result in toasting the amplified + syncr res[r7] // Ensure we have space in port buffers + syncr res[r8] + setc res[r6], XS1_SETC_RUN_STOPR // Stop clock so we can pre-load both ports + out res[r7], r11 + out res[r8], r11 + setc res[r6], XS1_SETC_RUN_STARTR // Start the clock outputs will be sync'ed until empty +#endif + not r11, r11; + stw r11, sp[SDAC_STACK_TIMEOUT_WORD]; + + + // Move the timeout timer on by at least one PWM period. We may come back here if no IN from filter + { gettime r11 ; ldw r6, sp[SDAC_STACK_TIMEOUT_PERIOD]} + { add r11, r11, r6 ; ldw r6, sp[SDAC_STACK_TIMEOUT_RESID]} + { setd res[r6], r11 ; ldc r11, 0x1;} // Move time trigger forwards and load timeout flag + + // Set timeout occurred flag in struct + ldw r6, sp[SDAC_STACK_SD_STRUCT_BASE] ; + {stw r11, r6[SDAC_TIMEOUT_OCCURED] ; waiteu} + + .align 4 #define F(x) ((x)<<(FRACTIONAL_BITS - 1)) diff --git a/lib_sw_dac/src/standard_fidelity/software_dac_sf.c b/lib_sw_dac/src/standard_fidelity/software_dac_sf.c index c219166..1891027 100644 --- a/lib_sw_dac/src/standard_fidelity/software_dac_sf.c +++ b/lib_sw_dac/src/standard_fidelity/software_dac_sf.c @@ -1,4 +1,4 @@ -// Copyright 2024-2026 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -14,6 +14,8 @@ #include "sw_dac_conf_default.h" #include "pre_distort.h" +// Assembly kernel that runs the sigma-delta. +void sigma_delta_1_5(sw_dac_sf_t *sd, chanend_t ce); // does not return static void init_ports(port_t dac_ports[2], xclock_t clk) { for(int i = 0; i < 2; i++) { @@ -38,9 +40,14 @@ void sw_dac_sf_init(sw_dac_sf_t *sd, float p_x2, float p_x3) { memset(sd, 0, sizeof(*sd)); -#if !defined(SW_DAC_SD_TEST_MODE) +#if !SW_DAC_SD_TEST_MODE init_ports(dac_ports, clk); #endif + sd->timeout_period = 70; // at 1.5MHz this will be 66.666 so set slightly above + sd->timeout_word = 0x0ff00ff0; // 50% duty cycle 16b word normally but full 32b version here for startup + sd->timeout_resid = hwtimer_alloc(); + sd->timeout_occurred = 0; + sd->clock_block = clk; memcpy(&sd->out_ports[0], dac_ports, 2 * sizeof(port_t)); sd->sd_coeffs = &sd_coeffs[0][0]; @@ -99,7 +106,7 @@ void sw_dac_sf_init(sw_dac_sf_t *sd, uint64_t mask_reversed = mask << (2*pwm_max-(i+pwm_max)); first_64 = mask_reversed | (1 << (2*pwm_max)) | (mask << (1 + 2*pwm_max)); sd->pwm_lookup[i] = first_64; -#if defined(SW_DAC_SD_TEST_MODE) +#if SW_DAC_SD_TEST_MODE printf("PWM %d:0x%08x\n", i, (int) sd->pwm_lookup[i]); #endif } @@ -111,15 +118,15 @@ void sw_dac_sf_init(sw_dac_sf_t *sd, uint64_t mask_reversed = mask << (2*pwm_max-(i+pwm_max)); first_64 = mask_reversed | (3 << (2*pwm_max)) | (mask << (2 + 2*pwm_max)); sd->pwm_lookup[i] = first_64; -#if defined(SW_DAC_SD_TEST_MODE) +#if SW_DAC_SD_TEST_MODE printf("PWM %d:0x%08x\n", i, (int) sd->pwm_lookup[i]); #endif } } } -DECLARE_JOB(filter_task, (sw_dac_sf_t *, chanend_t, chanend_t)); -DECLARE_JOB(sigma_delta_task, (sw_dac_sf_t *, chanend_t)); +DECLARE_JOB(filter_task, (sw_dac_sf_t *, chanend_t, chanend_t)); +DECLARE_JOB(sigma_delta_task_sf, (sw_dac_sf_t *, chanend_t)); static inline int filter_x125_64_i16_o32_n16_phased(sw_dac_sf_t *sd, int32_t *output, int32_t samples[16]) { switch(sd->bank) { @@ -194,6 +201,7 @@ static inline int filter_x125_64_i8_o16_n16_phased(sw_dac_sf_t *sd, int32_t *out struct filter_x125_64_phases { __attribute__(( fptrgroup("filter_x125_64") )) int(*filter_function)(int32_t *, int32_t *, int32_t *); int32_t *coeffs; + int n; }; struct filter_x125_64_phases filter_x125_64_i4_phases[16] = { @@ -487,7 +495,9 @@ void filter_task(sw_dac_sf_t *sd, chanend_t c_in, chanend_t c_out) { chanend_out_control_token(c_out, 1); } -void sigma_delta_task(sw_dac_sf_t *sd, chanend_t c_in) { +void sigma_delta_task_sf(sw_dac_sf_t *sd, chanend_t c_in) { + hwtimer_set_trigger_time(sd->timeout_resid, hwtimer_get_time(sd->timeout_resid) + sd->timeout_period + 500); // Allow some extra cycles for entry + sigma_delta_1_5(sd, c_in); } @@ -495,7 +505,7 @@ void sw_dac_sf(sw_dac_sf_t *sd, chanend_t c_in) { channel_t c = chan_alloc(); PAR_JOBS( - PJOB(filter_task, (sd, c_in, c.end_a)), - PJOB(sigma_delta_task, (sd, c.end_b)) + PJOB(filter_task, (sd, c_in, c.end_a)), + PJOB(sigma_delta_task_sf, (sd, c.end_b)) ); } diff --git a/lib_sw_dac/src/standard_fidelity/software_dac_sf.h b/lib_sw_dac/src/standard_fidelity/software_dac_sf.h index 8682aed..322d2cb 100644 --- a/lib_sw_dac/src/standard_fidelity/software_dac_sf.h +++ b/lib_sw_dac/src/standard_fidelity/software_dac_sf.h @@ -1,10 +1,11 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include #include #include +#include #include #ifndef __software_dac_sf_h__ @@ -31,6 +32,10 @@ typedef struct { xclock_t clock_block; int *sd_coeffs; port_t out_ports[CHANNELS]; + int timeout_period; + int timeout_word; + hwtimer_t timeout_resid; + int timeout_occurred; // Upsampling filters int32_t *filter0[CHANNELS]; @@ -165,9 +170,6 @@ void sw_dac_sf(sw_dac_sf_t *sd, chanend_t ce); #else DECLARE_JOB(sw_dac_sf, (sw_dac_sf_t *, chanend_t)); // Allow calling from PAR_JOB #endif -/** - * Assembly kernel that runs the sigma-delta. - */ -void sigma_delta_1_5(sw_dac_sf_t *sd, chanend_t ce); // does not return + #endif diff --git a/lib_sw_dac/src/watchdog_timer.c b/lib_sw_dac/src/watchdog_timer.c index bb966a5..eafafa3 100644 --- a/lib_sw_dac/src/watchdog_timer.c +++ b/lib_sw_dac/src/watchdog_timer.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/lib_sw_dac/src/watchdog_timer.h b/lib_sw_dac/src/watchdog_timer.h index 1df22c8..a7e788c 100644 --- a/lib_sw_dac/src/watchdog_timer.h +++ b/lib_sw_dac/src/watchdog_timer.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __watchdog_timer__ diff --git a/python/filter_44100.py b/python/filter_44100.py index 94865c1..ede9d80 100644 --- a/python/filter_44100.py +++ b/python/filter_44100.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. the_filter = [0, diff --git a/python/generate_44100_assembly_and_tables.py b/python/generate_44100_assembly_and_tables.py index 3edd442..939c535 100644 --- a/python/generate_44100_assembly_and_tables.py +++ b/python/generate_44100_assembly_and_tables.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import numpy as np diff --git a/settings.yml b/settings.yml index 725c6e2..bc71031 100644 --- a/settings.yml +++ b/settings.yml @@ -3,7 +3,7 @@ lib_name: lib_sw_dac project: '{{lib_name}}' title: '{{lib_name}}: Software DAC' -version: 0.2.0 +version: 1.0.0 documentation: exclude_patterns_path: doc/exclude_patterns.inc diff --git a/tests/common/sin1500.h b/tests/common/sin1500.h index 8bff816..67c73e6 100644 --- a/tests/common/sin1500.h +++ b/tests/common/sin1500.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. int sin1500[1500] = { 0, diff --git a/tests/common/sin2000.h b/tests/common/sin2000.h index 4d51dcf..7b82141 100644 --- a/tests/common/sin2000.h +++ b/tests/common/sin2000.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. int sin2000[2000] = { 0, diff --git a/tests/common/sin48.h b/tests/common/sin48.h index e10467c..4b4b745 100644 --- a/tests/common/sin48.h +++ b/tests/common/sin48.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. int sin48[48] = { 0, diff --git a/tests/common/sin768.h b/tests/common/sin768.h index 6c72d46..a33bebd 100644 --- a/tests/common/sin768.h +++ b/tests/common/sin768.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. int sin768[768] = { 0, diff --git a/tests/conftest.py b/tests/conftest.py index 7c54b8b..dde5eab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import pytest diff --git a/tests/helpers.py b/tests/helpers.py index 08381f7..06b08f0 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/requirements.txt b/tests/requirements.txt index 3171d69..9afcf8e 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -19,8 +19,8 @@ # same modules should appear in the setup.py list as given below. pytest==8.3.3 pytest-xdist==3.6.1 +pytest-timeout==2.4.0 filelock==3.19.1 -numpy==2.3.2 scipy==1.16.1 # Development dependencies @@ -36,4 +36,6 @@ scipy==1.16.1 # of its own setup.py file, then this list must include an entry for that # setup.py file, e.g., '-e .' or '-e ./python' (without the quotes). --e git+ssh://git@github.com/xmos/test_support.git@v2.0.0#egg=test_support \ No newline at end of file +-e git+ssh://git@github.com/xmos/test_support.git@v2.0.0#egg=test_support +# Head of develop as of 14th nov with THDN script just merged. This can be moved on when released +-e git+ssh://git@github.com/xmos/audio_test_tools.git@ab4d121e0b19fb6fbef6ad88a532afc152398f31#egg=audio_test_tools&subdirectory=python \ No newline at end of file diff --git a/tests/test_filter_thdn.py b/tests/test_filter_thdn.py index 16e21a5..44b5dc3 100644 --- a/tests/test_filter_thdn.py +++ b/tests/test_filter_thdn.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import pytest diff --git a/tests/test_filter_thdn/src/main.c b/tests/test_filter_thdn/src/main.c index ec393c1..31c6ad7 100644 --- a/tests/test_filter_thdn/src/main.c +++ b/tests/test_filter_thdn/src/main.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/tests/test_filter_thdn/src/sw_dac_conf.h b/tests/test_filter_thdn/src/sw_dac_conf.h index ad899e5..04c8f12 100644 --- a/tests/test_filter_thdn/src/sw_dac_conf.h +++ b/tests/test_filter_thdn/src/sw_dac_conf.h @@ -1,2 +1,2 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. diff --git a/tests/test_pre_distort.py b/tests/test_pre_distort.py index 7ff813b..b70c6bb 100644 --- a/tests/test_pre_distort.py +++ b/tests/test_pre_distort.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. from pathlib import Path diff --git a/tests/test_pre_distort/src/pre_distort_test.c b/tests/test_pre_distort/src/pre_distort_test.c index c904f90..de02d37 100644 --- a/tests/test_pre_distort/src/pre_distort_test.c +++ b/tests/test_pre_distort/src/pre_distort_test.c @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/tests/test_pre_distort/src/sw_dac_conf.h b/tests/test_pre_distort/src/sw_dac_conf.h index ad899e5..04c8f12 100644 --- a/tests/test_pre_distort/src/sw_dac_conf.h +++ b/tests/test_pre_distort/src/sw_dac_conf.h @@ -1,2 +1,2 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. diff --git a/tests/test_sigma_delta.py b/tests/test_sigma_delta.py index 6ebce35..9ff7e0d 100644 --- a/tests/test_sigma_delta.py +++ b/tests/test_sigma_delta.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import re import pytest @@ -17,6 +17,7 @@ max_pcm = 32767 pwm_rate = 1500000 filter_cutoff = 48000 +pwm_idle_words = [0x0ff0, 0xfffffffffffff00f, 0xfffffffff00ff00f, 0x0ff00ff0] def parse_output(stdout): pwm_lookup = {} @@ -27,11 +28,14 @@ def parse_output(stdout): current_loop = None loop_count = 0 max_pwm_magnitude = 0 + timedout = 0 for line in stdout: line = line.strip() if not line or "Started" in line or "Completed" in line: continue + if "Timeout" in line: + timedout = int(line.split(":")[1]) # Parse PWM table m_pwm = pwm_pattern.match(line) @@ -40,9 +44,16 @@ def parse_output(stdout): hexval = int(m_pwm.group(2), 16) pwm_lookup[hexval] = idx max_pwm_magnitude = abs(idx) if abs(idx) > max_pwm_magnitude else max_pwm_magnitude + # handle case where we have even number of PWM entries - need offset due to no 0 word + if len(pwm_lookup) % 2 == 0: + for pwm_val in pwm_lookup.keys(): + pwm_lookup[pwm_val] = pwm_lookup[pwm_val] + 0.5 + # Add idle words + for pwd_idle_word in pwm_idle_words: + pwm_lookup[pwd_idle_word] = 0 - return pwm_lookup, max_pwm_magnitude + return pwm_lookup, max_pwm_magnitude, timedout def parse_xscope(filepath, pwm_lookup): text = open(filepath, "r").read() @@ -67,14 +78,39 @@ def parse_xscope(filepath, pwm_lookup): if var == left_pwm_idx: left = pwm_lookup[pwm] + # if pwm in pwm_idle_words: + # print(f"Idle at {sample_num}") if var == right_pwm_idx: right = pwm_lookup[pwm] pairs.append((left, right)) sample_num += 1 - return np.array(pairs, dtype=np.int32) + return np.array(pairs, dtype=np.float64) +def check_hw_presence(): + run_cmd = f'xrun -l' + stdout = subprocess.check_output(run_cmd, shell = True) + run_output = stdout.decode("utf-8") + + return True if "No Available Devices Found" in run_output else False + +def run_on_sim(binary, xscope_file, burn, num_loops, pause_at): + run_cmd = f'xsim --xscope "-offline {xscope_file}" --args {binary} {burn} {num_loops} {pause_at}' + print("Running cmd: ", run_cmd) + stdout = subprocess.check_output(run_cmd, shell = True) + + return stdout.decode("utf-8").splitlines() + +def run_on_hw(binary, xscope_file, burn, num_loops, pause_at): + # Ensure we don't spin up two HW instances at the same time + with FileLock("xrun.lock"): + run_cmd = f'xrun --id 0 --xscope-file {xscope_file} --args {binary} {burn} {num_loops} {pause_at}' + print("Running cmd: ", run_cmd) + stdout = subprocess.check_output(run_cmd, shell = True) + + return stdout.decode("utf-8").splitlines() + """ This test runs just the sigma delta (and PWM) thread. It feeds in a 1.5MHz sampled sinewave of 1kHz and then captures the outputs to the ports over a channel. @@ -86,12 +122,14 @@ def parse_xscope(filepath, pwm_lookup): the expected THDN values of -90 or so unless we run for a long time on HW and downsample to 48k. The test automatically detects if there is an available target or not and uses xsim or xrun. """ -@pytest.mark.parametrize("burn", [1, 0]) +@pytest.mark.parametrize("burn", [0, 1]) +@pytest.mark.timeout(60 * 6) def test_sigma_delta(request, burn): test_name = "test_sigma_delta" + build = "CHAN" cwd = Path(request.fspath).parent - binary = Path(f'{cwd}/{test_name}/bin/{test_name}.xe') + binary = Path(f'{cwd}/{test_name}/bin/{build}/{test_name}_{build}.xe') assert Path(binary).exists(), f"Cannot find {binary}" tmp_binary = Path(f'{cwd}/{test_name}/bin/{test_name}_{burn}.xe') # Needed for xdist create_if_needed("logs") @@ -99,38 +137,28 @@ def test_sigma_delta(request, burn): shutil.copy2(binary, tmp_binary) # Check to see if we have HW - run_cmd = f'xrun -l' - stdout = subprocess.check_output(run_cmd, shell = True) - run_output = stdout.decode("utf-8") - using_simuator = True if "No Available Devices Found" in run_output else False - + using_simuator = check_hw_presence() print(f"Using simulator: {using_simuator}") + # About 2 mins on xsim and about 30s on xrun num_loops = 10000 if using_simuator else 2000000 xscope_file = Path(f"logs/{test_name}_trace_{burn}_{num_loops}.vcd") if using_simuator: - run_cmd = f'xsim --xscope "-offline {xscope_file}" --args {tmp_binary} {burn} {num_loops}' - print("Running cmd: ", run_cmd) - stdout = subprocess.check_output(run_cmd, shell = True) - run_output = stdout.decode("utf-8").splitlines() + run_output = run_on_sim(tmp_binary, xscope_file, burn, num_loops, num_loops) #pause_at == num_loops so no pause else: - # Ensure we don't spin up two HW instances at the same time - with FileLock("xrun.lock"): - run_cmd = f'xrun --id 0 --xscope-file {xscope_file} --args {tmp_binary} {burn} {num_loops}' - print("Running cmd: ", run_cmd) - stdout = subprocess.check_output(run_cmd, shell = True) - run_output = stdout.decode("utf-8").splitlines() - + run_output = run_on_hw(tmp_binary, xscope_file, burn, num_loops, num_loops) #pause_at == num_loops so no pause tmp_binary.unlink() # delete print("Parsing terminal output...") - pwm_lookup, max_pwm_magnitude = parse_output(run_output) + pwm_lookup, max_pwm_magnitude, timedout = parse_output(run_output) print("PWM Table (hex→index):") for k, v in pwm_lookup.items(): print(f" {hex(k)} → {v}") + print(f"SD thread timed out: {timedout}") + print("Parsing XSCOPE output...") pwm_vals = parse_xscope(xscope_file, pwm_lookup) @@ -139,6 +167,7 @@ def test_sigma_delta(request, burn): print(f"\nPWM output array shape: {pwm_array.shape}") + # Filter like the one on demo board sos = butter(N=2, Wn=filter_cutoff, fs=pwm_rate, output='sos') # Brickwall filter @@ -159,7 +188,11 @@ def test_sigma_delta(request, burn): # filtered = sosfilt(sos, one_channel) # like xms0021 HW - second order linear-phase-ish filtered = lfilter(brickwall, 1.0, one_channel) # brickwall - skip = 40 # There is a delay on the filter so skip the first few + if using_simuator: + skip = 40 + else: + skip = int(0.001 * pwm_rate) # There is a delay on the filter so skip the first millisecond + THDN, freq = THDN_and_freq(filtered[skip:], pwm_rate) print(f"Filtered channel {channel} THDN: {THDN} freq: {freq}") downsampled = resample_poly(filtered, up=up, down=down, axis=0) @@ -175,5 +208,8 @@ def test_sigma_delta(request, burn): wave_name = Path(f"logs/{test_name}_{sample_rate}_{burn}_{num_loops}.wav") wavfile.write(wave_name, sample_rate, np.int16(downsampled * max_pcm)) + + assert timedout == 0 + print() diff --git a/tests/test_sigma_delta/CMakeLists.txt b/tests/test_sigma_delta/CMakeLists.txt index 26fac0f..78e2edc 100644 --- a/tests/test_sigma_delta/CMakeLists.txt +++ b/tests/test_sigma_delta/CMakeLists.txt @@ -12,14 +12,16 @@ set(APP_DEPENDENT_MODULES "lib_sw_dac" set(APP_PCA_ENABLE OFF) # TODO why does PCA cause build error -set(APP_COMPILER_FLAGS -O3 - -mcmodel=large - -g - -report - -DSW_DAC_SD_TEST_MODE=1 - -fcmdline-buffer-bytes=1024) +set(COMPILER_FLAGS_COMMON -O3 + -mcmodel=large + -g + -report + -fcmdline-buffer-bytes=1024) set(APP_INCLUDES src) +set(APP_COMPILER_FLAGS_CHAN ${COMPILER_FLAGS_COMMON} -DSW_DAC_SD_TEST_MODE=1) +set(APP_COMPILER_FLAGS_PORT ${COMPILER_FLAGS_COMMON} -DSW_DAC_SD_TEST_MODE=0) + XMOS_REGISTER_APP() diff --git a/tests/test_sigma_delta/src/main.c b/tests/test_sigma_delta/src/main.c index b7ba018..4b9f7bb 100644 --- a/tests/test_sigma_delta/src/main.c +++ b/tests/test_sigma_delta/src/main.c @@ -1,10 +1,11 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include #include #include +#include #include #include #include @@ -12,16 +13,46 @@ #include "sdac_sf.h" #include "../../common/sin1500.h" #include +#include +extern void sigma_delta_task_sf(sw_dac_sf_t * sd, chanend_t c_sd); -DECLARE_JOB(test_app, (chanend_t, chanend_t, chanend_t, int, int)); -void test_app(chanend_t c_sd_in, chanend_t port_l, chanend_t port_r, int burn, int n_loops) { +int running = 1; + + +DECLARE_JOB(test_consumer, (chanend_t, chanend_t, int)); +void test_consumer(chanend_t port_l, chanend_t port_r, int burn) { xscope_mode_lossless(); + + if(burn){ + local_thread_mode_set_bits(thread_mode_fast); // Always issue + } + + hwtimer_t tmr = hwtimer_alloc(); + + uint32_t time_trig = hwtimer_get_trigger_time(tmr); + while(running){ + time_trig += (XS1_TIMER_HZ / 1500000); // Throttle consumption of outs because we are working over a chanend + hwtimer_wait_until(tmr, time_trig); + unsigned pwm_l = chanend_in_word(port_l); + unsigned pwm_r = chanend_in_word(port_r); + // printf("port: 0x%x 0x%x\n", pwm_l, pwm_r); + + xscope_int(0, pwm_l); + xscope_int(1, pwm_r); + } +} + + +DECLARE_JOB(test_producer, (sw_dac_sf_t*, chanend_t, int, int, int)); +void test_producer(sw_dac_sf_t *sd, chanend_t c_sd_in, int burn, int n_loops, int pause_at) { + hwtimer_t tmr = hwtimer_alloc(); if(burn){ local_thread_mode_set_bits(thread_mode_fast); // Always issue } - int n_sd_loops = 5; + + int n_sd_loops = 5; // How many samples to pass in each loop const int num_buffs_in_sd = 4; @@ -29,9 +60,7 @@ void test_app(chanend_t c_sd_in, chanend_t port_l, chanend_t port_r, int burn, i int sample_idx = 0; for(int loop_count = 0; loop_count < n_loops; loop_count++){ - // printf("loop: %d\n", loop_count); - // xscope_int(0, loop_count); - + // printintln(loop_count); int idx = loop_count % num_buffs_in_sd; // Send to SD modulator/PWM @@ -44,18 +73,25 @@ void test_app(chanend_t c_sd_in, chanend_t port_l, chanend_t port_r, int burn, i sample_idx++; } + if(loop_count == pause_at){ + const int num_milliseconds_pause = 20; + printstr("pause\n"); + hwtimer_delay(tmr, XS1_TIMER_KHZ * num_milliseconds_pause); + } + chanend_out_word(c_sd_in, (int) &data[idx][0]); - for(int n_outs = 0; n_outs < n_sd_loops; n_outs++){ - unsigned pwm_l = chanend_in_word(port_l); - unsigned pwm_r = chanend_in_word(port_r); - // printf("port: 0x%x 0x%x\n", pwm_l, pwm_r); - xscope_int(0, pwm_l); - xscope_int(1, pwm_r); + if(loop_count == 0){ + sd->timeout_occurred = 0; // Ensure we start clean after startup } + } + running = 0; // Stop consuming samples in consumer app + printf("Timeout: %d\n", sd->timeout_occurred); printf("Completed test app\n"); + hwtimer_delay(tmr, XS1_TIMER_MHZ); // FLush print + _Exit(0); } @@ -64,28 +100,38 @@ void burn(void){ while(1); } -DECLARE_JOB(run_sd_pwm, (sw_dac_sf_t *, chanend_t)); -void run_sd_pwm(sw_dac_sf_t *sd, chanend_t c_in) { - sigma_delta_1_5(sd, c_in); +// This ensures we don't start the SD until we are ready to produce +// Ensures we don't hit the timeout case in SD at startup +DECLARE_JOB(sigma_delta_task_sf_delay_start, (sw_dac_sf_t *, chanend_t)); +void sigma_delta_task_sf_delay_start(sw_dac_sf_t * sd, chanend_t c_sd){ + hwtimer_t tmr = hwtimer_alloc(); + hwtimer_delay(tmr, 20 * 100); // 20us + hwtimer_free(tmr); + sigma_delta_task_sf(sd, c_sd); } int main(int argc, char *argv[]) { - if(argc != 3){ - printf("Error - need to pass burn and loops as args\n"); + if(argc != 4){ + printf("Error - need to pass burn and loops and pause_at as args\n"); _Exit(-1); } int burn = atoi(argv[1]); int n_loops = atoi(argv[2]); + int pause_at = atoi(argv[3]); - printf("Started test app, loops: %d, burn: %d\n", burn, n_loops); - + printf("Started test app, burn: %d, n_loops: %d, pause_at: %d\n", burn, n_loops, pause_at); channel_t c_sd_ip = chan_alloc(); channel_t c_sd_op_0 = chan_alloc(); channel_t c_sd_op_1 = chan_alloc(); xclock_t clk = XS1_CLKBLK_1; +#if SW_DAC_SD_TEST_MODE port_t ports[2] = {c_sd_op_0.end_a, c_sd_op_1.end_a}; // L and R outputs - +#else + port_t ports[2] = {XS1_PORT_1A, XS1_PORT_1B}; // L and R outputs + port_enable(ports[0]); + port_enable(ports[1]); +#endif // Setup clock block to run from MCLK in which is set to 24MHz by the App PLL clock_enable(clk); clock_set_source_clk_ref(clk); @@ -97,20 +143,26 @@ int main(int argc, char *argv[]) { 1.0/120000, -1.0/250000, // flat_comp_x2, x3 3.0/157, 0.63/157); // pwm comp x2, x3 + // For xscope runs, we cannot sustain 2 x 1.5MHz 32b streams so make timeout large + if(n_loops == pause_at){ + sd.timeout_period = 100000; + } + + if(burn){ PAR_JOBS( - PJOB(burn, ()), - PJOB(burn, ()), - PJOB(burn, ()), - PJOB(burn, ()), - PJOB(burn, ()), - PJOB(test_app, (c_sd_ip.end_a, c_sd_op_0.end_b, c_sd_op_1.end_b, burn, n_loops)), - PJOB(run_sd_pwm, (&sd, c_sd_ip.end_b)) + PJOB(burn, ()), + PJOB(burn, ()), + PJOB(burn, ()), + PJOB(test_producer, (&sd, c_sd_ip.end_a, burn, n_loops, pause_at)), + PJOB(test_consumer, (c_sd_op_0.end_b, c_sd_op_1.end_b, burn)), + PJOB(sigma_delta_task_sf_delay_start, (&sd, c_sd_ip.end_b)) ); } else { PAR_JOBS( - PJOB(test_app, (c_sd_ip.end_a, c_sd_op_0.end_b, c_sd_op_1.end_b, burn, n_loops)), - PJOB(run_sd_pwm, (&sd, c_sd_ip.end_b)) + PJOB(test_producer, (&sd, c_sd_ip.end_a, burn, n_loops, pause_at)), + PJOB(test_consumer, (c_sd_op_0.end_b, c_sd_op_1.end_b, burn)), + PJOB(sigma_delta_task_sf_delay_start, (&sd, c_sd_ip.end_b)) ); } diff --git a/tests/test_sigma_delta/src/sw_dac_conf.h b/tests/test_sigma_delta/src/sw_dac_conf.h index ad899e5..04c8f12 100644 --- a/tests/test_sigma_delta/src/sw_dac_conf.h +++ b/tests/test_sigma_delta/src/sw_dac_conf.h @@ -1,2 +1,2 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. diff --git a/tests/test_sigma_delta_idle.py b/tests/test_sigma_delta_idle.py new file mode 100644 index 0000000..81f7b7e --- /dev/null +++ b/tests/test_sigma_delta_idle.py @@ -0,0 +1,138 @@ +# Copyright 2025 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +import re +import pytest +from pathlib import Path +from filelock import FileLock +import subprocess +import shutil +import numpy as np +from scipy.signal import resample_poly +from scipy.io import wavfile +from helpers import create_if_needed +from scipy.signal import butter, sosfilt, sosfiltfilt # Simulate the filter on xms0021 +from scipy.signal import firwin, freqz, lfilter # Brickwall filter +from test_sigma_delta import parse_output, parse_xscope, run_on_sim + +max_pcm = 32767 +pwm_rate = 1500000 +filter_cutoff = 48000 + +def analyze_signal(signal, fs): + # --- RMS --- + rms = np.sqrt(np.mean(signal**2)) + + # --- Mean --- + mean_val = np.mean(signal) + + # --- Approximate frequency (FFT method) --- + fft_vals = np.fft.fft(signal) + fft_freqs = np.fft.fftfreq(len(signal), 1/fs) + + pos_mask = fft_freqs > 0 + fft_vals = np.abs(fft_vals[pos_mask]) + fft_freqs = fft_freqs[pos_mask] + fft_freq = fft_freqs[np.argmax(fft_vals)] + + return rms, mean_val, fft_freq + +""" +Test to check whether we can recover from not feeding the SDM/PWM for a while. +Only works on sim because on HW, xscope_lossless sometimes backs up and breaks timing upstream. +This test runs just the sigma delta (and PWM) thread. It feeds in a 1.5MHz sampled sinewave +of 1kHz and then captures the outputs to the ports over a channel. +These are then converted to a time series of DC voltage levels and filtered and optionally +resampled to something a bit more manageable than 1.5MHz. +We check the output against RMS, freq and mean (DC level) +""" +@pytest.mark.parametrize("burn", [0, 1]) +@pytest.mark.timeout(60 * 6) +def test_sigma_delta_idle(request, burn): + dut_test_name = "test_sigma_delta" # Re-use same DUT app + test_name = "test_sigma_delta_idle" # But rename it so we don't clash with other tests using xdist + + cwd = Path(request.fspath).parent + binary = Path(f'{cwd}/{dut_test_name}/bin/CHAN/{dut_test_name}_CHAN.xe') + assert Path(binary).exists(), f"Cannot find {binary}" + tmp_binary = Path(f'{cwd}/{dut_test_name}/bin/{test_name}_{burn}.xe') # Needed for xdist + create_if_needed("logs") + with FileLock("file_copy.lock"): + shutil.copy2(binary, tmp_binary) + + + # About 2min on xsim + num_loops = 10000 + pause_at = num_loops // 2 + xscope_file = Path(f"logs/{test_name}_trace_{burn}_{num_loops}.vcd") + run_output = run_on_sim(tmp_binary, xscope_file, burn, num_loops, pause_at) + + tmp_binary.unlink() # delete + + print("Parsing terminal output...") + pwm_lookup, max_pwm_magnitude, timedout = parse_output(run_output) + print("PWM Table (hex→index):") + for k, v in pwm_lookup.items(): + print(f" {hex(k)} → {v}") + print(f"Timed out: {timedout}") + + + print("Parsing XSCOPE output...") + pwm_vals = parse_xscope(xscope_file, pwm_lookup) + + pwm_array = np.array(pwm_vals, dtype=float) + pwm_array /= max_pwm_magnitude # scale to +-1 + print(f"\nPWM output array shape: {pwm_array.shape}") + + + # Filter like the one on demo board + sos = butter(N=2, Wn=filter_cutoff, fs=pwm_rate, output='sos') + # Brickwall filter + brickwall = firwin(2001, filter_cutoff, fs=pwm_rate, window="hamming", pass_zero="lowpass") + + # Doesn't matter which we choose, as it's idle + recovery we are checking for + output_sample_rate = 48000 + + # Resample ratios + down = 125 + up = output_sample_rate / (pwm_rate / down) + for channel in [0, 1]: + print() + one_channel = pwm_array[:, channel] + # filtered = sosfilt(sos, one_channel) # like xms0021 HW - second order linear-phase-ish + filtered = lfilter(brickwall, 1.0, one_channel) # brickwall + + skip = 40 # There is a delay on the filter so skip the first few + downsampled = resample_poly(filtered, up=up, down=down, axis=0) + len_samples = downsampled.shape[0] + print(f"Resampling to: {output_sample_rate}, len_samples: {len_samples}") + + wave_name = Path(f"logs/{test_name}_{output_sample_rate}_{burn}_{num_loops}.wav") + wavfile.write(wave_name, output_sample_rate, np.int16(downsampled * max_pcm)) + + section_num = 1 + # These are tuned by looking at the output wav and measuring the signal, silence, signal sections + sections = [[0, int(len_samples*0.333)], + [int(len_samples*0.4), int(len_samples*0.6)], + [int(len_samples*0.666), len_samples]] + expected = [[0.32, 0.0, 1000], # RMS, Mean, freq + [0.0, 0.0, None], + [0.32, 0.0, 1000]] + + for section, expected in zip(sections, expected): + start, end = section + rms, mean, freq = analyze_signal(downsampled[start:end], output_sample_rate) + exp_rms, exp_mean, exp_freq = expected + print(f"section {section_num} {section}, rms: {rms:.2f} ({exp_rms:.2f}), mean: {mean:.2f} ({exp_mean:.2f}), freq: {freq:.2f} ({exp_freq})") + + # Note because of short run, we have to be quite lax on RTOL + assert np.isclose(exp_rms, rms, atol = 0.01), f"Expected RMS: {exp_rms} Actual RMS: {rms} - see {wave_name}" + assert np.isclose(exp_mean, mean, atol = 0.01), f"Expected RMS: {exp_mean} Actual RMS: {mean} - see {wave_name}" + if exp_freq is not None: + assert np.isclose(exp_freq, freq, rtol = 0.05), f"Expected RMS: {exp_freq} Actual RMS: {freq} - see {wave_name}" + + section_num += 1 + + + + + diff --git a/tests/test_timing_sf.py b/tests/test_timing_sf.py index 66cad37..48dcc38 100644 --- a/tests/test_timing_sf.py +++ b/tests/test_timing_sf.py @@ -1,4 +1,4 @@ -# Copyright 2025-2026 XMOS LIMITED. +# Copyright 2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import re import statistics @@ -11,8 +11,8 @@ import math cpu_clock_hz = 600e6 -sd_clock_hz = 24e6 # we now use a 24MHz clock gen -clock_factor = sd_clock_hz / 24e6 # Hangover from when clk was not exactly 24MHz +sd_clock_hz = 25e6 # set to ref / 4 in fw test app +clock_factor = sd_clock_hz / 24e6 # Because we are not testing at the 24MHz design spec in the sim num_channels = 2 trim_pcm_samples = 2 # We get weird effects at the end so trim some @@ -35,6 +35,7 @@ def extract_from_trace(file_path): if not m: continue thread = int(m.group(1)) # thread id + num_threads = thread + 1 if thread > num_threads else num_threads pause = ('P' in m.group(2)) addr = int(m.group(3).strip(), 16) @@ -48,8 +49,6 @@ def extract_from_trace(file_path): print(f"Hit main @{tstamp}") if hit_main: - num_threads = thread + 1 if (thread + 1) > num_threads else num_threads - events.append({ "time": tstamp, "thread": thread, @@ -76,7 +75,7 @@ def extract_from_trace(file_path): - Number of outs and pause time for SD stage 0 to stage 1 - Number of outpws """ -def analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_id, skip_loops, target_pwm_rate, verbose=False): +def analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_id, skip_loops, verbose=False): #now look for outs and pauses thread_events = [["out"], # test_app ["out"], # sd 0 @@ -85,14 +84,13 @@ def analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_i sample_count = 0 # Start at zero from the first PCM pwm_count = 0 last_pcm_out_time = 0 - sd_0_last_out_time = 0 - sd_0_paused_time = 0 - sd_0_was_paused = False + thread_1_last_out_time = 0 + thread_1_paused_time = 0 # For stats - app_thread_loop_times = [] - sd_0_pause_times_ns = [] - sd_1_pwm_frames_out = [] + thread_0_loop_times = [] + thread_1_pause_times_ns = [] + thread_2_pwm_frames_out = [] for event in events: thread = event["thread"] @@ -103,44 +101,36 @@ def analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_i # print(thread, instr, time, paused) - # App threads runs faster than downstream if thread == app_thread_id and any(k in instr for k in thread_events[0]) and not paused: # PCM out instruction from test_app sample_count += 1 if sample_count % num_channels == 0: pcm_out_time = time pcm_rate_hz = cpu_clock_hz / (pcm_out_time - last_pcm_out_time) / clock_factor - if verbose: print(f"Thread app PCM rate: {pcm_rate_hz}") + if verbose: print(f"Thread 0 PCM rate: {pcm_rate_hz}") if sample_count > skip_loops * num_channels: - app_thread_loop_times.append(pcm_out_time - last_pcm_out_time) + thread_0_loop_times.append(pcm_out_time - last_pcm_out_time) else: if verbose: print("SKIPPED") last_pcm_out_time = pcm_out_time - # sd0 (filter) should be blocked on OUT to sd1 if thread == sd_0_thread_id and any(k in instr for k in thread_events[1]): if paused: - if verbose: print(f"Thread sd_0 paused at addr: {hex(addr)} at time: {time}") - sd_0_paused_time = time - sd_0_was_paused = True + if verbose: print(f"Thread 1 paused at addr: {hex(addr)} at time: {time}") + thread_1_paused_time = time else: - # Pause time will be zero if no Pause on OUT - if not sd_0_was_paused: - sd_0_paused_time = time - - sd_0_period_ns = (time - sd_0_last_out_time) / cpu_clock_hz * 1e9 - if verbose: print(f"Thread sd_0 period {sd_0_period_ns}ns at addr: {hex(addr)}, paused for time: {time - sd_0_paused_time}") - sd_0_last_out_time = time + thread_1_period_ns = (time - thread_1_last_out_time) / cpu_clock_hz * 1e9 + if verbose: print(f"Thread 1 period {thread_1_period_ns}ns at addr: {hex(addr)}, unpaused at time: {time}") + thread_1_last_out_time = time if verbose: print(f"PWM out count since last 1->2: {pwm_count}") if sample_count > skip_loops * num_channels: - sd_1_pwm_frames_out.append(pwm_count) - sd_0_pause_time_ns = (time - sd_0_paused_time) / cpu_clock_hz * 1e9 - sd_0_pause_times_ns.append(sd_0_pause_time_ns) + thread_2_pwm_frames_out.append(pwm_count) + thread_1_pause_time_ns = (time - thread_1_paused_time) / cpu_clock_hz * 1e9 + thread_1_pause_times_ns.append(thread_1_pause_time_ns) pwm_count = 0 - sd_0_was_paused = False if thread == sd_1_thread_id and any(k in instr for k in thread_events[2]): # we have an output to the ports @@ -149,26 +139,25 @@ def analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_i #hack alert - last couple of results can be dodgy due to exit trim_last = 2 - app_thread_loop_times = app_thread_loop_times[0:-trim_last] - sd_0_pause_times_ns = sd_0_pause_times_ns[0:-trim_last] - - pcm_rate_hz = cpu_clock_hz / statistics.fmean(app_thread_loop_times) / clock_factor - sd_0_pause_pc = statistics.fmean(sd_0_pause_times_ns) / statistics.fmean(app_thread_loop_times) * 100 - sd_1_ave_pwm_frames = statistics.fmean(sd_1_pwm_frames_out) / num_channels - pwm_output_rate = sd_1_ave_pwm_frames * pcm_rate_hz - - print("**Analysis complete**") - print(f"App PCM rate average over {len(app_thread_loop_times)} loops: {pcm_rate_hz:.2f}Hz") - # print(app_thread_loop_times) - print(f"sd_0 average pause time: {sd_0_pause_pc:.2f}%") - print(f"sd_1 PWM average frames per PCM sample: {sd_1_ave_pwm_frames:.2f}") - print(f"sd_1 PWM output rate: {pwm_output_rate:.2f}Hz ({target_pwm_rate:.2f}Hz)") - print("\n") - - return pcm_rate_hz, sd_0_pause_pc, pwm_output_rate - -@pytest.mark.parametrize("sample_rate", [44100, 48000, 88200, 96000, 176400, 192000]) -@pytest.mark.parametrize("burn", [0, 1]) + thread_0_loop_times = thread_0_loop_times[0:-trim_last] + thread_1_pause_times_ns = thread_1_pause_times_ns[0:-trim_last] + + pcm_rate_hz = cpu_clock_hz / statistics.fmean(thread_0_loop_times) / clock_factor + thread_1_pause_pc = statistics.fmean(thread_1_pause_times_ns) / statistics.fmean(thread_0_loop_times) * 100 + thread_2_ave_pwm_frames = statistics.fmean(thread_2_pwm_frames_out) / num_channels + pwm_output_rate = thread_2_ave_pwm_frames * pcm_rate_hz + + print("\nAnalysis complete") + print(f"Thread 0 PCM rate average over {len(thread_0_loop_times)} loops: {pcm_rate_hz}") + print(thread_0_loop_times) + print(f"Thread 1 average pause time: {thread_1_pause_pc:.2f}%") + print(f"Thread 2 PWM average frames per PCM sample: {thread_2_ave_pwm_frames}") + print(f"Thread 2 PWM output rate: {pwm_output_rate:.2f}Hz") + + return pcm_rate_hz, thread_1_pause_pc, pwm_output_rate + +@pytest.mark.parametrize("sample_rate", [48000, 96000, 192000, 44100, 88200, 176400]) +@pytest.mark.parametrize("burn", [1, 0]) def test_thread_performance_sf(request, sample_rate, burn): cwd = Path(request.fspath).parent test_name = "timing_test_sf" @@ -191,22 +180,17 @@ def test_thread_performance_sf(request, sample_rate, burn): tmp_binary.unlink() # print(out, err) - target_pwm_rate = 1.5e6 events, app_thread_id, sd_0_thread_id, sd_1_thread_id = extract_from_trace(trace_file) - print(f"IDs app:{app_thread_id}, sd0:{sd_0_thread_id}, sd1:{sd_1_thread_id}") - pcm_rate_hz, sd_0_pause_pc, pwm_output_rate = analyse_extracted_trace(events, + pcm_rate_hz, thread_1_pause_pc, pwm_output_rate = analyse_extracted_trace(events, app_thread_id, sd_0_thread_id, sd_1_thread_id, skip_loops, - target_pwm_rate, - verbose=False) + verbose=True) + target_pwm_rate = 1.5e6 assert math.isclose(pwm_output_rate, target_pwm_rate, rel_tol=0.001), f"PWM rate not achieved: {pwm_output_rate:.2f} ({target_pwm_rate})" - assert math.isclose(pcm_rate_hz, sample_rate, rel_tol=0.001), f"PCM rate not achieved: {pcm_rate_hz:.2f} ({sample_rate})" - min_pause_pc = 5.0 # Generous min pause percentage - assert sd_0_pause_pc > min_pause_pc, f"Filter pause %age not achieved: {sd_0_pause_pc:.2f} ({min_pause_pc})" diff --git a/tests/timing_test_sf/src/main.c b/tests/timing_test_sf/src/main.c index eee8f3e..34c3615 100644 --- a/tests/timing_test_sf/src/main.c +++ b/tests/timing_test_sf/src/main.c @@ -1,27 +1,21 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include #include #include -#include #include #include #include #include "sw_dac.h" -volatile int clk_ready = 0; - -// Producer runs at full speed in this test so is throttled by downstream DECLARE_JOB(test_app, (chanend_t, int, int, int)); void test_app(chanend_t c_sd, int sample_rate, int burn, int n_loops) { if(burn){ local_thread_mode_set_bits(thread_mode_fast); // Always issue } - while(!clk_ready); - chanend_out_control_token(c_sd, 1); chanend_out_word(c_sd, sample_rate); @@ -34,62 +28,6 @@ void test_app(chanend_t c_sd, int sample_rate, int burn, int n_loops) { _Exit(0); } - -DECLARE_JOB(clk_gen,(void)); -void clk_gen(void){ - // 24 MHz pattern at 100MHz out - low noise below 1.5MHz - // LCM of 25 and 32 is 800. So that's 25 32b words we need. - // 25b pattern repeats wrapped for to 800 bits - - int pattern[25] = {1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0}; - uint32_t clk24_pattern[25] = {0}; - // Check we have correct num transitions - int old_pattern = 0; - int num_pos_edges = 0; - - - int pattern_idx = 0; - - for(int word = 0; word < 25; word++){ - for(int bit = 0; bit < 32; bit++){ - int next_bit = pattern[pattern_idx]; - - // Count rising edges - if(next_bit == 1 && old_pattern == 0){ - num_pos_edges++; - } - old_pattern = next_bit; - - clk24_pattern[word] |= (next_bit << bit); - // printf("word: %d bitpos: %d pattern_idx: %d, num_pos: %d\n", word, bit, next_bit, num_pos_edges); - // printf("%d", next_bit); - if(++pattern_idx == 25) pattern_idx = 0; - } - } - - // Check we have 24MHz exactly - xassert(100.0 / (25.0 * 32.0) * (float)num_pos_edges == 24.0); - - - port_t clk_out = XS1_PORT_1C; - xclock_t clk = XS1_CLKBLK_2; - clock_enable(clk); - clock_set_source_clk_ref(clk); - port_enable (clk_out); - port_set_clock(clk_out, clk); - port_start_buffered(clk_out, 32); - port_out(clk_out, 0); - - local_thread_mode_set_bits(thread_mode_fast); // Always issue for burn - clock_start(clk); - clk_ready = 1; - int idx = 0; - while(1){ - port_out(clk_out, clk24_pattern[idx]); - if(++idx == 25) idx = 0; - } -} - DECLARE_JOB(burn, (void)); void burn(void){ while(1); @@ -106,10 +44,9 @@ int main(int argc, char *argv[]) { port_t ports[2] = {XS1_PORT_1A, XS1_PORT_1B}; // L and R outputs // Setup clock block to run from MCLK in which is set to 24MHz by the App PLL - port_t clk_in = XS1_PORT_1C; - port_enable(clk_in); clock_enable(clk); - clock_set_source_port(clk, clk_in); + clock_set_source_clk_ref(clk); + clock_set_divide(clk, 4 / 2); // 25MHz sw_dac_sf_t sd; sw_dac_sf_init(&sd, ports, clk, 8, sd_coeffs_o6_f1_5_n8, @@ -117,7 +54,7 @@ int main(int argc, char *argv[]) { 1.0/120000, -1.0/250000, // flat_comp_x2, x3 3.0/157, 0.63/157); // pwm comp x2, x3 - printf("Started test app sr: %d, burn: %d, loops: %d\n", sample_rate, burn, n_loops); + printf("Started test app sr: %d, loops: %d, burn: %d\n", sample_rate, burn, n_loops); if(burn){ PAR_JOBS( @@ -125,13 +62,12 @@ int main(int argc, char *argv[]) { PJOB(burn, ()), PJOB(burn, ()), PJOB(burn, ()), - PJOB(clk_gen, ()), + PJOB(burn, ()), PJOB(test_app, (c_sd.end_a, sample_rate, burn, n_loops)), PJOB(sw_dac_sf, (&sd, c_sd.end_b)) ); } else { PAR_JOBS( - PJOB(clk_gen, ()), PJOB(test_app, (c_sd.end_a, sample_rate, burn, n_loops)), PJOB(sw_dac_sf, (&sd, c_sd.end_b)) ); diff --git a/tests/timing_test_sf/src/sw_dac_conf.h b/tests/timing_test_sf/src/sw_dac_conf.h index f616cc2..94074ae 100644 --- a/tests/timing_test_sf/src/sw_dac_conf.h +++ b/tests/timing_test_sf/src/sw_dac_conf.h @@ -1,4 +1,4 @@ -// Copyright 2025-2026 XMOS LIMITED. +// Copyright 2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1.