DDS FUNCTION GENERATOR V2

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