OpenCores
URL https://opencores.org/ocsvn/wbpwmaudio/wbpwmaudio/trunk

Subversion Repositories wbpwmaudio

[/] [wbpwmaudio/] [trunk/] [demo-rtl/] [pdmdemo.cpp] - Blame information for rev 4

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 4 dgisselq
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Filename:    pdmdemo.cpp
4
//
5
// Project:     A Wishbone Controlled PWM (audio) controller
6
//
7
// Purpose:     A Verilator driver to demonstrate, off-line, if the PDM
8
//              approach used by wbpwmaudio works.
9
//
10
// Creator:     Dan Gisselquist, Ph.D.
11
//              Gisselquist Technology, LLC
12
//
13
////////////////////////////////////////////////////////////////////////////////
14
//
15
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
16
//
17
// This program is free software (firmware): you can redistribute it and/or
18
// modify it under the terms of the GNU General Public License as published
19
// by the Free Software Foundation, either version 3 of the License, or (at
20
// your option) any later version.
21
//
22
// This program is distributed in the hope that it will be useful, but WITHOUT
23
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
24
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25
// for more details.
26
//
27
// You should have received a copy of the GNU General Public License along
28
// with this program.  (It's in the $(ROOT)/doc directory.  Run make with no
29
// target there if the PDF file isn't present.)  If not, see
30
// <http://www.gnu.org/licenses/> for a copy.
31
//
32
// License:     GPL, v3, as defined and found on www.gnu.org,
33
//              http://www.gnu.org/licenses/gpl.html
34
//
35
//
36
////////////////////////////////////////////////////////////////////////////////
37
//
38
//
39
#include <stdio.h>
40
#include <math.h>
41
 
42
#include <verilated.h>
43
#include <verilated_vcd_c.h>
44
#include "Vtoplevel.h"
45
#include "testb.h"
46
 
47
// Verilator changed their naming convention somewhere around version 3.9 or
48
// so.
49
#ifdef  NEW_VERILATOR
50
#define seq_step        toplevel__DOT__seq_step
51
#else
52
#define seq_step        v__DOT__seq_step
53
#endif
54
 
55
#define CLOCK_RATE_HZ   100000000       // FPGA clock rate
56
#define SAMPLE_RATE_HZ  44100           // Our output (generated) sample rate
57
#define AUDIO_TOP_HZ    22000           // The filter's cutoff frequency
58
 
59
//
60
// We are going to use an overkill filter, generated via the crude manner of
61
// windowing an "ideal" filter.  ("ideal" filters are never "ideal" ... but
62
// that's beside the point.)  The problem with this is that the filter has
63
// arbitrarily way too many taps.  Hence, we'll use a filter with FIRLEN
64
// taps and leave examples with fewer taps for any paying customers.
65
const int       FIRLEN = 65536*8;
66
 
