OpenCores
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

powered by: WebSVN 2.1.0

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