Analog Out, Verilog 2

This page has a more capable, versatile version of what you need to test the Analog Devices AD5541A 16 bit 1 Ms/s DAC mounted on a Digilent Pmod DA3. I am using a NandLand GoBoard, but this code can be easily ported to other environments.

The circuit runs in one of four modes, selected by pressing one of the four pushbutton switches. The modes are

  • Button 1 = 15.29 Hz saw tooth from 0 to 65537.
  • Button 2= 500 kHz square wave that is amplitude modulated by a 15.29 Hz sawtooth.
  • Button 3= 500 kHz square wave that is amplitude modulated by a 15.625 kHz sawtooth.
  • Button 4= Constant voltage. Initially zero but each time you press button 4 the DAC output increments by one DAC step.

Top level module

///////////////////////////////////////////////////////////////////////////////
// File downloaded from https://halverscience.net
///////////////////////////////////////////////////////////////////////////////
// Demonstrates and test Digilent's Pmod DA3 digital-to-analog converter
// using the Nandland.com GoBoard
// The DAC is to be plugged into the 2nd port of the Pmod connector, i.e., the
// 2nd rown of holes.
// The DAC is fed various 16 bit patterns depending on the switches.  
// See comments below.
// The update rate is 1 MHz.
// Author: Peter Halverson 7/2020
///////////////////////////////////////////////////////////////////////////////

