From 6803fbcd9687775615febe0b6018d242964db58f Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Fri, 26 Jun 2026 14:02:55 +0100 Subject: [PATCH 1/7] Wait for data on the port that isn't 0 or 1, send 55 while waiting --- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index a95ffdce..be7e6f48 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -473,6 +473,34 @@ void mic_array::StandardPdmRxService::SetPort(port_t template void mic_array::StandardPdmRxService::ThreadEntry() { + + uint32_t good_frames = 0; + while(1){ + // During boot, the PDM port may read all 0s or all 1s. + // Output 0x55 (zero) to the buffer until we get a valid frame. + uint32_t data = port_in(this->p_pdm_mics); + this->blocks[0][--phase] = 0x55555555; + + if(!phase){ + this->phase = this->num_phases; + uint32_t* ready_block = this->blocks[0]; + this->blocks[0] = this->blocks[1]; + this->blocks[1] = ready_block; + + s_chan_out_word(this->c_pdm_blocks.end_a, reinterpret_cast(ready_block)); + } + if (data == 0x00000000 || data == 0xFFFFFFFF) { + good_frames = 0; + continue; + } + good_frames++; + if (good_frames > 1) { + // Pin can toggle between 0 and 1, so we need to wait for a few good + // frames before we start using the data. + break; + } + } + while(1){ this->blocks[0][--phase] = port_in(this->p_pdm_mics); From 92454808a10ec39de915c5509f40c97b678b4299 Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Fri, 26 Jun 2026 15:50:01 +0100 Subject: [PATCH 2/7] add boot logic to python filter --- python/mic_array/filters.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/python/mic_array/filters.py b/python/mic_array/filters.py index 20f1f3cc..0b1de82e 100644 --- a/python/mic_array/filters.py +++ b/python/mic_array/filters.py @@ -78,6 +78,45 @@ def _pad_input(self, sig_in): S[:,P:] = sig_in return S + def boot_logic(self, pdm_signal: np.ndarray) -> np.ndarray: + # Simulate the PdmRx thread boot loop (StandardPdmRxService::ThreadEntry, + # PdmRx.hpp): words are received one at a time; 0x55555555 is written to + # the buffer (discarding real data) until good_frames > 1, i.e. 2 + # consecutive words that are neither all-0 (0x00000000) nor all-1 + # (0xFFFFFFFF). Bit layout: bit (c + CHANS*s) of word w = + # to_binary[c, w*spw+s], where to_binary(+1)=0, to_binary(-1)=1. + + CHANS, SAMPS_IN = pdm_signal.shape + BOOT_WORD = np.uint32(0x55555555) + spw = 32 // CHANS # PDM samples per channel packed per interleaved word + total_words = SAMPS_IN // spw + binary = ((1 - pdm_signal) // 2).astype(np.uint32) + + words = np.zeros(total_words, dtype=np.uint32) + for s in range(spw): + for c in range(CHANS): + words |= binary[c, s::spw][:total_words] << np.uint32(c + CHANS * s) + + good_frames = 0 + boot_words = 0 + for word in words: + boot_words += 1 + if word == np.uint32(0x00000000) or word == np.uint32(0xFFFFFFFF): + good_frames = 0 + else: + good_frames += 1 + if good_frames > 1: + break + + pdm_signal = pdm_signal.copy() + for w in range(boot_words): + for s in range(spw): + for c in range(CHANS): + bit = int((BOOT_WORD >> np.uint32(c + CHANS * s)) & np.uint32(1)) + pdm_signal[c, w * spw + s] = np.int32(-1 if bit else 1) + + return pdm_signal + def FilterInt16(self, pdm_signal: np.ndarray) -> np.ndarray: if pdm_signal.ndim == 1: pdm_signal = pdm_signal[np.newaxis,:] @@ -85,6 +124,8 @@ def FilterInt16(self, pdm_signal: np.ndarray) -> np.ndarray: Q = self.DecimationFactor N_pcm = SAMPS_IN // self.DecimationFactor + pdm_signal = self.boot_logic(pdm_signal) + S = self._pad_input(pdm_signal) coefs = self.Coef.astype(np.int32)[:,np.newaxis] res = np.empty((CHANS, N_pcm), dtype=np.int32) From 8b16777904084ec57acda2121449043dfb3ed21a Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Fri, 26 Jun 2026 16:26:51 +0100 Subject: [PATCH 3/7] bump jsl --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f7075e8f..d8e58b9e 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.49.0') _ +@Library('xmos_jenkins_shared_library@v0.52.0') _ getApproval() pipeline { From f38b2afdb5a5ca658c36085a78b322d3e6a2dee0 Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Mon, 29 Jun 2026 09:26:51 +0100 Subject: [PATCH 4/7] more sane python boot logic --- python/mic_array/filters.py | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/python/mic_array/filters.py b/python/mic_array/filters.py index 0b1de82e..952fb44b 100644 --- a/python/mic_array/filters.py +++ b/python/mic_array/filters.py @@ -82,41 +82,23 @@ def boot_logic(self, pdm_signal: np.ndarray) -> np.ndarray: # Simulate the PdmRx thread boot loop (StandardPdmRxService::ThreadEntry, # PdmRx.hpp): words are received one at a time; 0x55555555 is written to # the buffer (discarding real data) until good_frames > 1, i.e. 2 - # consecutive words that are neither all-0 (0x00000000) nor all-1 - # (0xFFFFFFFF). Bit layout: bit (c + CHANS*s) of word w = - # to_binary[c, w*spw+s], where to_binary(+1)=0, to_binary(-1)=1. + # consecutive words that are neither all-0 nor all-1 - CHANS, SAMPS_IN = pdm_signal.shape - BOOT_WORD = np.uint32(0x55555555) - spw = 32 // CHANS # PDM samples per channel packed per interleaved word - total_words = SAMPS_IN // spw - binary = ((1 - pdm_signal) // 2).astype(np.uint32) + good_frames = 0 - words = np.zeros(total_words, dtype=np.uint32) - for s in range(spw): - for c in range(CHANS): - words |= binary[c, s::spw][:total_words] << np.uint32(c + CHANS * s) + for start in range(0, pdm_signal.shape[1], 32): + pdm_signal[:,start:start+32] = np.tile([-1, 1], 16).T # write 0x55555555 to buffer - good_frames = 0 - boot_words = 0 - for word in words: - boot_words += 1 - if word == np.uint32(0x00000000) or word == np.uint32(0xFFFFFFFF): + if np.all(pdm_signal[:,start:start+32] == 1) or np.all(pdm_signal[:,start:start+32] == -1): good_frames = 0 - else: - good_frames += 1 + continue + good_frames += 1 if good_frames > 1: break - pdm_signal = pdm_signal.copy() - for w in range(boot_words): - for s in range(spw): - for c in range(CHANS): - bit = int((BOOT_WORD >> np.uint32(c + CHANS * s)) & np.uint32(1)) - pdm_signal[c, w * spw + s] = np.int32(-1 if bit else 1) - return pdm_signal + def FilterInt16(self, pdm_signal: np.ndarray) -> np.ndarray: if pdm_signal.ndim == 1: pdm_signal = pdm_signal[np.newaxis,:] From 1bf15acfd46ae19dd5ddd50c5d36ea748278d356 Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Mon, 29 Jun 2026 10:00:35 +0100 Subject: [PATCH 5/7] correct patterns for other mic counts --- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 12 +++++++++++- python/mic_array/filters.py | 7 +++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index be7e6f48..83ba3200 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -474,12 +474,22 @@ template void mic_array::StandardPdmRxService::ThreadEntry() { + // The boot pattern is interleaved silence on each channel, so 0101.. for 1 mic + // 00110011 for 2 mics etc. After deinterleaving, each channel ends up with the + // 0x55555555 (0101..) silence pattern. The pre-deinterleave pattern is + // CHANNELS_IN zeros followed by CHANNELS_IN ones, repeated. + uint32_t boot_pattern = (CHANNELS_IN == 1) ? 0x55555555 : + (CHANNELS_IN == 2) ? 0x33333333 : + (CHANNELS_IN == 4) ? 0x0F0F0F0F : + (CHANNELS_IN == 8) ? 0x00FF00FF : + 0x0000FFFF; // 16 mics + uint32_t good_frames = 0; while(1){ // During boot, the PDM port may read all 0s or all 1s. // Output 0x55 (zero) to the buffer until we get a valid frame. uint32_t data = port_in(this->p_pdm_mics); - this->blocks[0][--phase] = 0x55555555; + this->blocks[0][--phase] = boot_pattern; if(!phase){ this->phase = this->num_phases; diff --git a/python/mic_array/filters.py b/python/mic_array/filters.py index 952fb44b..55f152c5 100644 --- a/python/mic_array/filters.py +++ b/python/mic_array/filters.py @@ -87,11 +87,14 @@ def boot_logic(self, pdm_signal: np.ndarray) -> np.ndarray: good_frames = 0 for start in range(0, pdm_signal.shape[1], 32): - pdm_signal[:,start:start+32] = np.tile([-1, 1], 16).T # write 0x55555555 to buffer - if np.all(pdm_signal[:,start:start+32] == 1) or np.all(pdm_signal[:,start:start+32] == -1): good_frames = 0 + pdm_signal[:,start:start+32] = np.tile([-1, 1], 16).T # write 0x55555555 to buffer continue + + # signal gets modified either way + pdm_signal[:,start:start+32] = np.tile([-1, 1], 16).T # write 0x55555555 to buffer + good_frames += 1 if good_frames > 1: break From 6a78128de35eab548ad7061460581e60c47ce72f Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Mon, 29 Jun 2026 15:09:31 +0100 Subject: [PATCH 6/7] xrun -l --- Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index d8e58b9e..bfc8e702 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -169,6 +169,8 @@ pipeline { // This ensures a project for XS2 can be built and runs OK sh "xsim test_xs2_benign/bin/xs2.xe" + sh "xrun -l" + // Run this first to ensure the XTAG is up and running for subsequent tests timeout(time: 2, unit: 'MINUTES') { sh "xrun --xscope --id 0 unit/bin/tests-unit.xe" From f89758d389568b96ae661da492e8fe23da30bc83 Mon Sep 17 00:00:00 2001 From: Allan Skellett Date: Mon, 29 Jun 2026 15:57:34 +0100 Subject: [PATCH 7/7] slightly faster --- lib_mic_array/api/mic_array/cpp/PdmRx.hpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index 83ba3200..8408f2c7 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -499,14 +499,11 @@ void mic_array::StandardPdmRxService::ThreadEntry() s_chan_out_word(this->c_pdm_blocks.end_a, reinterpret_cast(ready_block)); } - if (data == 0x00000000 || data == 0xFFFFFFFF) { - good_frames = 0; - continue; - } - good_frames++; + // During boot the pin can toggle between all-0s and all-1s, so wait for a + // couple of consecutive valid frames before using the data + bool bad_frame = (data == 0x00000000) || (data == 0xFFFFFFFF); + good_frames = bad_frame ? 0 : (good_frames + 1); if (good_frames > 1) { - // Pin can toggle between 0 and 1, so we need to wait for a few good - // frames before we start using the data. break; } }