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