Implemnt sound capture in the simulator
diff --git a/verilog/sim/Makefile b/verilog/sim/Makefile index eb56330..7ee86b6 100644 --- a/verilog/sim/Makefile +++ b/verilog/sim/Makefile
@@ -36,10 +36,13 @@ CPPSRCS += \ ./main.cpp \ + ./audiosim.cpp \ + ./cic.cpp \ ./dispsim.cpp \ ./mbcsim.cpp \ ./memsim.cpp \ ./mmrprobe.cpp \ + ./waveheader.cpp \ verilated.cpp \ verilated_vcd_c.cpp
diff --git a/verilog/sim/audiosim.cpp b/verilog/sim/audiosim.cpp new file mode 100644 index 0000000..55894e5 --- /dev/null +++ b/verilog/sim/audiosim.cpp
@@ -0,0 +1,103 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// audiosim.cpp: Capture PDM output and pass through a filter then save to wave +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include <vector> +#include "cic.h" +#include "audiosim.h" +#include "waveheader.h" + +AUDIOSIM::AUDIOSIM(void) { + initializeCicFilterStruct(3, DECIMATION_M, &filter_left); + initializeCicFilterStruct(3, DECIMATION_M, &filter_right); + pcm.clear(); + bit_counter = 0; + byte_counter = 0; + bypass_counter = 0; +} + +AUDIOSIM::~AUDIOSIM(void) { + +} + +void AUDIOSIM::save(char *fname) { + printf("Max: %d, min: %d\n", max, min); + save_wav(fname, pcm); + save_wav("bypass.wav", byp); +} + +void AUDIOSIM::save_wav(char *fname, std::vector<int16_t> &pcm) { + uint8_t header[44]; + waveheader(header, 48000, 16, pcm.size() / 2); + FILE *fp; + fp = fopen(fname, "wb+"); + fwrite(header, 44, 1, fp); + fwrite(&pcm[0], pcm.size() * 2, 1, fp); + fclose(fp); + printf("Audio save to %s\n", fname); +} + +void AUDIOSIM::apply(uint8_t left, uint8_t right) { + byte_left |= left << 7; + byte_right |= right << 7; + bit_counter++; + if (bit_counter < 8) { + byte_left >>= 1; + byte_right >>= 1; + } + else { + bit_counter = 0; + pdm_buffer_left[byte_counter] = byte_left; + pdm_buffer_right[byte_counter] = byte_right; + byte_counter++; + if (byte_counter == PDM_SAMPLE_SIZE * DECIMATION_M / 8) { + byte_counter = 0; + executeCicFilter(pdm_buffer_left, PDM_SAMPLE_SIZE * DECIMATION_M, + pcm_buffer_left, &filter_left); + executeCicFilter(pdm_buffer_right, PDM_SAMPLE_SIZE * DECIMATION_M, + pcm_buffer_right, &filter_right); + for (int i = 0; i < PDM_SAMPLE_SIZE; i++) { + pcm.push_back((int16_t)(pcm_buffer_left[i] / 64)); + pcm.push_back((int16_t)(pcm_buffer_right[i] / 64)); + if (pcm_buffer_left[i] > max) + max = pcm_buffer_left[i]; + if (pcm_buffer_left[i] < min) + min = pcm_buffer_left[i]; + } + } + } +} + +void AUDIOSIM::bypass(int16_t left, int16_t right) { + bypass_counter++; + if (bypass_counter == DECIMATION_M) { + bypass_counter = 0; + byp.push_back(left); + byp.push_back(right); + } +} \ No newline at end of file
diff --git a/verilog/sim/audiosim.h b/verilog/sim/audiosim.h new file mode 100644 index 0000000..97c2a86 --- /dev/null +++ b/verilog/sim/audiosim.h
@@ -0,0 +1,58 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// audiosim.h: Capture PDM output and pass through a filter then save to wave +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +// 4M->48K: 88 times decimation +#define DECIMATION_M 88 +#define PDM_SAMPLE_SIZE 4096 + +class AUDIOSIM { +public: + AUDIOSIM(void); + ~AUDIOSIM(void); + void save(char *fname); + void apply(uint8_t left, uint8_t right); + void bypass(int16_t left, int16_t right); +private: + int bit_counter; + int byte_counter; + uint8_t byte_left; + uint8_t byte_right; + struct CicFilter_t filter_left; + struct CicFilter_t filter_right; + uint8_t pdm_buffer_left[PDM_SAMPLE_SIZE * DECIMATION_M / 8]; + int32_t pcm_buffer_left[PDM_SAMPLE_SIZE]; + uint8_t pdm_buffer_right[PDM_SAMPLE_SIZE * DECIMATION_M / 8]; + int32_t pcm_buffer_right[PDM_SAMPLE_SIZE]; + std::vector<int16_t> pcm; + + int bypass_counter; + std::vector<int16_t> byp; + + int32_t max; + int32_t min; + + void save_wav(char *fname, std::vector<int16_t> &pcm); +};
diff --git a/verilog/sim/cic.cpp b/verilog/sim/cic.cpp new file mode 100644 index 0000000..e0fa141 --- /dev/null +++ b/verilog/sim/cic.cpp
@@ -0,0 +1,104 @@ +// Software CIC +// https://blog.y2kb.com/posts/pdm-mic-spi-cic/ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> +#include "cic.h" + +void initializeCicFilterStruct(uint8_t CicFilterOrder, uint32_t CicFilterDecimation, struct CicFilter_t* st) +{ + if (CicFilterOrder == 0) + { + printf("Order of CIC filter must be over 0\r\n"); + return; + } + if(CicFilterDecimation == 0) + { + printf("Decimation of CIC filter must be over 0\r\n"); + return; + } + st->order = CicFilterOrder; + st->decimation = CicFilterDecimation; + st->out_i = (int32_t*) malloc(sizeof(int32_t) * st->order); + st->out_c = (int32_t*) malloc(sizeof(int32_t) * st->order); + st->z1_c = (int32_t*) malloc(sizeof(int32_t) * st->order); + + if ((st->out_i == NULL) || (st->out_c == NULL) || (st->z1_c == NULL)) + { + printf("CicFilterStruct malloc error\r\n"); + return; + } + resetCicFilterStruct(st); +} + +void resetCicFilterStruct(struct CicFilter_t* st) +{ + if(st == NULL) + return; + + for(uint8_t i = 0; i < st->order; i++) + { + *(st->out_i + i) = 0; + *(st->out_c + i) = 0; + *(st->z1_c + i) = 0; + } +} + +void executeCicFilter(uint8_t* pInBit, uint32_t pInBit_Num, int32_t* pOut_int32, struct CicFilter_t* st) +{ + //uint8_t Bout = st->order * (uint8_t)(ceil(log2f(st->decimation))) + 1; + int32_t in_bit; + uint8_t in_Byte; + uint32_t Bit_i, Decimation_count; + uint8_t i; + + for(Bit_i = 0, Decimation_count = st->decimation - 1; Bit_i < pInBit_Num; ++Bit_i, --Decimation_count) + { + in_Byte = *(pInBit + (Bit_i >> 3) ); + if( (in_Byte >> (Bit_i & 0x07)) & 0x01 ) // First bit is [b0] + in_bit = 1; + else + in_bit = -1; + + // Integrator (No need to consider saturation. + // ref : http://d.hatena.ne.jp/suikan+blackfin/20060611/1149996053 (Japanese) ) + *(st->out_i) += in_bit; + + for (i = 1; i < st->order; ++i) + { + *(st->out_i + i) += *(st->out_i + i - 1); + } + + // Decimation + if(Decimation_count == 0) + { + Decimation_count = st->decimation; + + // Comb filter + *(st->out_c) = *(st->out_i + st->order - 1) - *(st->z1_c); + *(st->z1_c) = *(st->out_i + st->order - 1); + + for (i = 1; i < st->order; ++i) + { + *(st->out_c + i) = *(st->out_c + i - 1) - *(st->z1_c + i); + *(st->z1_c + i) = *(st->out_c + i - 1); + } + + *(pOut_int32 + Bit_i / st->decimation) = *(st->out_c + st->order - 1); + } + } +} + +void finalizeCicFilterStruct(struct CicFilter_t* st) +{ + if (st == NULL) + return; + + st->order = 0; + st->decimation = 0; + free(st->out_i); + free(st->out_c); + free(st->z1_c); +}
diff --git a/verilog/sim/cic.h b/verilog/sim/cic.h new file mode 100644 index 0000000..abe8e89 --- /dev/null +++ b/verilog/sim/cic.h
@@ -0,0 +1,17 @@ +// Software CIC +// https://blog.y2kb.com/posts/pdm-mic-spi-cic/ +#pragma once + +struct CicFilter_t +{ + uint8_t order; + uint32_t decimation; + int32_t *out_i; + int32_t *out_c; + int32_t *z1_c; +}; + +void initializeCicFilterStruct(uint8_t, uint32_t, struct CicFilter_t*); +void resetCicFilterStruct(struct CicFilter_t*); +void executeCicFilter(uint8_t*, uint32_t, int32_t*, struct CicFilter_t*); +void finalizeCicFilterStruct(struct CicFilter_t*);
diff --git a/verilog/sim/main.cpp b/verilog/sim/main.cpp index b98ede5..396fb3e 100644 --- a/verilog/sim/main.cpp +++ b/verilog/sim/main.cpp
@@ -26,6 +26,7 @@ #include <stdint.h> #include <assert.h> #include <time.h> +#include <vector> #include <SDL.h> @@ -37,6 +38,8 @@ #include "mbcsim.h" #include "dispsim.h" #include "mmrprobe.h" +#include "cic.h" +#include "audiosim.h" #define CLK_PERIOD_PS 250000 @@ -62,6 +65,7 @@ static bool nostop = false; static bool itrace = false; static bool usembc = false; +static bool enable_audio = false; static unsigned short breakpoint = 0xff7f; static char result_file[127]; @@ -71,6 +75,7 @@ MBCSIM *mbc; DISPSIM *dispsim; MMRPROBE *mmrprobe; +AUDIOSIM *audiosim; FILE *it; // State @@ -115,6 +120,15 @@ core->valid); } + if (enable_audio) { + audiosim->apply( + core->audiol, + core->audior); + audiosim->bypass( + core->simtop__DOT__chip__DOT__left, + core->simtop__DOT__chip__DOT__right); + } + if (verbose) { mmrprobe->apply( SIGNAL(cpu_dout), @@ -219,6 +233,10 @@ if (strcmp(argv[i], "--mbc") == 0) { usembc = true; } + // Enable audio capture + if (strcmp(argv[i], "--audio") == 0) { + enable_audio = true; + } } if (enable_trace) { @@ -254,6 +272,10 @@ else cartrom->load(argv[1]); + if (enable_audio) { + audiosim = new AUDIOSIM(); + } + // Start simulation if (verbose) printf("Simulation start.\n"); @@ -406,6 +428,10 @@ delete cartrom; delete cartram; } + if (enable_audio) { + audiosim->save("audio.wav"); + delete audiosim; + } return 0; } \ No newline at end of file
diff --git a/verilog/sim/waveheader.cpp b/verilog/sim/waveheader.cpp new file mode 100644 index 0000000..1e44700 --- /dev/null +++ b/verilog/sim/waveheader.cpp
@@ -0,0 +1,53 @@ +#include <stdint.h> +#include "waveheader.h" + +void waveheader(uint8_t *header, uint32_t sampleRate, uint32_t bitsPerSample, uint32_t samples) { + uint32_t audioDataLen = samples * (bitsPerSample / 8u) * 2u; + uint32_t fileSize = audioDataLen + 44u; + uint32_t totalDataLen = fileSize - 8u; + uint32_t byteRate = sampleRate * (bitsPerSample / 8u) * 2u; + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + header[4] = (totalDataLen & 0xff); /* file-size (equals file-size - 8) */ + header[5] = ((totalDataLen >> 8U) & 0xff); + header[6] = ((totalDataLen >> 16U) & 0xff); + header[7] = ((totalDataLen >> 24U) & 0xff); + header[8] = 'W'; /* Mark it as type "WAVE" */ + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + header[12] = 'f'; /* Mark the format section 'fmt ' chunk */ + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + header[16] = 16; /* 4 bytes: size of 'fmt ' chunk, Length of format data. Always 16 */ + header[17] = 0; + header[18] = 0; + header[19] = 0; + header[20] = 1; /* format = 1 ,Wave type PCM */ + header[21] = 0; + header[22] = 2; /* channels */ + header[23] = 0; + header[24] = (sampleRate & 0xff); + header[25] = ((sampleRate >> 8U) & 0xff); + header[26] = ((sampleRate >> 16U) & 0xff); + header[27] = ((sampleRate >> 24U) & 0xff); + header[28] = (byteRate & 0xff); + header[29] = ((byteRate >> 8U) & 0xff); + header[30] = ((byteRate >> 16U) & 0xff); + header[31] = ((byteRate >> 24U) & 0xff); + header[32] = (2 * bitsPerSample / 8); /* block align */ + header[33] = 0; + header[34] = bitsPerSample; /* bits per sample */ + header[35] = 0; + header[36] = 'd'; /*"data" marker */ + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + header[40] = (audioDataLen & 0xff); /* data-size (equals file-size - 44).*/ + header[41] = ((audioDataLen >> 8) & 0xff); + header[42] = ((audioDataLen >> 16) & 0xff); + header[43] = ((audioDataLen >> 24) & 0xff); +} \ No newline at end of file
diff --git a/verilog/sim/waveheader.h b/verilog/sim/waveheader.h new file mode 100644 index 0000000..cce5f7e --- /dev/null +++ b/verilog/sim/waveheader.h
@@ -0,0 +1,4 @@ + +#pragma once + +void waveheader(uint8_t *header, uint32_t sampleRate, uint32_t bitsPerSample, uint32_t samples); \ No newline at end of file