diff --git a/Jenkinsfile b/Jenkinsfile index f7075e8f..2013be2b 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 { @@ -169,11 +169,15 @@ 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" } - + dir("signal/profile") { + sh "pytest -v" + } // note no xdist for HW tests as only 1 hw instance // Each test has it's own conftest.py so we need to run these seprarately dir("signal/pdmrx_isr") { @@ -202,9 +206,7 @@ pipeline { dir("signal/FilterDesign") { runPytest('-v') } - dir("signal/profile") { - sh "pytest -v" - } + } } archiveArtifacts artifacts: "**/*.pkl", allowEmptyArchive: true diff --git a/lib_mic_array/api/mic_array/cpp/Decimator.hpp b/lib_mic_array/api/mic_array/cpp/Decimator.hpp index 7e785113..68e04713 100644 --- a/lib_mic_array/api/mic_array/cpp/Decimator.hpp +++ b/lib_mic_array/api/mic_array/cpp/Decimator.hpp @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -69,6 +70,14 @@ class Decimator * Per-mic, 32-bit PDM output words from the PDM RX stage. */ unsigned pdm_out_words_per_mic; + + /** + * Per-mic flag indicating whether the most recently processed PDM word + * was flagged as bad (excessive leading run of 0s or 1s). Used so that + * the word immediately following a bad word is also replaced, since it + * may itself be corrupted. + */ + bool pdm_word_was_bad[MIC_COUNT]; } stage1; public: @@ -197,8 +206,11 @@ void mic_array::Decimator this->stage1.pdm_out_words_per_mic = pdm_out_words_per_mic; memset(this->stage1.pdm_history_ptr, 0x55, sizeof(int32_t) * MIC_COUNT * this->stage1.pdm_history_sz); + memset(this->stage1.pdm_word_was_bad, 0, sizeof(this->stage1.pdm_word_was_bad)); if(decimator_conf.num_filter_stages >= 2) { + memset(decimator_conf.filter_conf[1].state, 0, sizeof(int32_t) * MIC_COUNT * decimator_conf.filter_conf[1].state_words_per_channel); + for(int k = 0; k < MIC_COUNT; k++){ filter_fir_s32_init(&this->stage2.filters[k], decimator_conf.filter_conf[1].state + (k * decimator_conf.filter_conf[1].state_words_per_channel), decimator_conf.filter_conf[1].num_taps, decimator_conf.filter_conf[1].coef, decimator_conf.filter_conf[1].shr); @@ -207,6 +219,8 @@ void mic_array::Decimator } if(decimator_conf.num_filter_stages == 3) { + memset(decimator_conf.filter_conf[2].state, 0, sizeof(int32_t) * MIC_COUNT * decimator_conf.filter_conf[2].state_words_per_channel); + for(int k = 0; k < MIC_COUNT; k++){ filter_fir_s32_init(&this->stage3.filters[k], decimator_conf.filter_conf[2].state + (k * decimator_conf.filter_conf[2].state_words_per_channel), decimator_conf.filter_conf[2].num_taps, decimator_conf.filter_conf[2].coef, decimator_conf.filter_conf[2].shr); @@ -224,9 +238,29 @@ void mic_array::Decimator { for(unsigned mic = 0; mic < MIC_COUNT; mic++){ uint32_t* hist = this->stage1.pdm_history_ptr + (mic * this->stage1.pdm_history_sz); + bool prev_word_bad = this->stage1.pdm_word_was_bad[mic]; for(unsigned k = 0; k < this->stage2.decimation_factor; k++){ hist[0] = *(pdm_block + (mic*this->stage2.decimation_factor + k)); + + constexpr unsigned max_leading_run = 3; + // leading_zeros and leading_ones are mutually exclusive (only one can + // be non-zero for a given word), so XOR the word with its own sign + // mask (0x00000000 or 0xFFFFFFFF) to fold whichever run it has down + // to a leading-zero run, then use a single clz (a single-cycle + // instruction on XS3A) instead of two clz calls plus a max(). + uint32_t sign_mask = (uint32_t)((int32_t)hist[0] >> 31); + unsigned max_leading_run_len = __builtin_clz(hist[0] ^ sign_mask); + + bool this_word_bad = max_leading_run_len > max_leading_run; + + + if(this_word_bad || prev_word_bad) { + hist[0] = 0x55555555; // write 0x55555555 to buffer + } + + prev_word_bad = this_word_bad; + int32_t streamA_sample = fir_1x16_bit(hist, this->stage1.filter_coef); shift_buffer(hist); @@ -236,6 +270,8 @@ void mic_array::Decimator sample_out[mic] = filter_fir_s32(&this->stage2.filters[mic], streamA_sample); } } + + this->stage1.pdm_word_was_bad[mic] = prev_word_bad; } } diff --git a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp index a95ffdce..ea51186f 100644 --- a/lib_mic_array/api/mic_array/cpp/PdmRx.hpp +++ b/lib_mic_array/api/mic_array/cpp/PdmRx.hpp @@ -473,6 +473,41 @@ void mic_array::StandardPdmRxService::SetPort(port_t 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] = boot_pattern; + + // 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)); + // } + // // 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) { + // break; + // } + // } + while(1){ this->blocks[0][--phase] = port_in(this->p_pdm_mics); diff --git a/python/mic_array/filters.py b/python/mic_array/filters.py index 20f1f3cc..55f152c5 100644 --- a/python/mic_array/filters.py +++ b/python/mic_array/filters.py @@ -78,6 +78,30 @@ 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 nor all-1 + + good_frames = 0 + + for start in range(0, pdm_signal.shape[1], 32): + 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 + + 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 +109,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)