`default_nettype none       // Use this to find undeclared wires. Optional.

module DAC_Test_Top
  (input  wire i_Clk,         // Main Clock, 25 MHz
   output wire o_Segment1_A,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,     // Wiring to the ADC board ~ chip select (active low)
   //input  io_PMOD_2,        // data from ADC 0
   output  wire io_PMOD_2,    // temporary mirror of data to DAC
   input  wire io_PMOD_3,     // data from ADC 1  (not used)
   output wire io_PMOD_4,     // clock to read out data
   output wire io_PMOD_7,     // Wiring to the DAC board ~ chip select (active low)
   output wire io_PMOD_8,     // data to DAC 0
   output wire io_PMOD_9,     // data to ADC 1  (not used)
   output wire io_PMOD_10,    // clock to read out data
   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 connector
   output wire o_VGA_VSync,   // I'm using this to trigger the oscilloscope (3rd row, 2nd hole)
                              // Ground is 2nd row, last hole, to the right.
   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   
  );
    
  reg [1:0] r_Waveform_Select = 2'b00;  // Press button 1,2,3 or 4 to select waveform
  reg [15:0] r_ADC_Constant_Value = 16'h0000; // Will be used for waveform of button 4, a 
                                              // constant output incremented with each button 4 press.
///////////////// Code for the switches ////////////////////
  wire w_Switch_4;  reg r_Switch_4 = 1'b0; // Needed for debouncing switch 4
  Debounce_Switch Debounce_Inst
  (.i_Clk(i_Clk), 
   .i_Switch(i_Switch_4),
   .o_Switch(w_Switch_4));

  always @(posedge i_Clk) begin
    if (i_Switch_1 == 1'b1) r_Waveform_Select = 0;
    else if (i_Switch_2 == 1'b1) r_Waveform_Select = 1;
    else if (i_Switch_3 == 1'b1) r_Waveform_Select = 2;
    else if (i_Switch_4 == 1'b1) r_Waveform_Select = 3;
    r_Switch_4 <= w_Switch_4;         // Creates a Register for debouncing switch 4
    if (w_Switch_4 == 1'b1 && r_Switch_4 == 1'b0) begin // Increment the constant value output when switch
      // 4 has a debounced rising edge.
      r_ADC_Constant_Value <= r_ADC_Constant_Value +1;
    end //if
    if ((i_Switch_1 == 1'b1) || (i_Switch_2 == 1'b1) || (i_Switch_3 == 1'b1))
      r_ADC_Constant_Value <= 16'hffff;  // Reset if we are not in waveform 3 (button 4)
  end //always 


  wire w_Sample_Clock;              // 1 MHz.  This is the max speed of the DA3 module.
  Make_Sample_Clock
  Make_Sample_Clock_inst(
    .i_Clk(i_Clk),                // 25 MHz clock goes in
    .o_Sample_Clock(w_Sample_Clock)  // 1 MHz comes out
  );

  wire [15:0] w_Waveform;
  Make_Test_Waveforms 
  Make_Test_Waveforms_inst(
    .i_Clk(i_Clk),
    .i_Sample_Clock(w_Sample_Clock),
    .i_Waveform_Select(r_Waveform_Select),
    .i_ADC_Constant_Value(r_ADC_Constant_Value),
    .o_Waveform(w_Waveform),
    .o_Scope_Trigger(o_VGA_VSync)
  );

// GoBoard's PMOD connector
// Looking at the connector, into the holes you see two rows of 6 holes:
// oooooo          1st row
// oooooo          2nd row
// 
// The first row, which is for PMOD module 1, going left-to-right is
// Pin 6, VCC (+3.3 V)
// Pin 5, Ground
// Pin 4, Port 1 pin 4  (usually a clock signal to the module)
// Pin 3, Port 1 pin 3  (can be data or other signal)
// Pin 2, Port 1 pin 2  (usually data to or from the module)
// Pin 1, Port 1 pin 1  (usually a select or "activate" signal, active low, to the module)
// 
// The second row, which is for PMOD module 2, going left-to-right is
// Pin 12, VCC (+3.3 V)
// Pin 11, Ground
// Pin 10, Port 2 pin 4  (usually a clock signal to the module)
// Pin  9, Port 2 pin 3  (can be data or other signal)
// Pin  8, Port 2 pin 2  (usually data to or from the module)
// Pin  7, Port 2 pin 1  (usually a select or "activate" signal, active low, to the module)

                          // 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.
  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_Sample_Clock),
    .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)
  );

  // 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 io_PMOD_4 = w_Pmod_Port2_Pin4; //DUPLICATE for easier connection to oscilloscope

  wire w_Segment1_A;  wire w_Segment1_B;  wire w_Segment1_C;
  wire w_Segment1_D;  wire w_Segment1_E;  wire w_Segment1_F;
  wire w_Segment1_G;  wire w_Segment2_A;  wire w_Segment2_B;
  wire w_Segment2_C;  wire w_Segment2_D;  wire w_Segment2_E;
  wire w_Segment2_F;  wire w_Segment2_G;

  // Instantiate Binary to 7-Segment Converter #1
  Binary_To_7Segment Binary_To_7Segment_Inst1(.i_Clk(i_Clk),
     .i_Binary_Num(w_Waveform[7:4]),  //Display the 3rd nibble
     .o_Segment_A(w_Segment1_A),.o_Segment_B(w_Segment1_B),.o_Segment_C(w_Segment1_C),
     .o_Segment_D(w_Segment1_D),.o_Segment_E(w_Segment1_E),.o_Segment_F(w_Segment1_F),
     .o_Segment_G(w_Segment1_G));
   
  assign o_Segment1_A = ~w_Segment1_A;  assign o_Segment1_B = ~w_Segment1_B;
  assign o_Segment1_C = ~w_Segment1_C;  assign o_Segment1_D = ~w_Segment1_D;
  assign o_Segment1_E = ~w_Segment1_E;  assign o_Segment1_F = ~w_Segment1_F;
  assign o_Segment1_G = ~w_Segment1_G;

  // Instantiate Binary to 7-Segment Converter #2
  Binary_To_7Segment Binary_To_7Segment_Inst2(.i_Clk(i_Clk),
     .i_Binary_Num(w_Waveform[3:0]),  //Display the 4th nibble, least significant 4 bits
     .o_Segment_A(w_Segment2_A),.o_Segment_B(w_Segment2_B),.o_Segment_C(w_Segment2_C),
     .o_Segment_D(w_Segment2_D),.o_Segment_E(w_Segment2_E),.o_Segment_F(w_Segment2_F),
     .o_Segment_G(w_Segment2_G));
   
  assign o_Segment2_A = ~w_Segment2_A;  assign o_Segment2_B = ~w_Segment2_B;
  assign o_Segment2_C = ~w_Segment2_C;  assign o_Segment2_D = ~w_Segment2_D;
  assign o_Segment2_E = ~w_Segment2_E;  assign o_Segment2_F = ~w_Segment2_F;
  assign o_Segment2_G = ~w_Segment2_G;


  //used to dim the four LEDs (otherwise they are blinding!)
  reg [1:0] r_LED_Dimmer_Counter; reg r_LED_Dim;
  always @(posedge i_Clk) begin 
     r_LED_Dimmer_Counter <= r_LED_Dimmer_Counter+1; 
     if (r_LED_Dimmer_Counter == 2'b00) r_LED_Dim <= 1; else r_LED_Dim <= 0;
  end //always
  //LEDs indicate the state of r_Waveform_Select
  assign o_LED_1 = (r_Waveform_Select == 2'd0) ? r_LED_Dim : 0;
  assign o_LED_2 = (r_Waveform_Select == 2'd1) ? r_LED_Dim : 0;
  assign o_LED_3 = (r_Waveform_Select == 2'd2) ? r_LED_Dim : 0;
  assign o_LED_4 = (r_Waveform_Select == 2'd3) ? r_LED_Dim : 0;

endmodule

`default_nettype wire        // Sets the default back to the usual.

Make_Test_Waveforms

Module to make four test waveforms, switch selectable

///////////////////////////////////////////////////////////////////////////////
// File downloaded from https://halverscience.net
///////////////////////////////////////////////////////////////////////////////
// Module generates various 16 bit patterns to drive a Digilent Pmod DA3 DAC.
// Which pattern depends on i_Waveform_Select.
// See comments below.
// The update rate is 1 MHz.
// Author: Peter Halverson 7/2020
///////////////////////////////////////////////////////////////////////////////

