Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
296 lines (269 sloc) 10.4 KB
////////////////////////////////////////////////////////////////////////////////
//
// 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 = FULL_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