67
//
68
// class PDMDEMO is a wrapper class around the Verilator generated simulation
69
// component.
70
//
71
class   PDMDEMO : public TESTB<Vtoplevel> {
72
        // Resampling filter taps
73
        double          *m_taps;
74
        // Resampler variables, telling us when to produce the next output
75
        double          m_subsample, m_step;
76
        // The file to place these values into
77
        FILE            *m_wavfp;
78
        // Filter memory
79
        int             *m_firmem, m_firpos;
80
public:
81
 
82
        PDMDEMO(void) {
83
                // Our filter cutoff
84
                const   double  fc = (double)AUDIO_TOP_HZ
85
                                        / (double)CLOCK_RATE_HZ;
86
 
87
                // Allocate memory for taps and data values
88
                m_taps = new double[FIRLEN];
89
                m_firmem = new int[FIRLEN];
90
                m_firpos = 0;
91
 
92
                for(int i=0; i<FIRLEN; i++) {
93
                        double  window, t;
94
 
95
                        m_firmem[i] = 0;
96
 
97
                        // Blackman Window --- see Oppenheim and Schafer
98
                        // for more info.
99
                        t = (i+1.0)/(FIRLEN+1.0);
100
                        window = 0.42
101
                                -0.50 * cos(2.0*M_PI*t)
102
                                +0.08 * cos(4.0*M_PI*t);
103
 
104
                        // An "ideal" lowpass filter, with cutoff at fc
105
                        // (fc is in units of normalized frequency)
106
                        t = (i+1.0-FIRLEN/2);
107
                        m_taps[i] = sin(2.0 * M_PI * fc * t) / (M_PI * t);
108
 
109
                        // Apply the window to the filter tap
110
                        m_taps[i] *= window;
111
 
112
                }
113
 
114
                // We'll set the middle tap special, since sin(x)/x isn't
115
                // known for its convergence when x=0.
116
                m_taps[FIRLEN/2-1] = fc;
117
 
118
                // In case you'd like to look at this filter, we'll dump its
119
                // taps to a file.
120
                m_wavfp = fopen("filter.dbl","w");
121
                fwrite(m_taps, sizeof(m_taps[0]), FIRLEN, m_wavfp);
122
                fclose(m_wavfp);
123
 
124
                // Otherwise, everything is going to be dumped to the
125
                // wavfp.dbl file.
126
                m_wavfp = fopen("wavfp.dbl","w");
127
 
128
                // Initialize the values we need to use for determining our
129
                // resample clock.
130
                m_subsample = 0.0;
131
                m_step = (double)SAMPLE_RATE_HZ / (double)CLOCK_RATE_HZ;
132
        }
133
 
134
        ~PDMDEMO(void) {
135
                // Close our output waveform file
136
                if (m_wavfp)
137
                        fclose(m_wavfp);
138
        }
139
 
140
        void    tick(void) {
141
                int     output;
142
 
143
                // Tick the clock.
144
                TESTB<Vtoplevel>::tick();
145
 
146
                // Examine the output
147
                output = TESTB<Vtoplevel>::m_core->o_pwm;
148
 
149
                // If we are writing to an output file (should always be true)
150
                // then ...
151
                if (m_wavfp) {
152
 
153
                        // Turn this output into a "voltage" centered upon
154
                        // zero.  If the output is not shutdown, the voltage
155
                        // will be dependent upon the pins value, and will
156
                        // either be +/- one.  Otherwise, we'll just output a
157
                        // zero value.
158
                        if (m_core->o_shutdown_n)
159
                                output = 2*m_core->o_pwm - 1;
160
                        else
161
                                output = 0;
162
 
163
                        // Add this value to our filter memory, and adjust the
164
                        // pointer to the oldest sample in memory.
165
                        m_firmem[m_firpos++] = output;
166
                        if (m_firpos >= FIRLEN)
167
                                m_firpos = 0;
168
 
169
                        // If it's time to run the filter to calculate a result,
170
                        // ...
171
                        m_subsample += m_step;
172
                        if (m_subsample >= 1.0) {
173
 
174
                                // Then apply the taps from the filter to our
175
                                // data smaples.  Note that there will be some
176
                                // amount of phase noise from using this
177
                                // approach, since it essentially amounts to
178
                                // applying a filter to get an instantaneous
179
                                // output and then using a nearest neighbour
180
                                // interpolator---rather than properly
181
                                // getting any subsample resolution.
182
                                //
183
                                // For our purposes today, this should be good
184
                                // enough.
185
                                double  acc = 0.0;
186
 
187
                                // First run through memory from the oldest
188
                                // value until the end of the buffer
189
                                for(int i=0; i+m_firpos < FIRLEN; i++)
190
                                        acc += m_taps[i] * m_firmem[m_firpos+i];
191
                                // Then continue from the beginning of the
192
                                // buffer to the most recent value.
193
                                for(int i=FIRLEN-m_firpos; i < FIRLEN; i++)
194
                                        acc += m_taps[i] * m_firmem[m_firpos+i-FIRLEN];
195
 
196
                                // Write the output out into a file.
197
                                // The value is of type double.
198
                                fwrite(&acc, sizeof(double), 1, m_wavfp);
199
 
200
                                // Set us up to calculate when the next
201
                                // sample will be at this rate.
202
                                m_subsample -= 1.0;
203
                        }
204
                }
205
        }
206
};
207
 
208
int main(int  argc, char **argv) {
209
        Verilated::commandArgs(argc, argv);
210
        // Create a class containing our design
211
        PDMDEMO *tb = new PDMDEMO;
212
 
213
        // This should really be a command line parameter ...
214
        // Adjust this value to true to produce a traditional PWM output, false
215
        // to produce the "improved" PDM output.
216
        const   bool    traditional_pwm = true;
217
 
218
        printf("\n\n");
219
        if (traditional_pwm) {
220
                printf("Creating the output for a traditional PWM\n");
221
                tb->m_core->i_sw = 0;
222
        } else {
223
                printf("Creating the output for the modified/improved PDM\n");
224
                tb->m_core->i_sw = 1;
225
        }
226
        printf("\n\n");
227
 
228
        // If you want to see a trace from this run, then uncomment the line
229
        // below.  Be aware, the trace file can quickly become many GB in
230
        // length!
231
        //
232
        // tb->opentrace("pdmdemo.vcd");
233
 
234
        //
235
        // Simulate ten seconds of our waveform generator
236
        for(int k=0; k< 10 * CLOCK_RATE_HZ; k++) {
237
 
238
                // Just so we believe its doing something, let's output
239
                // what step we are on, and what frequency is going into the
240
                // frequency generator.
241
                if ((k % (CLOCK_RATE_HZ/1000))==0) {
242
                        double  secs = k / (double)CLOCK_RATE_HZ;
243
                        if (tb->m_core->o_shutdown_n) {
244
                                double  f = tb->m_core->seq_step;
245
                                f = f * CLOCK_RATE_HZ / pow(2,34);
246
                                printf("k = %10d clocks, %5.2f secs, f = %8.1f Hz\n", k, secs, f);
247
                        } else
248
                                printf("k = %10d clocks, %5.2f secs\n", k, secs);
249
 
250
                }
251
 
252
                // Step the simulation forward by a single clock tick
253
                tb->tick();
254
        }
255
 
256
        // Now that we're all done, delete the simulation and exit
257
        delete tb;
258
        exit(EXIT_SUCCESS);
259
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.