`default_nettype none       // Use this to find undeclared wires. Optional.

// Button 1 = 15.29 Hz saw tooth from 0 to 65537  (Waveform 1)
// Button 2= 500 kHz square wave that is amplitude modulated by a 15.29 Hz sawtooth  (Waveform 2)
// Button 3= 500 kHz square wave that is amplitude modulated by a 15.625 kHz sawtooth  (Waveform 4)
// Button 4= Constant voltage.  Initially zero but each time you press button 4
//                        the DAC output increments by one DAC step.                (Waveform 5)

module Make_Test_Waveforms (
  input wire i_Clk,                 // 25 MHz
  input wire i_Sample_Clock,          // 1 MHz
  input wire [1:0] i_Waveform_Select,
  input wire [15:0] i_ADC_Constant_Value,
  output wire [15:0] o_Waveform,
  output wire o_Scope_Trigger
  );

  reg [15:0] r_Waveform1; //15.29 Hz saw tooth from 0 to 65537      
  reg [15:0] r_Waveform2; //500 kHz square wave AM mod by 15.29 Hz sawtooth
  reg [5:0] r_Waveform3a;    reg [9:0] r_Waveform3b;     // 6 + 10 = 16 bits total
  wire [15:0] r_Waveform3;//15.625 kHz sawtooth
  assign r_Waveform3 =  {r_Waveform3a, r_Waveform3b};
  reg [15:0] r_Waveform4; //500 kHz square wave AM mod by 15.625 kHz sawtooth
  reg [15:0] r_Waveform5; //Constant output incremented by switch 4    
  always @(posedge i_Sample_Clock) begin
    r_Waveform1 <= r_Waveform1 + 1;    // This will make a sawtooth, Waveform 1
    // Since this is a 16 bit DAC and the waveform steps through all 2**16 values, the
    // frequency of the sawtooth is slow:  f = 1 MHz / 2**16 = 15.258 Hz

    // Following code makes high frequency squarewave that grows as a sawtooth.
    // The squarewave is at 1 MHz / 2 = 500 kHz
    // The sawtooth envelope is at the same frequency as Waveform 1
    // This is a tough test for the DAC -- will the DAC be able to handle it?
    // The answer is YES.  However, the digital oscilloscope has trouble displaying it.
    // On the other hand, my old analog oscilloscope has no trouble with it.
    if (r_Waveform1[0] == 1'b0) begin
      r_Waveform2[15] <= 1'b1;
      r_Waveform2[14:0] <= r_Waveform1[15:1];  // This makes a sawtooth that begins at 
                                            // 1000 0000 0000 0000 and ends at 1111 1111 1111 1111
    end else begin
      r_Waveform2[15] <= 1'b0;
      r_Waveform2[14:0] <= ~r_Waveform1[15:1];  // This makes a sawtooth that begins at 
                                            // 0111 1111 1111 1111 and ends at 0000 0000 0000 0000
    end
    r_Waveform3a <= r_Waveform3a + 1;    // This will make a sawtooth, Waveform 3 at 15.625 kHz
    r_Waveform3b <= 0;
    // Following makes waveform similar to Waveform 2, a growing 500 kHz squarewave.
    // This is something the digital oscilloscope can more easily handle because the 
    // envelope and squarewave frequencies are not so vastly different.
    // It is equally tough on the ADC.

    if (r_Waveform3a[0] == 1'b0) begin
      r_Waveform4[15] <= 1'b1;
      r_Waveform4[14:0] <= r_Waveform3[15:1];
    end else begin
      r_Waveform4[15] <= 1'b0;
      r_Waveform4[14:0] <= ~r_Waveform3[15:1];
    end
    r_Waveform5 <= i_ADC_Constant_Value;
  end //always 

  
  //The next line means "if i_Waveform_Select equals 0 then o_Waveform=r_Waveform1" otherwise
  // "if i_Waveform_Select equals 1 then o_Waveform=r_Waveform2" etc.
  assign o_Waveform = (i_Waveform_Select == 0) ? r_Waveform1 :
    (i_Waveform_Select == 1) ? r_Waveform2 : 
    (i_Waveform_Select == 2) ? r_Waveform4 : r_Waveform5;

  //assign o_Scope_Trigger = r_Waveform1[15]; // Used to sync my oscilloscope for a stable display
  assign o_Scope_Trigger = (i_Waveform_Select[1] == 1'b0) ? ~r_Waveform1[15] : ~r_Waveform3[15];
  // I added the "~" to make the rising edge of the scope trigger be at the start of the waveform.
endmodule

`default_nettype wire        // Sets the default back to the usual.

Other required modules

The Send_To_DAC_Pmod_DA3 and Make_Sample_Clock modules are on the previous page, "Analog Out, Verilog 1"

The Debounce_Switch module is available from NandLand here: Go Board Project - Debounce A Switch

The Binary_To_7Segment module is available from NandLand here: Drive a 7-Segment Display With Your FPGA