URL
https://opencores.org/ocsvn/wbpwmaudio/wbpwmaudio/trunk
Subversion Repositories wbpwmaudio
Compare Revisions
- This comparison shows the changes necessary to convert path
/
- from Rev 3 to Rev 4
- ↔ Reverse comparison
Rev 3 → Rev 4
/wbpwmaudio/trunk/README.md
0,0 → 1,28
# A Wishbone Controlled PWM (audio) controller |
|
This PWM controller was designed with audio in mind, although it should be |
sufficient for many other purposes. Specifically, it creates a pulse-width |
modulated output, where the amount of time the output is 'high' is determined |
by the pulse width data given to it. Further, the 'high' time is spread out in |
bit reversed order. In this fashion, a halfway point will alternate between |
high and low, rather than the normal fashion of being high for half the time |
and then low. This approach was chosen to move the PWM artifacts to higher, |
inaudible frequencies and hence improve the sound quality. |
|
The interface supports two addresses: |
|
- Addr[0] is the data register. Writes to this register will set |
a 16-bit sample value to be produced by the PWM logic. |
Reads will also produce, in the 17th bit, whether the interrupt |
is set or not. (If set, it's time to write a new data value ...) |
|
- Addr[1] is a timer reload value, used to determine how often the PWM logic |
needs its next value. This number should be set to the number of clock cycles |
between reload values. So, for example, an 80 MHz clock can generate a |
44.1 kHz audio stream by reading in a new sample every (80e6/44.1e3 = 1814) |
samples. After loading a sample, the device is immediately ready to load a |
second. Once the first sample completes, the second sample will start going |
to the output, and an interrupt will be generated indicating that the device |
is now ready for the third sample. (The one sample buffer allows some |
flexibility in getting the new sample there fast enough ...) |
|
/wbpwmaudio/trunk/demo-rtl/Makefile
0,0 → 1,88
################################################################################ |
## |
## Filename: rtl/Makefile |
## |
## Project: A Wishbone Controlled PWM (audio) controller |
## |
## Purpose: To direct the Verilator build of the PWM example demo sources. |
## The result is C++ code (built by Verilator), that is then built |
## (herein) into an executable library. |
## |
## Targets: The default target, all, builds the target test, which includes |
## the libraries necessary for Verilator testing. It also builds |
## the pdmdemo target--building a C++ program which can be used to drive |
## this test via Verilator. |
## |
## Creator: Dan Gisselquist, Ph.D. |
## Gisselquist Technology, LLC |
## |
################################################################################ |
## |
## Copyright (C) 2017, Gisselquist Technology, LLC |
## |
## This program is free software (firmware): you can redistribute it and/or |
## modify it under the terms of the GNU General Public License as published |
## by the Free Software Foundation, either version 3 of the License, or (at |
## your option) any later version. |
## |
## This program is distributed in the hope that it will be useful, but WITHOUT |
## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
## for more details. |
## |
## You should have received a copy of the GNU General Public License along |
## with this program. (It's in the $(ROOT)/doc directory. Run make with no |
## target there if the PDF file isn't present.) If not, see |
## <http://www.gnu.org/licenses/> for a copy. |
## |
## License: GPL, v3, as defined and found on www.gnu.org, |
## http://www.gnu.org/licenses/gpl.html |
## |
################################################################################ |
## |
## |
all: test pdmdemo |
FBDIR := . |
VDIRFB:= $(FBDIR)/obj_dir |
|
.PHONY: test |
test: $(VDIRFB)/Vtoplevel__ALL.a |
VOBJ := obj_dir |
SUBMAKE := $(MAKE) --no-print-directory --directory=$(VOBJ) -f |
ifeq ($(VERILATOR_ROOT),) |
VERILATOR := verilator |
VERILATOR_ROOT ?= $(shell bash -c '$(VERILATOR) -V|grep VERILATOR_ROOT | head -1 | sed -e " s/^.*=\s*//"') |
else |
VERILATOR := $(VERILATOR_ROOT)/bin/verilator |
endif |
export $(VERILATOR) |
VROOT := $(VERILATOR_ROOT) |
VINCD := $(VROOT)/include |
VINC := -I$(VINCD) -I$(VINCD)/vlstd -I$(VDIRFB) |
VFLAGS := -Wall -MMD --trace -y ../rtl -cc |
|
$(VDIRFB)/Vtoplevel__ALL.a: $(VDIRFB)/Vcordic.h $(VDIRFB)/Vcordic.cpp |
$(VDIRFB)/Vtoplevel__ALL.a: $(VDIRFB)/Vcordic.mk |
$(VDIRFB)/Vtoplevel.h $(VDIRFB)/Vtoplevel.cpp $(VDIRFB)/Vtoplevel.mk: toplevel.v |
|
$(VDIRFB)/V%.cpp $(VDIRFB)/V%.h $(VDIRFB)/V%.mk: $(FBDIR)/%.v |
$(VERILATOR) $(VFLAGS) $*.v |
|
$(VDIRFB)/V%__ALL.a: $(VDIRFB)/V%.mk |
$(SUBMAKE) V$*.mk |
|
.PHONY: clean |
clean: |
rm -rf $(VDIRFB)/ |
|
pdmdemo: pdmdemo.cpp $(VDIRFB)/Vtoplevel__ALL.a |
g++ -Wall $(VINC) $(VINCD)/verilated.cpp $(VINCD)/verilated_vcd_c.cpp $^ -o pdmdemo |
|
# |
# Note Verilator's dependency created information, and include it here if we |
# can |
DEPS := $(wildcard $(VDIRFB)/*.d) |
|
ifneq ($(DEPS),) |
include $(DEPS) |
endif |
/wbpwmaudio/trunk/demo-rtl/README.md
0,0 → 1,48
# A demonstration of the Improved PWM waveform |
|
This directory contains the files necessary to build a demonstration of the |
[improved PWM](../rtl/wbpwmaudio.v) waveform used in this repository. The |
demonstration is designed to work both in a Verilator based simulation |
(pdmdemo.cpp), as well as with a board having a single PMod AMP2, switch, LED, |
and a 100MHz clock input. |
|
The [toplevel Verilog file](toplevel.v) should be easily recognizable as |
[toplevel.v](toplevel.v). This contains the [cordic.v](cordic.v) component to |
generate a sine wave as the test signal, as well as components to turn this |
test signal into both a [traditional PWM](traditionalpwm.v) output as well as |
the [improved version](../rtl/wbpwmaudio.v) found within this repository. |
The [toplevel design](toplevel.v) will either output a 440 Hz tone, or a swept |
tone running from 110Hz to about 27.3kHz, depending upon which part of the |
test sequence is being run. |
|
The `DEF_RELOAD` parameter within toplevel.v can be used to adjust the PWM |
interval for the [traditional PWM](traditionalpwm.v), or sample interval for |
the [improved version](../rtl/wbpwmaudio.v). Likewise, when run on actual |
hardware, the switch input will control which |
waveform is produced--either the |
[traditional PWM](traditionalpwm.v) or the |
[improved waveform](../rtl/wbpwmaudio.v). |
|
These files can also be composed within a |
[Verilator](https://www.veripool.org/wiki/verilator) based simulation. In this |
case [pdmdemo.cpp](pdmdemo.cpp) forms the main simulation component. A boolean |
value within it, `traditional_pwm`, can be used to control whether or not the |
file downsamples and outputs a [traditional PWM](traditionalpwm.v) signal or |
the [improved version](../rtl/wbpwmaudio.v). Likewise, |
there's a commented line that you can use to generate a VCD file which can be |
used to view traces of the logic within this demo. This demonstration file |
will also produce a file of 64-bit doubles (overkill, I know) containing |
downsampled audio samples at 44.1kHz. This audio file, named wavfp.dbl, can |
be processed within |
[Octave](https://www.gnu.org/software/octave) |
by using [showspectrogram](showspectrogram.m), to view a spectrogram of the |
data. |
|
If you take the time to run the [simulation](pdmdemo.cpp), you'll clearly see |
that the [improved PWM](../rtl/wbpwmaudio.v) waveform is much cleaner: |
it doesn't include unwanted harmonics, nor does it include any artificial |
features (beyond any expected aliasing products, present in both). |
The [traditional PWM](traditionalpwm.v) waveform, on the other hand, cannot |
seem to be able to produce a clear tone without also creating unwanted |
harmonics. This effect is only worsened when the PWM period is within |
hearing range, such as when the period is the reciprocal of 8kHz. |
/wbpwmaudio/trunk/demo-rtl/cordic.v
0,0 → 1,250
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: ../rtl/cordic.v |
// |
// Project: A series of CORDIC related projects |
// |
// Purpose: This file executes a vector rotation on the values |
// (i_xval, i_yval). This vector is rotated left by |
// i_phase. i_phase is given by the angle, in radians, multiplied by |
// 2^32/(2pi). In that fashion, a two pi value is zero just as a zero |
// angle is zero. |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2017, Gisselquist Technology, LLC |
// |
// This program is free software (firmware): you can redistribute it and/or |
// modify it under the terms of the GNU General Public License as published |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
`default_nettype none |
// |
module cordic(i_clk, i_reset, i_ce, i_xval, i_yval, i_phase, i_aux, |
o_xval, o_yval, o_aux); |
localparam IW=16, // The number of bits in our inputs |
OW=16, // The number of output bits to produce |
NSTAGES=21, |
XTRA= 5,// Extra bits for internal precision |
WW=21, // Our working bit-width |
PW=25; // Bits in our phase variables |
input wire i_clk, i_reset, i_ce; |
input wire signed [(IW-1):0] i_xval, i_yval; |
input wire [(PW-1):0] i_phase; |
output reg signed [(OW-1):0] o_xval, o_yval; |
input wire i_aux; |
output reg o_aux; |
// First step: expand our input to our working width. |
// This is going to involve extending our input by one |
// (or more) bits in addition to adding any xtra bits on |
// bits on the right. The one bit extra on the left is to |
// allow for any accumulation due to the cordic gain |
// within the algorithm. |
// |
wire signed [(WW-1):0] e_xval, e_yval; |
assign e_xval = { {i_xval[(IW-1)]}, i_xval, {(WW-IW-1){1'b0}} }; |
assign e_yval = { {i_yval[(IW-1)]}, i_yval, {(WW-IW-1){1'b0}} }; |
|
// Declare variables for all of the separate stages |
reg signed [(WW-1):0] xv [0:(NSTAGES)]; |
reg signed [(WW-1):0] yv [0:(NSTAGES)]; |
reg [(PW-1):0] ph [0:(NSTAGES)]; |
|
// |
// Handle the auxilliary logic. |
// |
// The auxilliary bit is designed so that you can place a valid bit into |
// the CORDIC function, and see when it comes out. While the bit is |
// allowed to be anything, the requirement of this bit is that it *must* |
// be aligned with the output when done. That is, if i_xval and i_yval |
// are input together with i_aux, then when o_xval and o_yval are set |
// to this value, o_aux *must* contain the value that was in i_aux. |
// |
reg [(NSTAGES):0] ax; |
|
always @(posedge i_clk) |
if (i_reset) |
ax <= {(NSTAGES+1){1'b0}}; |
else if (i_ce) |
ax <= { ax[(NSTAGES-1):0], i_aux }; |
|
// First stage, get rid of all but 45 degrees |
// The resulting phase needs to be between -45 and 45 |
// degrees but in units of normalized phase |
always @(posedge i_clk) |
if (i_reset) |
begin |
xv[0] <= 0; |
yv[0] <= 0; |
ph[0] <= 0; |
end else if (i_ce) |
begin |
// Walk through all possible quick phase shifts necessary |
// to constrain the input to within +/- 45 degrees. |
case(i_phase[(PW-1):(PW-3)]) |
3'b000: begin // 0 .. 45, No change |
xv[0] <= e_xval; |
yv[0] <= e_yval; |
ph[0] <= i_phase; |
end |
3'b001: begin // 45 .. 90 |
xv[0] <= -e_yval; |
yv[0] <= e_xval; |
ph[0] <= i_phase - 25'h800000; |
end |
3'b010: begin // 90 .. 135 |
xv[0] <= -e_yval; |
yv[0] <= e_xval; |
ph[0] <= i_phase - 25'h800000; |
end |
3'b011: begin // 135 .. 180 |
xv[0] <= -e_xval; |
yv[0] <= -e_yval; |
ph[0] <= i_phase - 25'h1000000; |
end |
3'b100: begin // 180 .. 225 |
xv[0] <= -e_xval; |
yv[0] <= -e_yval; |
ph[0] <= i_phase - 25'h1000000; |
end |
3'b101: begin // 225 .. 270 |
xv[0] <= e_yval; |
yv[0] <= -e_xval; |
ph[0] <= i_phase - 25'h1800000; |
end |
3'b110: begin // 270 .. 315 |
xv[0] <= e_yval; |
yv[0] <= -e_xval; |
ph[0] <= i_phase - 25'h1800000; |
end |
3'b111: begin // 315 .. 360, No change |
xv[0] <= e_xval; |
yv[0] <= e_yval; |
ph[0] <= i_phase; |
end |
endcase |
end |
|
// |
// In many ways, the key to this whole algorithm lies in the angles |
// necessary to do this. These angles are also our basic reason for |
// building this CORDIC in C++: Verilog just can't parameterize this |
// much. Further, these angle's risk becoming unsupportable magic |
// numbers, hence we define these and set them in C++, based upon |
// the needs of our problem, specifically the number of stages and |
// the number of bits required in our phase accumulator |
// |
wire [24:0] cordic_angle [0:(NSTAGES-1)]; |
|
assign cordic_angle[ 0] = 25'h025_c80a; // 26.565051 deg |
assign cordic_angle[ 1] = 25'h013_f670; // 14.036243 deg |
assign cordic_angle[ 2] = 25'h00a_2223; // 7.125016 deg |
assign cordic_angle[ 3] = 25'h005_161a; // 3.576334 deg |
assign cordic_angle[ 4] = 25'h002_8baf; // 1.789911 deg |
assign cordic_angle[ 5] = 25'h001_45ec; // 0.895174 deg |
assign cordic_angle[ 6] = 25'h000_a2f8; // 0.447614 deg |
assign cordic_angle[ 7] = 25'h000_517c; // 0.223811 deg |
assign cordic_angle[ 8] = 25'h000_28be; // 0.111906 deg |
assign cordic_angle[ 9] = 25'h000_145f; // 0.055953 deg |
assign cordic_angle[10] = 25'h000_0a2f; // 0.027976 deg |
assign cordic_angle[11] = 25'h000_0517; // 0.013988 deg |
assign cordic_angle[12] = 25'h000_028b; // 0.006994 deg |
assign cordic_angle[13] = 25'h000_0145; // 0.003497 deg |
assign cordic_angle[14] = 25'h000_00a2; // 0.001749 deg |
assign cordic_angle[15] = 25'h000_0051; // 0.000874 deg |
assign cordic_angle[16] = 25'h000_0028; // 0.000437 deg |
assign cordic_angle[17] = 25'h000_0014; // 0.000219 deg |
assign cordic_angle[18] = 25'h000_000a; // 0.000109 deg |
assign cordic_angle[19] = 25'h000_0005; // 0.000055 deg |
assign cordic_angle[20] = 25'h000_0002; // 0.000027 deg |
// Std-Dev : 0.00 (Units) |
// Phase Quantization: 0.000001 (Radians) |
// Gain is 1.164435 |
// You can annihilate this gain by multiplying by 32'hdbd95b16 |
// and right shifting by 32 bits. |
|
genvar i; |
generate for(i=0; i<NSTAGES; i=i+1) begin : CORDICops |
always @(posedge i_clk) |
// Here's where we are going to put the actual CORDIC |
// we've been studying and discussing. Everything up to |
// this point has simply been necessary preliminaries. |
if (i_reset) |
begin |
xv[i+1] <= 0; |
yv[i+1] <= 0; |
ph[i+1] <= 0; |
end else if (i_ce) |
begin |
if ((cordic_angle[i] == 0)||(i >= WW)) |
begin // Do nothing but move our outputs |
// forward one stage, since we have more |
// stages than valid data |
xv[i+1] <= xv[i]; |
yv[i+1] <= yv[i]; |
ph[i+1] <= ph[i]; |
end else if (ph[i][(PW-1)]) // Negative phase |
begin |
// If the phase is negative, rotate by the |
// CORDIC angle in a clockwise direction. |
xv[i+1] <= xv[i] + (yv[i]>>>(i+1)); |
yv[i+1] <= yv[i] - (xv[i]>>>(i+1)); |
ph[i+1] <= ph[i] + cordic_angle[i]; |
end else begin |
// On the other hand, if the phase is |
// positive ... rotate in the |
// counter-clockwise direction |
xv[i+1] <= xv[i] - (yv[i]>>>(i+1)); |
yv[i+1] <= yv[i] + (xv[i]>>>(i+1)); |
ph[i+1] <= ph[i] - cordic_angle[i]; |
end |
end |
end endgenerate |
|
// Round our result towards even |
wire [(WW-1):0] pre_xval, pre_yval; |
|
assign pre_xval = xv[NSTAGES] + {{(OW){1'b0}}, |
xv[NSTAGES][(WW-OW)], |
{(WW-OW-1){!xv[NSTAGES][WW-OW]}}}; |
assign pre_yval = yv[NSTAGES] + {{(OW){1'b0}}, |
yv[NSTAGES][(WW-OW)], |
{(WW-OW-1){!yv[NSTAGES][WW-OW]}}}; |
|
always @(posedge i_clk) |
begin |
o_xval <= pre_xval[(WW-1):(WW-OW)]; |
o_yval <= pre_yval[(WW-1):(WW-OW)]; |
o_aux <= ax[NSTAGES]; |
end |
|
// Make Verilator happy with pre_.val |
// verilator lint_off UNUSED |
wire [(2*(WW-OW)-1):0] unused_val; |
assign unused_val = { |
pre_xval[(WW-OW-1):0], |
pre_yval[(WW-OW-1):0] |
}; |
// verilator lint_on UNUSED |
endmodule |
/wbpwmaudio/trunk/demo-rtl/mymap.m
0,0 → 1,61
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% Filename: mymap.m |
%% |
%% Project: A Wishbone Controlled PWM (audio) controller |
%% |
%% Purpose: This file generates my favorite spectrogram color map. The map |
%% is designed so that zero maps to black, and one maps to white. |
%% In between the two extremes, the color goes from black to blue, red, |
%% orange, yellow, and then white. |
%% |
%% The one argument given to the colormap is the number of colors |
%% you would like to have in your map. 64 is often sufficient. |
%% |
%% Creator: Dan Gisselquist, Ph.D. |
%% Gisselquist Technology, LLC |
%% |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% Copyright (C) 2017, Gisselquist Technology, LLC |
%% |
%% This program is free software (firmware): you can redistribute it and/or |
%% modify it under the terms of the GNU General Public License as published |
%% by the Free Software Foundation, either version 3 of the License, or (at |
%% your option) any later version. |
%% |
%% This program is distributed in the hope that it will be useful, but WITHOUT |
%% ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
%% for more details. |
%% |
%% You should have received a copy of the GNU General Public License along |
%% with this program. (It's in the $(ROOT)/doc directory. Run make with no |
%% target there if the PDF file isn't present.) If not, see |
%% <http://www.gnu.org/licenses/> for a copy. |
%% |
%% License: GPL, v3, as defined and found on www.gnu.org, |
%% http://www.gnu.org/licenses/gpl.html |
%% |
%% |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% |
function [map] = mymap(nclrs) |
|
map = zeros(nclrs,3); |
idx = ((1:nclrs)-1)/(nclrs-1); |
|
map(:,1) = 1 - cos((idx-1/3)*3*pi)'; |
map((idx<1/3),1) = 0; |
map((idx>2/3),1) = 1; |
|
map(:,2) = 1 - cos((idx-2/3)*3*pi)'; |
map(idx<2/3,2) = 0; |
|
map(:,3) = 0.5 * (1-cos(idx*3*pi))'; |
map((idx<5/6)&(idx>=2/3),3) = 0; |
map((idx>=5/6),3) = 1-cos((idx(idx>=5/6)-2/3)*1.5*pi)'; |
|
map(map>1.0) = 1.0; |
map(map<0.0) = 0.0; |
/wbpwmaudio/trunk/demo-rtl/pdmdemo.cpp
0,0 → 1,259
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: pdmdemo.cpp |
// |
// Project: A Wishbone Controlled PWM (audio) controller |
// |
// Purpose: A Verilator driver to demonstrate, off-line, if the PDM |
// approach used by wbpwmaudio works. |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2015-2017, Gisselquist Technology, LLC |
// |
// This program is free software (firmware): you can redistribute it and/or |
// modify it under the terms of the GNU General Public License as published |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
#include <stdio.h> |
#include <math.h> |
|
#include <verilated.h> |
#include <verilated_vcd_c.h> |
#include "Vtoplevel.h" |
#include "testb.h" |
|
// Verilator changed their naming convention somewhere around version 3.9 or |
// so. |
#ifdef NEW_VERILATOR |
#define seq_step toplevel__DOT__seq_step |
#else |
#define seq_step v__DOT__seq_step |
#endif |
|
#define CLOCK_RATE_HZ 100000000 // FPGA clock rate |
#define SAMPLE_RATE_HZ 44100 // Our output (generated) sample rate |
#define AUDIO_TOP_HZ 22000 // The filter's cutoff frequency |
|
// |
// We are going to use an overkill filter, generated via the crude manner of |
// windowing an "ideal" filter. ("ideal" filters are never "ideal" ... but |
// that's beside the point.) The problem with this is that the filter has |
// arbitrarily way too many taps. Hence, we'll use a filter with FIRLEN |
// taps and leave examples with fewer taps for any paying customers. |
const int FIRLEN = 65536*8; |
|
// |
// class PDMDEMO is a wrapper class around the Verilator generated simulation |
// component. |
// |
class PDMDEMO : public TESTB<Vtoplevel> { |
// Resampling filter taps |
double *m_taps; |
// Resampler variables, telling us when to produce the next output |
double m_subsample, m_step; |
// The file to place these values into |
FILE *m_wavfp; |
// Filter memory |
int *m_firmem, m_firpos; |
public: |
|
PDMDEMO(void) { |
// Our filter cutoff |
const double fc = (double)AUDIO_TOP_HZ |
/ (double)CLOCK_RATE_HZ; |
|
// Allocate memory for taps and data values |
m_taps = new double[FIRLEN]; |
m_firmem = new int[FIRLEN]; |
m_firpos = 0; |
|
for(int i=0; i<FIRLEN; i++) { |
double window, t; |
|
m_firmem[i] = 0; |
|
// Blackman Window --- see Oppenheim and Schafer |
// for more info. |
t = (i+1.0)/(FIRLEN+1.0); |
window = 0.42 |
-0.50 * cos(2.0*M_PI*t) |
+0.08 * cos(4.0*M_PI*t); |
|
// An "ideal" lowpass filter, with cutoff at fc |
// (fc is in units of normalized frequency) |
t = (i+1.0-FIRLEN/2); |
m_taps[i] = sin(2.0 * M_PI * fc * t) / (M_PI * t); |
|
// Apply the window to the filter tap |
m_taps[i] *= window; |
|
} |
|
// We'll set the middle tap special, since sin(x)/x isn't |
// known for its convergence when x=0. |
m_taps[FIRLEN/2-1] = fc; |
|
// In case you'd like to look at this filter, we'll dump its |
// taps to a file. |
m_wavfp = fopen("filter.dbl","w"); |
fwrite(m_taps, sizeof(m_taps[0]), FIRLEN, m_wavfp); |
fclose(m_wavfp); |
|
// Otherwise, everything is going to be dumped to the |
// wavfp.dbl file. |
m_wavfp = fopen("wavfp.dbl","w"); |
|
// Initialize the values we need to use for determining our |
// resample clock. |
m_subsample = 0.0; |
m_step = (double)SAMPLE_RATE_HZ / (double)CLOCK_RATE_HZ; |
} |
|
~PDMDEMO(void) { |
// Close our output waveform file |
if (m_wavfp) |
fclose(m_wavfp); |
} |
|
void tick(void) { |
int output; |
|
// Tick the clock. |
TESTB<Vtoplevel>::tick(); |
|
// Examine the output |
output = TESTB<Vtoplevel>::m_core->o_pwm; |
|
// If we are writing to an output file (should always be true) |
// then ... |
if (m_wavfp) { |
|
// Turn this output into a "voltage" centered upon |
// zero. If the output is not shutdown, the voltage |
// will be dependent upon the pins value, and will |
// either be +/- one. Otherwise, we'll just output a |
// zero value. |
if (m_core->o_shutdown_n) |
output = 2*m_core->o_pwm - 1; |
else |
output = 0; |
|
// Add this value to our filter memory, and adjust the |
// pointer to the oldest sample in memory. |
m_firmem[m_firpos++] = output; |
if (m_firpos >= FIRLEN) |
m_firpos = 0; |
|
// If it's time to run the filter to calculate a result, |
// ... |
m_subsample += m_step; |
if (m_subsample >= 1.0) { |
|
// Then apply the taps from the filter to our |
// data smaples. Note that there will be some |
// amount of phase noise from using this |
// approach, since it essentially amounts to |
// applying a filter to get an instantaneous |
// output and then using a nearest neighbour |
// interpolator---rather than properly |
// getting any subsample resolution. |
// |
// For our purposes today, this should be good |
// enough. |
double acc = 0.0; |
|
// First run through memory from the oldest |
// value until the end of the buffer |
for(int i=0; i+m_firpos < FIRLEN; i++) |
acc += m_taps[i] * m_firmem[m_firpos+i]; |
// Then continue from the beginning of the |
// buffer to the most recent value. |
for(int i=FIRLEN-m_firpos; i < FIRLEN; i++) |
acc += m_taps[i] * m_firmem[m_firpos+i-FIRLEN]; |
|
// Write the output out into a file. |
// The value is of type double. |
fwrite(&acc, sizeof(double), 1, m_wavfp); |
|
// Set us up to calculate when the next |
// sample will be at this rate. |
m_subsample -= 1.0; |
} |
} |
} |
}; |
|
int main(int argc, char **argv) { |
Verilated::commandArgs(argc, argv); |
// Create a class containing our design |
PDMDEMO *tb = new PDMDEMO; |
|
// This should really be a command line parameter ... |
// Adjust this value to true to produce a traditional PWM output, false |
// to produce the "improved" PDM output. |
const bool traditional_pwm = true; |
|
printf("\n\n"); |
if (traditional_pwm) { |
printf("Creating the output for a traditional PWM\n"); |
tb->m_core->i_sw = 0; |
} else { |
printf("Creating the output for the modified/improved PDM\n"); |
tb->m_core->i_sw = 1; |
} |
printf("\n\n"); |
|
// If you want to see a trace from this run, then uncomment the line |
// below. Be aware, the trace file can quickly become many GB in |
// length! |
// |
// tb->opentrace("pdmdemo.vcd"); |
|
// |
// Simulate ten seconds of our waveform generator |
for(int k=0; k< 10 * CLOCK_RATE_HZ; k++) { |
|
// Just so we believe its doing something, let's output |
// what step we are on, and what frequency is going into the |
// frequency generator. |
if ((k % (CLOCK_RATE_HZ/1000))==0) { |
double secs = k / (double)CLOCK_RATE_HZ; |
if (tb->m_core->o_shutdown_n) { |
double f = tb->m_core->seq_step; |
f = f * CLOCK_RATE_HZ / pow(2,34); |
printf("k = %10d clocks, %5.2f secs, f = %8.1f Hz\n", k, secs, f); |
} else |
printf("k = %10d clocks, %5.2f secs\n", k, secs); |
|
} |
|
// Step the simulation forward by a single clock tick |
tb->tick(); |
} |
|
// Now that we're all done, delete the simulation and exit |
delete tb; |
exit(EXIT_SUCCESS); |
} |
/wbpwmaudio/trunk/demo-rtl/showspectrogram.m
0,0 → 1,101
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% Filename: showspectrogram.m |
%% |
%% Project: A Wishbone Controlled PWM (audio) controller |
%% |
%% Purpose: To generate a spectrogram image which can then be used to |
%% evaluate the wavfp.dbl file produced by the pdmdemo executable |
%% in this directory. |
%% |
%% This script has been tested with Octave, and it is known to work with |
%% Octave. While it may work within Matlab as well, no representation is |
%% being made to that effect. |
%% |
%% Creator: Dan Gisselquist, Ph.D. |
%% Gisselquist Technology, LLC |
%% |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% Copyright (C) 2017, Gisselquist Technology, LLC |
%% |
%% This program is free software (firmware): you can redistribute it and/or |
%% modify it under the terms of the GNU General Public License as published |
%% by the Free Software Foundation, either version 3 of the License, or (at |
%% your option) any later version. |
%% |
%% This program is distributed in the hope that it will be useful, but WITHOUT |
%% ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
%% for more details. |
%% |
%% You should have received a copy of the GNU General Public License along |
%% with this program. (It's in the $(ROOT)/doc directory. Run make with no |
%% target there if the PDF file isn't present.) If not, see |
%% <http://www.gnu.org/licenses/> for a copy. |
%% |
%% License: GPL, v3, as defined and found on www.gnu.org, |
%% http://www.gnu.org/licenses/gpl.html |
%% |
%% |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
%% |
%% |
function [img] = showspectrogram(fname) |
% Read the waveform into memory |
fid = fopen(fname,'r'); |
dat = fread(fid, inf, 'double'); |
fclose(fid); |
|
% You can plot it at this point if you would like |
% plot(dat); |
|
sample_rate = 44.1e3; |
window_length = 512; % Size of the FFT |
Nc = 256; % Number of colors |
|
h = hanning(window_length); % About 1 ms |
rows = (length(dat)-length(h)) / length(h) * 2 |
img = zeros(length(h)/2, rows); |
size(img) |
|
for n=1:rows |
cut = dat((1:length(h)) + (n-1)*length(h)/2); |
cutf = abs(fft(cut.*h)).^2; |
img(:,n) = cutf(1:(length(h)/2)); |
end |
|
mintime = 0; |
maxtime = rows / sample_rate * window_length / 2; |
minfreq = 0; |
maxfreq = 0.5 * sample_rate; |
|
% Normalize the image so that the maximum value is one. |
img = img ./ max(max(img)); |
|
% |
% Turn out spectrogram image into dB |
% |
% Adding 1e-8 is useful for artificially forcing the floor of the |
% image to be at a particular value, as well as for keeping the log |
% from reporting values too negative to plot successfully. |
img = 10 * log(img + 1e-8)/log(10); |
|
img = (img + 80)/80; |
colormap(mymap(256)); |
image([mintime, maxtime], [minfreq, maxfreq/1e3], img * Nc); |
ylabel('Frequency (kHz)'); |
xlabel('Time (s)'); |
|
% Trim this image output to the relevant portion of the output |
axis([0, 6.2, 0, maxfreq/1e3]); |
|
% For the plot, comment out the img ./ max(max(img)) line, as well as the |
% img = (img+80)/80 line. Then you can call this as: |
% img = showspectrogram('pdm8k-weak.dbl'); |
% imgpwm = showspectrogram('pwm8k-weak.dbl'); |
% |
% freq = (0:(window_length-1))/(window_length)*(sample_rate/2/1e3) |
% plot(freq,img(:,1400),'b;PDM;',freq,imgpwm(:,1400),'r;PWM;'); |
% grid on; xlabel('Frequency (kHz)'); ylabel('Volume (dB)'); |
% axis([0,22,-70,50]); |
/wbpwmaudio/trunk/demo-rtl/testb.h
0,0 → 1,117
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: testb.h |
// |
// Project: A Wishbone Controlled PWM (audio) controller |
// |
// Purpose: A wrapper for a common interface to a clocked FPGA core |
// begin exercised in Verilator. |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2015,2017, Gisselquist Technology, LLC |
// |
// This program is free software (firmware): you can redistribute it and/or |
// modify it under the terms of the GNU General Public License as published |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
#ifndef TESTB_H |
#define TESTB_H |
|
#include <stdio.h> |
#include <stdint.h> |
#include <verilated_vcd_c.h> |
|
#define TBASSERT(TB,A) do { if (!(A)) { (TB).closetrace(); } assert(A); } while(0); |
|
template <class VA> class TESTB { |
public: |
VA *m_core; |
VerilatedVcdC* m_trace; |
unsigned long m_tickcount; |
|
TESTB(void) : m_trace(NULL), m_tickcount(0l) { |
m_core = new VA; |
Verilated::traceEverOn(true); |
m_core->i_clk = 0; |
eval(); // Get our initial values set properly. |
} |
virtual ~TESTB(void) { |
closetrace(); |
delete m_core; |
m_core = NULL; |
} |
|
virtual void opentrace(const char *vcdname) { |
if (!m_trace) { |
m_trace = new VerilatedVcdC; |
m_core->trace(m_trace, 99); |
m_trace->open(vcdname); |
} |
} |
|
virtual void closetrace(void) { |
if (m_trace) { |
m_trace->close(); |
delete m_trace; |
m_trace = NULL; |
} |
} |
|
virtual void eval(void) { |
m_core->eval(); |
} |
|
virtual void tick(void) { |
m_tickcount++; |
|
// Make sure we have our evaluations straight before the top |
// of the clock. This is necessary since some of the |
// connection modules may have made changes, for which some |
// logic depends. This forces that logic to be recalculated |
// before the top of the clock. |
eval(); |
if (m_trace) m_trace->dump(10*m_tickcount-2); |
m_core->i_clk = 1; |
eval(); |
if (m_trace) m_trace->dump(10*m_tickcount); |
m_core->i_clk = 0; |
eval(); |
if (m_trace) { |
m_trace->dump(10*m_tickcount+5); |
m_trace->flush(); |
} |
} |
|
virtual void reset(void) { |
tick(); |
// printf("RESET\n"); |
} |
|
unsigned long tickcount(void) { |
return m_tickcount; |
} |
}; |
|
#endif |
/wbpwmaudio/trunk/demo-rtl/toplevel.v
0,0 → 1,295
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: toplevel.v |
// |
// Project: A Wishbone Controlled PWM (audio) controller |
// |
// Purpose: This is the top level file in Verilog demonstration illustrating |
// the differences between a traditional PWM generated audio |
// output signal, generated by traditionalpwm.v, and a less conventional |
// PWM generation method more appropriately termed PDM provided by |
// wbpwmaudio.v. |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2017, Gisselquist Technology, LLC |
// |
// This program is free software (firmware): you can redistribute it and/or |
// modify it under the terms of the GNU General Public License as published |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
`default_nettype none |
// |
module toplevel(i_clk, i_sw, o_led, o_shutdown_n, o_gain, o_pwm); |
input wire i_clk, i_sw; |
output wire o_led; |
output wire o_shutdown_n, o_gain; |
output reg o_pwm; |
|
// 8 second repeating test sequence |
// |
// 0: Off |
// 1: Off |
// 2: 440 Hz |
// 3: 440 Hz (continued) |
// 4: off |
// 5: Sweep, starting at 110Hz |
// 6: Sweep (continued) |
// 7: Sweep continued, but with a small discontinuity |
reg [36:0] test_params [0:7]; |
reg [36:0] params; |
initial begin |
// Sweep starts from 110 Hz |
test_params[0] = { 2'b11, |
// Start at 110 Hz |
34'd18_898, // 0x49d2 |
// Sweep |
1'b1 }; |
test_params[1] = { 2'b11, 34'd158_1398, 1'b1 }; |
test_params[2] = { 2'b11, 34'd314_3898, 1'b1 }; |
// One second of silence |
test_params[3] = { 2'b00, 34'd0, 1'b0 }; |
// 440 Hz tone for two seconds |
test_params[4] = { 2'b11, |
// 440Hz Tone frequency / 100MHz clock rate * 2^34 |
34'd75_591, |
// No sweep rate |
1'b0 }; |
test_params[5] = { 2'b11, 34'd75_591, 1'b0 }; |
// Off again for two seconds prior to the next step |
test_params[6] = { 2'b00, 34'd0, 1'b0 }; |
test_params[7] = { 2'b00, 34'd0, 1'b0 }; |
end |
|
reg seq_sweep; |
reg [1:0] seq_aux; |
reg [33:0] seq_step; |
reg [31:0] sub_second_counter; |
reg next_second; |
|
|
// We'll step through the sequence once per second. Hence, our |
// full test-sequence will take 8-seconds. |
// |
// Artificially start just before the top of a second |
initial next_second = 0; |
initial sub_second_counter = 32'hffff_fff0; |
always @(posedge i_clk) |
// 32'd43 is taken from 2^32 / CLOCK_FREQUENCY and then rounded |
// to the nearest integer. Overflows set next_second for one |
// clock period only. |
{ next_second, sub_second_counter } |
<= sub_second_counter + 32'd43; |
|
// Artificially start at the beginning of the frequency sweep. |
reg test_reset; |
reg [2:0] test_state; |
initial test_state = 3'b000; |
always @(posedge i_clk) |
// Advance the test on the top of each second |
if (next_second) |
test_state <= test_state + 1'b1; |
|
// At the top of the last second, apply a reset to everything, so we |
// know we come back to the same values. In reality, all this signal |
// is going to do is to make sure our two components under test start |
// in the exact same configuration. |
always @(posedge i_clk) |
test_reset <= (next_second)&&(test_state == 3'h7); |
|
// Read our parameters for the next test. These will be applied at the |
// top of the next second. |
always @(posedge i_clk) |
params <= test_params[test_state]; |
|
reg sub_sweep_overflow; |
reg [5:0] sub_sweep; |
initial sub_sweep = 0; |
initial sub_sweep_overflow = 0; |
always @(posedge i_clk) |
if (next_second) |
begin |
// seq_aux controls the gain and shutdown values |
seq_aux <= params[36:35]; |
// seq_step controls our output frequency. It's |
// defined as a step in phase |
seq_step <= params[34:1]; |
// seq_sweep is a single bit indicating whether or not |
// we are sweeping or not. |
seq_sweep <= params[0]; |
end else begin |
// At each second, add one to the frequency if our |
// sweep step counter overflowed. |
seq_step <= seq_step + {{(33){1'b0}}, sub_sweep_overflow}; |
|
// Calculate a sweep step counter. This is a six |
// bit counter (0...63). When it overflows, we'll |
// increase our phase step amount (seq_step) |
{ sub_sweep_overflow, sub_sweep } <= sub_sweep + { {(5){1'b0}}, (seq_sweep) }; |
end |
|
// Generating either a tone or a swept tone requires a phase, which we |
// can then use to calculate a sin() and cos(). The change in this |
// phase value is proportional to the frequency of this phase. |
reg [33:0] test_phase; |
always @(posedge i_clk) |
test_phase <= test_phase + seq_step; |
|
// We're not going to use all of the capabilities of our CORDIC |
// sin() and cos() generator. In particular, we're going to ignore |
// the sin() output and the aux synchronization output. Since |
// V*rilator will otherwise complain about this if we turn -Wall on |
// (Thank you, verilator), we'll tell it during the definition of |
// these values that we aren't going to use them. |
// |
// verilator lint_off UNUSED |
wire ignore_aux; |
wire [15:0] geny; |
// verilator lint_on UNUSED |
|
// The result we want from the cordic, though, is the cos() result. |
// this will be used. |
wire signed [15:0] signal; |
|
// Here's a straight CORDIC. The inputs to this should be fairly |
// self-explanatory, with the exception of the 16'h6de0. This value |
// is the gain necessary to cause the CORDIC to produce a full-range |
// output value. This gain is calculated internal to the CORDIC |
// core generator, but can also be found within the cordic.v code |
// following the cordic_angle[] array initialization. In our case, |
// we've taken the top 15-bits of the gain annihilator, prepended |
// a zero sign bit, and used that for our value here. See the |
// discussion on CORDIC's, and the CORDIC file for more info. |
// |
// The value was dropped from 16'h6dec to 16'h6de8 to provide some |
// cushion against overflow. |
localparam [15:0] FULL_SCALE = 16'h6de8, |
SMALL_SCALE= 16'h006e; |
localparam [15:0] ACTUAL_SCALE = SMALL_SCALE; |
cordic gentone(i_clk, test_reset, 1'b1, ACTUAL_SCALE, 16'h0, |
test_phase[33:9], 1'b0, signal, geny, ignore_aux); |
|
// Our goal is to calculate two outputs: one from the PDM, one from |
// the PWM. |
wire pdm_out, pwm_out; |
|
// verilator lint_off UNUSED |
wire ignore_ack, ignore_stall, ignore_int, |
trad_ack, trad_stall, trad_int; |
wire [31:0] ignore_data, trad_data; |
wire ignore_pwm_aux, trad_pwm_aux; |
// verilator lint_on UNUSED |
|
// Here's our component under test. |
// |
// DEFAULT_RELOAD of 3125 comes from a request to do 32kHz sampling. |
// 100e6/32e3 = 3125 |
// VARIABLE_RATE(0) keeps us from needing to adjust (or set) this |
// sampling rate for this test |
// The NAUX value is normally used to control the shutdown and gain |
// pins. Since we're handling theses via another fashion, we'll just |
// set this to (1) and ignore the aux pins. |
// |
// As for the wishbone interface, if all we do is set CYC, STB, and WE |
// then the component will accept a value anytime it is ready for the |
// next sample. This allows us to ignore the rest of the wishbone |
// interface (ack, stall, data, etc.) Finally, we'll also ignore the |
// interrupt output from the core simply because we don't need it for |
// this test setup. |
// |
localparam signed [15:0] DR_44_1KHZ = 16'd2268, |
DR_32KHZ = 16'd3125, |
DR_8KHZ = 16'd12500; |
localparam [15:0] DEF_RELOAD = DR_8KHZ; |
localparam signed [15:0] HALF_DR = DEF_RELOAD[15:1] - 3; |
|
wbpwmaudio #(.DEFAULT_RELOAD(DEF_RELOAD), |
.VARIABLE_RATE(0), |
.NAUX(1)) |
genpwm(i_clk, test_reset, 1'b1, 1'b1, 1'b1, 1'b0, |
{ 16'h0, signal[14:0], 1'b0 }, |
ignore_ack, ignore_stall, ignore_data, |
pdm_out, ignore_pwm_aux, ignore_int); |
|
// |
// Now for the control. |
// |
// The hypothesis under test is that the waveform above will more |
// faithfully generate an audio signal (tone or swept tone) than this |
// control signal does. To make this claim, we need to compare it |
// against something. Hence, our traditional PWM component. |
|
// One of the difficulties of traditional PWM signals is that they |
// can only handle values between zero and the number of clocks in |
// their period. Hence, we'll multiply this by just less than half |
// of 3125 to get us into the range of -3125/2 to 3125/2. Inside the |
// component, 3125/2 will be added to the value. Because 3125/2 isn't |
// an integer, this will produce a DC bias. |
reg signed [31:0] scaled_signal; |
always @(posedge i_clk) |
scaled_signal <= signal * HALF_DR; |
|
// Since we're only going to use the top 16-bits, let's tell V*rilator |
// that the bottom sixteen bits are unused. |
// |
// Well, ... not quite. We're only using bits 29:14. Hence we need |
// to tell Verilator that the other bits are unused. Since adjusting |
// for the scale factor, the number of "unused" bits declared below is a |
// touch more than the actual unused bits, but this should just allow |
// us to modify things without Verilator complaining at us. |
// |
// verilator lint_off UNUSED |
wire [17:0] unused; |
assign unused = { scaled_signal[31:30], scaled_signal[15:0] }; |
// verilator lint_on UNUSED |
|
// |
// Here's the traditional PWM component. It's interface is nearly |
// identical to the component above. |
traditionalpwm #(.DEFAULT_RELOAD(DEF_RELOAD), |
.VARIABLE_RATE(0), .NAUX(1)) |
regpwm(i_clk, test_reset, 1'b1, 1'b1, 1'b1, 1'b0, |
{ 16'h0, scaled_signal[29:14] }, |
trad_ack, trad_stall, trad_data, |
pwm_out, trad_pwm_aux, trad_int); |
|
|
// As a last step, use the switch to control which value actually goes |
// out the output port. |
always @(posedge i_clk) |
if (i_sw) |
o_pwm <= pdm_out; |
else |
o_pwm <= pwm_out; |
|
// Turn the LED "on" if we are producing the improved signal, leave |
// it off otherwise. |
assign o_led = i_sw; |
|
// Finally, use the seq_aux values to control the two audio control |
// pins. |
assign o_shutdown_n = seq_aux[1]; |
assign o_gain = seq_aux[0]; |
|
endmodule |
/wbpwmaudio/trunk/demo-rtl/traditionalpwm.v
0,0 → 1,191
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: traditionalpwm.v |
// |
// Project: A Wishbone Controlled PWM (audio) controller |
// |
// Purpose: |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2015, Gisselquist Technology, LLC |
// |
// This program is free software (firmware): you can redistribute it and/or |
// modify it under the terms of the GNU General Public License as published |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
`default_nettype none |
// |
module traditionalpwm(i_clk, i_reset, |
// Wishbone interface |
i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, |
o_wb_ack, o_wb_stall, o_wb_data, |
o_pwm, o_aux, o_int); |
parameter DEFAULT_RELOAD = 16'd1814, // about 44.1 kHz @ 80MHz |
//DEFAULT_RELOAD = 16'd2268,//about 44.1 kHz @ 100MHz |
NAUX=2, // Dev control values |
VARIABLE_RATE=0, |
TIMING_BITS=16; |
input wire i_clk, i_reset; |
input wire i_wb_cyc, i_wb_stb, i_wb_we; |
input wire i_wb_addr; |
input wire [31:0] i_wb_data; |
output reg o_wb_ack; |
output wire o_wb_stall; |
output wire [31:0] o_wb_data; |
output reg o_pwm; |
output reg [(NAUX-1):0] o_aux; |
output wire o_int; |
|
|
// How often shall we create an interrupt? Every reload_value clocks! |
// If VARIABLE_RATE==0, this value will never change and will be kept |
// at the default reload rate (defined up top) |
wire [(TIMING_BITS-1):0] w_reload_value; |
generate |
if (VARIABLE_RATE != 0) |
begin |
reg [(TIMING_BITS-1):0] r_reload_value; |
initial r_reload_value = DEFAULT_RELOAD; |
always @(posedge i_clk) // Data write |
if ((i_wb_stb)&&(i_wb_addr)&&(i_wb_we)) |
r_reload_value <= i_wb_data[(TIMING_BITS-1):0]-1'b1; |
assign w_reload_value = r_reload_value; |
end else begin |
assign w_reload_value = DEFAULT_RELOAD; |
end endgenerate |
|
// |
// The next value timer |
// |
// We'll want a new sample every w_reload_value clocks. When the |
// timer hits zero, the signal ztimer (zero timer) will also be |
// set--allowing following logic to depend upon it. |
// |
reg ztimer; |
reg [(TIMING_BITS-1):0] timer; |
initial timer = DEFAULT_RELOAD; |
initial ztimer= 1'b0; |
always @(posedge i_clk) |
if (i_reset) |
ztimer <= 1'b0; |
else |
ztimer <= (timer == { {(TIMING_BITS-1){1'b0}}, 1'b1 }); |
|
always @(posedge i_clk) |
if ((ztimer)||(i_reset)) |
timer <= w_reload_value; |
else |
timer <= timer - {{(TIMING_BITS-1){1'b0}},1'b1}; |
|
// |
// Whenever the timer runs out, accept the next value from the single |
// sample buffer. |
// |
reg [15:0] sample_out; |
always @(posedge i_clk) |
if (ztimer) |
sample_out <= next_sample; |
|
|
// |
// Control what's in the single sample buffer, next_sample, as well as |
// whether or not it's a valid sample. Specifically, if next_valid is |
// false, then the sample buffer needs a new value. Once the buffer |
// has a value within it, further writes will just quietly overwrite |
// this value. |
reg [15:0] next_sample; |
reg next_valid; |
initial next_valid = 1'b1; |
initial next_sample = 16'h8000; |
always @(posedge i_clk) // Data write |
if ((i_wb_stb)&&(i_wb_we) |
&&((!i_wb_addr)||(VARIABLE_RATE==0))) |
begin |
// We get a two's complement data from the bus. |
// Convert it here to an unsigned binary offset |
// representation |
next_sample <= i_wb_data[15:0] + { 1'b0, w_reload_value[15:1] } + 1'b1; |
next_valid <= 1'b1; |
if (i_wb_data[16]) |
o_aux <= i_wb_data[(NAUX+20-1):20]; |
end else if (ztimer) |
next_valid <= 1'b0; |
|
assign o_int = (!next_valid); |
|
reg [15:0] pwm_counter; |
initial pwm_counter = 16'h00; |
always @(posedge i_clk) |
if (ztimer) |
pwm_counter <= 0; |
else |
pwm_counter <= pwm_counter + 1'b1; |
|
always @(posedge i_clk) |
o_pwm <= (sample_out >= pwm_counter); |
|
// |
// Handle the bus return traffic. |
generate |
if (VARIABLE_RATE == 0) |
begin |
// If we are running off of a fixed rate, then just return |
// the current setting of the aux registers, the current |
// interrupt value, and the current sample we are outputting. |
assign o_wb_data = { {(12-NAUX){1'b0}}, o_aux, |
3'h0, o_int, sample_out }; |
end else begin |
// On the other hand, if we have been built to support a |
// variable sample rate, then return the reload value for |
// address one but otherwise the data value (above) for address |
// zero. |
reg [31:0] r_wb_data; |
always @(posedge i_clk) |
if (i_wb_addr) |
r_wb_data <= { (32-TIMING_BITS),w_reload_value}; |
else |
r_wb_data <= { {(12-NAUX){1'b0}}, o_aux, |
3'h0, o_int, sample_out }; |
assign o_wb_data = r_wb_data; |
end endgenerate |
|
// Always ack on the clock following any request |
initial o_wb_ack = 1'b0; |
always @(posedge i_clk) |
o_wb_ack <= (i_wb_stb); |
|
// Never stall |
assign o_wb_stall = 1'b0; |
|
// Make Verilator happy. Since we aren't using all of the bits from |
// the bus, Verilator -Wall will complain. This just informs |
// V*rilator that we already know these bits aren't being used. |
// |
// verilator lint_off UNUSED |
wire [14:0] unused; |
assign unused = { i_wb_cyc, i_wb_data[31:21], i_wb_data[19:17] }; |
// verilator lint_on UNUSED |
|
endmodule |
/wbpwmaudio/trunk/demo-rtl/vversion.sh
0,0 → 1,65
#!/bin/bash |
################################################################################ |
## |
## Filename: vversion.sh |
## |
## Project: A Wishbone Controlled PWM (audio) controller |
## |
## Purpose: To determine whether or not the verilator prefix for internal |
## variables is v__DOT__ or the name of the top level followed by |
## __DOT__. If it is the later, output -DNEW_VERILATOR, else be silent. |
## |
## |
## Creator: Dan Gisselquist, Ph.D. |
## Gisselquist Technology, LLC |
## |
################################################################################ |
## |
## Copyright (C) 2017, Gisselquist Technology, LLC |
## |
## This program is free software (firmware): you can redistribute it and/or |
## modify it under the terms of the GNU General Public License as published |
## by the Free Software Foundation, either version 3 of the License, or (at |
## your option) any later version. |
## |
## This program is distributed in the hope that it will be useful, but WITHOUT |
## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
## for more details. |
## |
## You should have received a copy of the GNU General Public License along |
## with this program. (It's in the $(ROOT)/doc directory. Run make with no |
## target there if the PDF file isn't present.) If not, see |
## <http://www.gnu.org/licenses/> for a copy. |
## |
## License: GPL, v3, as defined and found on www.gnu.org, |
## http://www.gnu.org/licenses/gpl.html |
## |
## |
################################################################################ |
## |
## |
if [[ -x ${VERILATOR_ROOT}/bin/verilator ]]; |
then |
export VERILATOR=${VERILATOR_ROOT}/bin/verilator |
fi |
if [[ ! -x ${VERILATOR} ]]; |
then |
export VERILATOR=verilator |
fi |
if [[ ! -x `which ${VERILATOR}` ]]; |
then |
echo "Verilator not found in environment or in path" |
exit -1 |
fi |
|
VVERLINE=`${VERILATOR} -V | grep -i ^Verilator` |
VVER=`echo ${VVERLINE} | cut -d " " -f 2` |
LATER=`echo $VVER \>= 3.9 | bc` |
if [[ $LATER > 0 ]]; |
then |
echo "-DNEW_VERILATOR" |
else |
echo "-DOLD_VERILATOR" |
fi |
exit 0 |
wbpwmaudio/trunk/demo-rtl/vversion.sh
Property changes :
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: wbpwmaudio/trunk/demo-rtl
===================================================================
--- wbpwmaudio/trunk/demo-rtl (nonexistent)
+++ wbpwmaudio/trunk/demo-rtl (revision 4)
wbpwmaudio/trunk/demo-rtl
Property changes :
Added: svn:ignore
## -0,0 +1,4 ##
+obj_dir
+pdmdemo
+*.vcd
+*.dbl
Index: wbpwmaudio/trunk/rtl/wbpwmaudio.v
===================================================================
--- wbpwmaudio/trunk/rtl/wbpwmaudio.v (revision 3)
+++ wbpwmaudio/trunk/rtl/wbpwmaudio.v (revision 4)
@@ -1,7 +1,7 @@
-///////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
//
// Filename: wbpwmaudio.v
-//
+//
// Project: A Wishbone Controlled PWM (audio) controller
//
// Purpose: This PWM controller was designed with audio in mind, although
@@ -22,7 +22,7 @@
// is set or not. (If set, it's time to write a new data value
// ...)
//
-// Addr[1] is a timer reload value, used to determine how often the
+// Addr[1] is a timer reload value, used to determine how often the
// PWM logic needs its next value. This number should be set
// to the number of clock cycles between reload values. So,
// for example, an 80 MHz clock can generate a 44.1 kHz audio
@@ -53,12 +53,12 @@
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
-///////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as published
+// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
@@ -76,27 +76,31 @@
// http://www.gnu.org/licenses/gpl.html
//
//
-///////////////////////////////////////////////////////////////////////////
-module wbpwmaudio(i_clk,
+////////////////////////////////////////////////////////////////////////////////
+//
+//
+`default_nettype none
+//
+module wbpwmaudio(i_clk, i_reset,
// Wishbone interface
i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
o_wb_ack, o_wb_stall, o_wb_data,
o_pwm, o_aux, o_int);
- parameter DEFAULT_RELOAD = 17'd1814, // about 44.1 kHz @ 80MHz
- //DEFAULT_RELOAD = 32'd2268,//about 44.1 kHz @ 100MHz
+ parameter DEFAULT_RELOAD = 16'd1814, // about 44.1 kHz @ 80MHz
+ //DEFAULT_RELOAD = 16'd2268,//about 44.1 kHz @ 100MHz
NAUX=2, // Dev control values
- VARIABLE_RATE=0,
- TIMING_BITS=17;
- input i_clk;
- input i_wb_cyc, i_wb_stb, i_wb_we;
- input i_wb_addr;
- input [31:0] i_wb_data;
+ VARIABLE_RATE=0,
+ TIMING_BITS=16;
+ input wire i_clk, i_reset;
+ input wire i_wb_cyc, i_wb_stb, i_wb_we;
+ input wire i_wb_addr;
+ input wire [31:0] i_wb_data;
output reg o_wb_ack;
output wire o_wb_stall;
output wire [31:0] o_wb_data;
output reg o_pwm;
output reg [(NAUX-1):0] o_aux;
- output reg o_int;
+ output wire o_int;
// How often shall we create an interrupt? Every reload_value clocks!
@@ -109,53 +113,90 @@
reg [(TIMING_BITS-1):0] r_reload_value;
initial r_reload_value = DEFAULT_RELOAD;
always @(posedge i_clk) // Data write
- if ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_addr)&&(i_wb_we))
- r_reload_value <= i_wb_data[(TIMING_BITS-1):0];
+ if ((i_wb_stb)&&(i_wb_addr)&&(i_wb_we))
+ r_reload_value <= i_wb_data[(TIMING_BITS-1):0] - 1'b1;
assign w_reload_value = r_reload_value;
end else begin
assign w_reload_value = DEFAULT_RELOAD;
end endgenerate
+ //
+ // The next value timer
+ //
+ // We'll want a new sample every w_reload_value clocks. When the
+ // timer hits zero, the signal ztimer (zero timer) will also be
+ // set--allowing following logic to depend upon it.
+ //
+ reg ztimer;
reg [(TIMING_BITS-1):0] timer;
initial timer = DEFAULT_RELOAD;
+ initial ztimer= 1'b0;
always @(posedge i_clk)
- if (timer == 0)
+ if (i_reset)
+ ztimer <= 1'b0;
+ else
+ ztimer <= (timer == { {(TIMING_BITS-1){1'b0}}, 1'b1 });
+
+ always @(posedge i_clk)
+ if ((ztimer)||(i_reset))
timer <= w_reload_value;
else
timer <= timer - {{(TIMING_BITS-1){1'b0}},1'b1};
+ //
+ // Whenever the timer runs out, accept the next value from the single
+ // sample buffer.
+ //
reg [15:0] sample_out;
always @(posedge i_clk)
- if (timer == 0)
+ if (ztimer)
sample_out <= next_sample;
+ //
+ // Control what's in the single sample buffer, next_sample, as well as
+ // whether or not it's a valid sample. Specifically, if next_valid is
+ // false, then the sample buffer needs a new value. Once the buffer
+ // has a value within it, further writes will just quietly overwrite
+ // this value.
reg [15:0] next_sample;
reg next_valid;
initial next_valid = 1'b1;
initial next_sample = 16'h8000;
always @(posedge i_clk) // Data write
- if ((i_wb_cyc)&&(i_wb_stb)&&(i_wb_we)
- &&((~i_wb_addr)||(VARIABLE_RATE==0)))
+ if ((i_wb_stb)&&(i_wb_we)
+ &&((!i_wb_addr)||(VARIABLE_RATE==0)))
begin
// Write with two's complement data, convert it
- // internally to binary offset
- next_sample <= { ~i_wb_data[15], i_wb_data[14:0] };
+ // internally to an unsigned binary offset
+ // representation
+ next_sample <= { !i_wb_data[15], i_wb_data[14:0] };
next_valid <= 1'b1;
if (i_wb_data[16])
o_aux <= i_wb_data[(NAUX+20-1):20];
- end else if (timer == 0)
+ end else if (ztimer)
next_valid <= 1'b0;
- initial o_int = 1'b0;
- always @(posedge i_clk)
- o_int <= (~next_valid);
+ // If the value in our sample buffer isn't valid, create an interrupt
+ // that can be sent to a processor to know when to send a new sample.
+ // This output can also be used to control a read from a FIFO as well,
+ // depending on how you wish to use the core.
+ assign o_int = (!next_valid);
+ //
+ // To generate our waveform, we'll compare our sample value against
+ // a bit reversed counter. This counter is kept in pwm_counter.
+ // The choice of a 16-bit counter is arbitrary, but it was made to
+ // match the sixteen bits of the input
reg [15:0] pwm_counter;
initial pwm_counter = 16'h00;
always @(posedge i_clk)
- pwm_counter <= pwm_counter + 16'h01;
+ if (i_reset)
+ pwm_counter <= 16'h0;
+ else
+ pwm_counter <= pwm_counter + 16'h01;
+ // Bit-reverse the counter
wire [15:0] br_counter;
genvar k;
generate for(k=0; k<16; k=k+1)
@@ -163,19 +204,29 @@
assign br_counter[k] = pwm_counter[15-k];
end endgenerate
+ // Apply our comparison to determine the next output bit
always @(posedge i_clk)
o_pwm <= (sample_out >= br_counter);
+ //
+ // Handle the bus return traffic.
generate
if (VARIABLE_RATE == 0)
begin
+ // If we are running off of a fixed rate, then just return
+ // the current setting of the aux registers, the current
+ // interrupt value, and the current sample we are outputting.
assign o_wb_data = { {(12-NAUX){1'b0}}, o_aux,
3'h0, o_int, sample_out };
end else begin
+ // On the other hand, if we have been built to support a
+ // variable sample rate, then return the reload value for
+ // address one but otherwise the data value (above) for address
+ // zero.
reg [31:0] r_wb_data;
always @(posedge i_clk)
if (i_wb_addr)
- r_wb_data <= w_reload_value;
+ r_wb_data <= { (32-TIMING_BITS),w_reload_value};
else
r_wb_data <= { {(12-NAUX){1'b0}}, o_aux,
3'h0, o_int, sample_out };
@@ -182,9 +233,21 @@
assign o_wb_data = r_wb_data;
end endgenerate
+ // Always ack on the clock following any request
initial o_wb_ack = 1'b0;
always @(posedge i_clk)
- o_wb_ack <= (i_wb_cyc)&&(i_wb_stb);
+ o_wb_ack <= (i_wb_stb);
+
+ // Never stall
assign o_wb_stall = 1'b0;
+ // Make Verilator happy. Since we aren't using all of the bits from
+ // the bus, Verilator -Wall will complain. This just informs
+ // V*rilator that we already know these bits aren't being used.
+ //
+ // verilator lint_off UNUSED
+ wire [14:0] unused;
+ assign unused = { i_wb_cyc, i_wb_data[31:21], i_wb_data[19:17] };
+ // verilator lint_on UNUSED
+
endmodule
/wbpwmaudio/trunk/wbpwmaudio.core
0,0 → 1,13
CAPI=1 |
[main] |
description = A Wishbone Controlled PWM (audio) controller |
|
[fileset rtl] |
files = rtl/wbpwmaudio.v |
file_type = verilogSource |
|
[provider] |
name=github |
user=ZipCPU |
repo=wbpwmaudio |
version = master |