Improved Function Generator
In this version improvements have been made:
- The number of samples in a full cycle has been increased by four, from 4096 to 16384. This will help reduce high frequency sampling noise when making low frequency signals.
- To quadruple the samples, the FPGA's memory is loaded with 1/4th of a sine. The FPGA does appropriate flipping and reflections to achieve a full cycle.
- Verilog code now uses signed integer math which simplifies signal processing.
- Buttons 1 and 2 on the board increase/decrease the phase by 2.5 degrees relative to the scope trigger.
- Button 1 and 2 pressed simultaneously resets the phase to zero, relative to the scope trigger.
- Buttons 3 and 4 increase/decrease the frequency by 2.5 Hz.
Since the memory now has 1/4th of the waveform, arbitrary waveform shapes are now more limited. Only symmetric waveforms are possible.
DDS Top level module
///////////////////////////////////////////////////////////////////////////////
// File downloaded from https://halverscience.net
///////////////////////////////////////////////////////////////////////////////
// This is a Direct Digital Synthesis function generator, frequency range
// 0.000728 Hz to ~200 kHz. It outputs a sine though that can be changed by
// loading memory with different numbers.
// The accuracy of the output frequency is dependent on the clock crystal on the
// FPGA board, typically 30 ppm (parts per million). 50 ppm on the GoBoard.
// Waveform values are sent to DAC at 1 MHz.
// Currently uses a Digilent Pmod DA3 DAC, but could be easily adapted to other DACs.
// Author: Peter Halverson 7/2020
// Compared to the previous version of the DDS generator this implementation adds
// ----- Four times more points in the function output. But the memory now holds
// just one quadrant of the waveform, which means that the waveform must be symmetric.
// (Triangle wave, square wave OK. EKG not OK.)
// ----- Switch control of frequency and phase.
// Switch 1 increases the phase by 22.5 degrees relative to the scope trigger.
// Switch 2 decreases the phase by 22.5 degrees.
// Switch 1 AND 2 together resets the phase of waveform to zero, relative to the scope trigger.
// Also, the output is temporarily off when 1 and 2 are pressed together.
// Switch 3 adds 2.5 Hz to F. Switch 4 subtracts 2.5 Hz from F.
///////////////////////////////////////////////////////////////////////////////
`default_nettype none // Use this to find undeclared wires. Optional.
module DDS_Sinewave_Generator_Top
(input wire i_Clk, // Main Clock, 25 MHz
output wire o_Segment1_A, // Segment1 is upper digit, Segment2 is lower digit
output wire o_Segment1_B, output wire o_Segment1_C, output wire o_Segment1_D,
output wire o_Segment1_E, output wire o_Segment1_F, output wire o_Segment1_G,
output wire o_Segment2_A, output wire o_Segment2_B, output wire o_Segment2_C,
output wire o_Segment2_D, output wire o_Segment2_E, output wire o_Segment2_F,
output wire o_Segment2_G,
output wire o_LED_1, // Wiring to the LEDs
output wire o_LED_2, output wire o_LED_3, output wire o_LED_4,
output wire io_PMOD_1, // First PMOD connector - not used
output wire io_PMOD_2, //
input wire io_PMOD_3, //
output wire io_PMOD_4, //
output wire io_PMOD_7, // 2nd PMOD connector - has the DAC board.
output wire io_PMOD_8, //
output wire io_PMOD_9, //
output wire io_PMOD_10, //
input wire i_Switch_1, // Wires to the switches. Not used now, but useful for debugging.
input wire i_Switch_2,
input wire i_Switch_3,
input wire i_Switch_4,
output wire o_VGA_HSync, // VGA
output wire o_VGA_VSync, // Used for scope trigger
output wire o_VGA_Red_0, output wire o_VGA_Red_1, output wire o_VGA_Red_2,
output wire o_VGA_Grn_0, output wire o_VGA_Grn_1, output wire o_VGA_Grn_2,
output wire o_VGA_Blu_0, output wire o_VGA_Blu_1, output wire o_VGA_Blu_2
);
// Switches will be used to adjust the frequencies
// See the nandland.com discussion of switch debouncing
wire w_Switch_1;
Debounce_Switch Debounce_Inst1(.i_Clk(i_Clk), .i_Switch(i_Switch_1),.o_Switch(w_Switch_1));
wire w_Switch_2;
Debounce_Switch Debounce_Inst2 (.i_Clk(i_Clk), .i_Switch(i_Switch_2),.o_Switch(w_Switch_2));
wire w_Switch_3;
Debounce_Switch Debounce_Inst3 (.i_Clk(i_Clk), .i_Switch(i_Switch_3),.o_Switch(w_Switch_3));
wire w_Switch_4;
Debounce_Switch Debounce_Inst4 (.i_Clk(i_Clk), .i_Switch(i_Switch_4),.o_Switch(w_Switch_4));
reg r_Switch_1 = 1'b0; reg r_Switch_2 = 1'b0; reg r_Switch_3 = 1'b0; reg r_Switch_4 = 1'b0;
wire w_Sample_Clock;
Make_Sample_Clock #(.DIVIDER(25)) Make_Sample_Clock_inst
(
.i_Clk(i_Clk), // 25 MHz clock in
.o_Sample_Clock(w_Sample_Clock) // 1 MHz sample clock out
);
parameter NUMBER_OF_POINTS_PER_QUADRANT = 3406; // Originally 4096*4 but changed for accurate mHz
parameter NUMBER_OF_POINTS = NUMBER_OF_POINTS_PER_QUADRANT*4;
parameter DATA_WIDTH = 16; // Our DAC is 16 bits, and the stored waveform has 16 bits.
wire signed [DATA_WIDTH-1:0] ws_Waveform; //"signed" means the msb is used as a sign bit
wire [DATA_WIDTH-1:0] w_Waveform; // This is unsigned
wire w_Data_Valid;
Make_Sinewave Make_Sinewave_inst(
.i_Clk(i_Clk),
.i_Sample_Clock(w_Sample_Clock),
.i_Waveform_Index(r_Waveform_Index),
.os_Waveform(ws_Waveform),
.o_Data_Valid(w_Data_Valid)
);
assign w_Waveform[14:0] = ws_Waveform[14:0]; //everything but the sign bit
assign w_Waveform[15] = ~ws_Waveform[15]; //sign bit, flipped to behave like unsigned
// Comments apply to the Pmod DA3. (The Pmod DA2 is different)
wire w_Pmod_Port2_Pin1; // ~CS, Pull low when moving data into the DAC
wire w_Pmod_Port2_Pin2; // DIN Data stream going to the DAC. 16 bits, but 1st 4 bits are always 0.
wire w_Pmod_Port2_Pin3; // ~LDAC, Falling edge or holding low activates the DAC
wire w_Pmod_Port2_Pin4; // SCLK, DAC Clock. Data bits are latched on rising edge of this clock.
Send_To_DAC_Pmod_DA3
Send_To_DAC_Pmod_DA3_inst(
.i_Clk(i_Clk),
.i_Data_Valid(w_Data_Valid),
.i_DAC_Data(w_Waveform),
.o_notCS(w_Pmod_Port2_Pin1),
.o_notLDAC(w_Pmod_Port2_Pin3),
.o_Sclk(w_Pmod_Port2_Pin4),
.o_Serial_Data(w_Pmod_Port2_Pin2)
);
reg [35-1:0] r_Phase_Accumulator;
reg [14-1:0] r_Waveform_Index; // 14 bits needed for 16384 points (4096 * 4 quadrants)
reg [35-1:0] r_Phase_Adjust = 0;
// TUNING WORD determines the frequency. Output freq will be
// (25 MHz * 2**35) * TUNING_WORD = 0.0007275957614183426 Hz * TUNING_WORD
// Numbers assume the 25 MHz crystal is perfect.
// Typically, crystals have +/- 30 ppm accuracy, so 25 MHz +/- 750 Hz
// Therefore the frequencies below are useful only for comparison to the clock
// which comes out at the scope trigger
parameter TUNING_WORD_START = 1374390; // Close to 1000 Hz. Actually 1000.0003385357559 Hz
reg [32-1:0] r_Tuning_Word;
reg [8-1:0] r_Power_On = 0; // Used to wait until the FPGA is fully on and functional
reg r_Phase_Reset = 0;
always @(posedge i_Clk) begin
if (r_Phase_Reset == 1'b1) r_Phase_Accumulator <= 0; // Set phase to zero
else
r_Phase_Accumulator <= r_Phase_Accumulator + r_Tuning_Word + r_Phase_Adjust;
r_Waveform_Index <= r_Phase_Accumulator[35-1:21]; // 14 most sig. bits for function lookup
r_Switch_1 <= w_Switch_1; // See the nandland.com discussion of switch debouncing
r_Switch_2 <= w_Switch_2; r_Switch_3 <= w_Switch_3; r_Switch_4 <= w_Switch_4;
if (r_Power_On == 8'hff) begin
if (r_Switch_1 == 1'b1 && r_Switch_2 == 1'b1) begin // If sw1 AND sw2 are on then
r_Phase_Reset <= 1; // reset the phase of the sinewave and also the scope trigger.
r_Phase_Adjust <= 0;
end else begin
r_Phase_Reset <= 0;
if (w_Switch_1 == 1'b1 && r_Switch_1 == 1'b0) begin //Look for rising edge on sw1
// For phase adjustment, need to understand that in the phase accumulator 2**35 is one cycle.
// 2**35 = 35'h800000000
r_Phase_Adjust <= 35'h080000000;// 22.5 deg = 1/16th cycle so want 2**35/16
end else begin
if (w_Switch_2 == 1'b1 && r_Switch_2 == 1'b0)
r_Phase_Adjust <= 35'h800000000-35'h080000000;//-22.5 deg=360-22.5 so want 2**35-(2**35/16)
else
r_Phase_Adjust <= 0;
end //if
end //if
if (w_Switch_3 == 1'b1 && r_Switch_3 == 1'b0)
//r_Tuning_Word <= r_Tuning_Word + 13744;// Increase the frequency by 10.0000761449337 Hz
//r_Tuning_Word <= r_Tuning_Word + 6872; // Increase the frequency by 5.00003807246685 Hz
r_Tuning_Word <= r_Tuning_Word + 3436; // Increase the frequency by 2.500019036233425 Hz
else if (w_Switch_4 == 1'b1 && r_Switch_4 == 1'b0)
//r_Tuning_Word <= r_Tuning_Word - 13744;// Decrease the frequency by 10.0000761449337 Hz
//r_Tuning_Word <= r_Tuning_Word - 6872 ;// Decrease the frequency by 5.00003807246685 Hz
r_Tuning_Word <= r_Tuning_Word - 3436; // Decrease the frequency by 2.500019036233425 Hz
end else begin
// Note: it seems LSE doesn't implement initialized registers, except for zeroing at power-up.
r_Power_On <= r_Power_On + 1'b1; // Keep looping while the FPGA starts up.
r_Tuning_Word <= TUNING_WORD_START; // Set the initial freq. when the circuit is powered up.
end //if
end //always
// Make a steady scope trigger for reference and debugging
reg [18-1:0] r_Scope_Trigger_Counter; // If I need to divide 25 MHz by 250000, need 18 bits
reg r_Scope_Trigger;
always @(posedge i_Clk) begin //25 MHz clock
if (r_Phase_Reset == 1'b1) begin
r_Scope_Trigger_Counter <= 0;
r_Scope_Trigger <= 1;
end else begin
if (r_Scope_Trigger_Counter == 25000-1) begin //250000 for 100 Hz, 25000 for 1000 Hz, etc.
r_Scope_Trigger_Counter <= 0;
r_Scope_Trigger <= 1;
end else begin
r_Scope_Trigger_Counter <= r_Scope_Trigger_Counter + 1;
if (r_Scope_Trigger_Counter >= 249) r_Scope_Trigger <= 0; //249 would give a 10 microsec pulse
else r_Scope_Trigger <= 1;
end //if
end //if
end //always
assign o_VGA_VSync = r_Scope_Trigger;
// Wires to the DAC
assign io_PMOD_7 = w_Pmod_Port2_Pin1;//Chip select signal, active low
assign io_PMOD_1 = w_Pmod_Port2_Pin1;//Duplicate for easier connection to oscilloscope
assign io_PMOD_8 = w_Pmod_Port2_Pin2;//Serial data out to DAC
assign io_PMOD_2 = w_Pmod_Port2_Pin2;//Duplicate for easier connection to oscilloscope
assign io_PMOD_9 = w_Pmod_Port2_Pin3;//~LDAC, When pulled down, activates the DAC.
//(See AD5541A data sheet)
assign io_PMOD_10 = w_Pmod_Port2_Pin4;//25 MHz clock to clock in the serial data stream
assign o_LED_1 = 0; // Turn off the LEDs (because they are annoying)
assign o_LED_2 = 0; assign o_LED_3 = 0; assign o_LED_4 = 0;
assign o_Segment1_A = 1; assign o_Segment1_B = 1; assign o_Segment1_C = 1; assign o_Segment1_D = 1;
assign o_Segment1_E = 1; assign o_Segment1_F = 1; assign o_Segment1_G = 1; assign o_Segment2_A = 1;
assign o_Segment2_B = 1; assign o_Segment2_C = 1; assign o_Segment2_D = 1; assign o_Segment2_E = 1;
assign o_Segment2_F = 1; assign o_Segment2_G = 1;
endmodule
`default_nettype wire // Go back to the usual default
Make_Sinewave module
///////////////////////////////////////////////////////////////////////////////
// File downloaded from https://halverscience.net
///////////////////////////////////////////////////////////////////////////////
// This is part of a Direct Digital Synthesis function generator, frequency range
// 0.000728 Hz to ~200 kHz. It outputs a sine though that can be changed by
// loading memory with different numbers.
// This module reads RAM that has been pre-loaded with one quadrant of function
// and delivers the 16 bit function value to the top module.
// Since memory holds only 1/4th of the complete waveform, this function must decide
// which quadrant the requested data belongs to and depending on which quadrant
// the answer is negated (positive flipped to negative) and/or reflected left-right.
// In the current code memory holds a cosine function from zero to 90 degrees, in 4096
// steps. Cosine was used instead of sine because I could more easily visualize the
// negations and left-right reflections.
// Peter Halverson 7/27/2020
// /////////////////////////////////////////////////////////////////////////////
`default_nettype none // Use this to find undeclared wires. Optional.
module Make_Sinewave
#(parameter ADDRESS_WIDTH = 14,NUMBER_OF_POINTS = 16384, DATA_WIDTH = 16) (
input wire i_Clk,
input wire i_Sample_Clock, // Tells generator to start making the next point
input wire [ADDRESS_WIDTH-1:0] i_Waveform_Index, // 14 bits needed to select among
//16384 points. (4096 points in each quarter wave)
output wire signed [DATA_WIDTH-1:0] os_Waveform, // Its a 16 bit output, since my DAC is 16 bits
output wire o_Data_Valid
);
parameter NUMBER_OF_POINTS_PER_QUADRANT = NUMBER_OF_POINTS / 4;
reg [ADDRESS_WIDTH-2-1:0] r_ram_Address; //Need 2 bits fewer to address a quadrant
reg [DATA_WIDTH-1:0] ram [0:NUMBER_OF_POINTS_PER_QUADRANT-1]; //Inferred memory
initial begin
$readmemh ("ram_sin.ini", ram); //Load memory with 1/4 cycle of cosine data.
end
reg [1:0] r_State; // 2 bits allows for 4 states
parameter IDLE_STATE = 0; parameter READ_RAM_STATE = 1;
parameter FLIP_WAVEFORM_STATE = 2; parameter DATA_VALID_STATE = 3;
reg r_Data_Valid;
reg signed [DATA_WIDTH-1:0] rs_Waveform;
reg signed [DATA_WIDTH-1:0] rs_Waveform_Temp;
always @(posedge i_Clk) begin
case (r_State)
IDLE_STATE: begin
r_Data_Valid <= 1'b0;
if (i_Waveform_Index[13-1] == 1'b0)
r_ram_Address <= i_Waveform_Index[12-1:0]; //2nd and 3rd quadrants use address as-is
else
r_ram_Address <= 4095 - i_Waveform_Index[12-1:0]; //1st and 3rd quadrants reverse address
if (i_Sample_Clock == 1'b1) r_State <= READ_RAM_STATE;
else r_State <= IDLE_STATE;
end //IDLE_STATE
READ_RAM_STATE: begin
rs_Waveform_Temp <= ram[r_ram_Address]; // Extra step needed to work around a bug in
// Lattice Synthesis Engine. It doesn't like - ram[r_ram_Address]
r_State <= FLIP_WAVEFORM_STATE;
end //READ_RAM_STATE
FLIP_WAVEFORM_STATE: begin
if (i_Waveform_Index[14-1] == 1'b0) rs_Waveform <= rs_Waveform_Temp;//Use table data as is
else rs_Waveform <= - rs_Waveform_Temp; //Use negative of table data
r_State <= DATA_VALID_STATE;
end //FLIP_WAVEFORM_STATE
DATA_VALID_STATE: begin
r_Data_Valid <= 1'b1;
r_State <= IDLE_STATE;
end //DATA_VALID_STATE
default: r_State <= IDLE_STATE;
endcase
end //always
assign os_Waveform = rs_Waveform;
assign o_Data_Valid = r_Data_Valid;
endmodule
`default_nettype wire // Go back to the usual default
Make_Sample_Clock module
Same as on the DDS Function Generator page.
Send_To_DAC_Pmod_DA3
Same as on the Analog Output page.
Python code Generate_Table_and_Check_Accuracy.py
Used to generate the Verilog compatible datafile used to load the quarter cycle of sinewave into FPGA RAM. You could change this to make other waveforms, as long as they are symmetric.
#################################################################################
# File downloaded from https://halverscience.net
#
# Python program to crate a hexadecimal encoded table of values that can be read by
# Verilog and used to create a sine wave lookup table in an FPGA
#
# To save memory, the FPGA only holds 1/4 of a cycle of sine data and re-uses
# that data to construct the other 3/4ths of the cycle.
#
# Peter Halverson 7/28/2020
##################################################################################
from math import *
maximum = 2.0**15 - 1 # Use for 16 bit DACs
#maximum = 2.0**11 - 1 # Use for 12 bit DACs
imaximum = int(maximum)
# I'm not going up to 2**16 - 1 because this data will be flipped and used for the
# negative part of the sine wave. So this data spans only 1/2 of the DAC's range.
ntable = 4096 #Table size, 2**12, just fits into the GoBoard's iCE40 HX1K VQ100 memory
isin_table = [] #Creates an empty array
for itable in range(0,ntable+1):
x = 0.5*pi*itable/ntable #x is in radians
isin_table = isin_table + [int(round(imaximum*sin(x)))] #Fill the array
most_positive_error = 0.0
most_negative_error = 0.0
most_positive_error_i = 0
most_negative_error_i = 0
npoints = ntable #This will be 1/4 cycle, pi/2 radians
for i in range(0,npoints+1):
x = 0.5*pi*i/npoints #x is in radians
true_sin = maximum*sin(x)
fpga_sin = isin_table[i]
error = fpga_sin-true_sin
print "i=%4i"%i,"x=%5.3f"%x,"truth=%8.2f"%true_sin,
print "fpga=%6i"%fpga_sin,"err=%7.2f"%error
if error > most_positive_error:
most_positive_error = error
most_positive_error_i = i
if error < most_negative_error:
most_negative_error = error
most_negative_error_i = i
print "most_positive_error =",most_positive_error,"at i=",most_positive_error_i
print "most_negative_error =",most_negative_error,"at i=",most_negative_error_i
print_hex_values = True #Copy/paste or pipe ">" this output into a verilog memory initialization file
debugging = False
if print_hex_values:
for i in range(0,npoints):
d=isin_table[i]
if debugging: print i,",",d
else:
# s is the string that will have the hex data.
s = '{:04x}'.format(d)
print s