Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ tests/trace.txt
# editor
**/.vscode
*/trace.gtkw
tests/*.gtkw
7 changes: 5 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
lib_sw_dac change log
=====================

0.2.0
1.0.0
-----

* Added 44,100 family
* 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
-----
Expand Down
6 changes: 3 additions & 3 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -15,12 +15,12 @@ pipeline {
)
string(
name: 'XMOSDOC_VERSION',
defaultValue: 'v7.4.0',
defaultValue: 'v8.0.0',
description: 'xmosdoc version'
)
string(
name: 'INFR_APPS_VERSION',
defaultValue: 'v3.1.1',
defaultValue: 'v3.2.0',
description: 'The infr_apps version'
)
}
Expand Down
16 changes: 6 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: Audio, DAC, PWM
: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
Expand Down
5 changes: 0 additions & 5 deletions doc/rst/lib_sw_dac.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------
Expand Down
2 changes: 1 addition & 1 deletion lib_sw_dac/lib_build_info.cmake
Original file line number Diff line number Diff line change
@@ -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
Expand Down
24 changes: 18 additions & 6 deletions lib_sw_dac/src/standard_fidelity/run_sf.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand Down
12 changes: 12 additions & 0 deletions lib_sw_dac/src/standard_fidelity/sdac_sf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
77 changes: 65 additions & 12 deletions lib_sw_dac/src/standard_fidelity/sigma_delta_1_5.S
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xs1.h>
#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
Expand All @@ -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
Expand All @@ -27,22 +27,39 @@
.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
{ ld8u r1, r5[r11] ; ldap r11, min_max_table } // Load top byte of output value
{ 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 }
Expand All @@ -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
Expand All @@ -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))
Expand Down
25 changes: 17 additions & 8 deletions lib_sw_dac/src/standard_fidelity/software_dac_sf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -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];
Expand Down Expand Up @@ -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
}
Expand All @@ -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) {
Expand Down Expand Up @@ -488,15 +495,17 @@ 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);
}

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))
);
}
Loading