How to read an ADC (analog-to-digital converter) with an FPGA, coded in Verilog.

Read_ADC_working


Here is a NanLand.com GoBoard with an FPGA running Verilog code (see below) that reads out a Digilent PmodAD1 ADC and shows the voltage using the LED displays.

The GoBoard’s FPGA is a Lattice HX1K, and the ADC board has two Analog Devices AD7476A 12 bit ADCs. Right now, the readout is 200 samples/s but in principle each ADC can do 1 million samples/s. My eventual goal is to make a 1 Ms/s two-channel oscilloscope.

Please see the video on YouTube:
How to read an ADC with an FPGA

In you have questions about this setup, please contact me, either through this web page or by email at pghalverson “at” gmail “dot” com.

Also, be sure to visit the
NandLand web site for fun tutorials. Without those tutorials I would be very frustrated!

Connection diagram:
GoBoard_AD1_Connection_Diagram

Connections photo:
Go_Board_and_AD1_and_breadboard

Scope signals:
AD1_PMOD_scope_signals

====================================================================


Separate this Verilog code into three files to compile

// Filename:  Read_ADC_Top.v
// Written by me, Peter Halverson, and posted on my web page http://halverscience.net/   Nov 4, 2019
// This Verilog code is intended to run on a GoBoard, available from nandland.com,
// and it uses a PmodAD1 analog-to-digital converter, available from Digilent
// This is the top module.  NOTE: I had trouble with the Lattice LSE Synthesis tool
// not "autoguessing" that this is the top module.  I had to explicitly tell the tool.
// To do that, go into Tool > Tool Options > LSE (tab) > Top Level Unit and type in "Read_ADC_Top"

module Read_ADC_Top
  (input  i_Clk,       // Wire from the Main Clock, 25 MHz
   output o_Segment1_A,  // Wiring to the 1st 7-segment display.
   output o_Segment1_B,
   output o_Segment1_C,
   output o_Segment1_D,
   output o_Segment1_E,
   output o_Segment1_F,
   output o_Segment1_G,
   output o_Segment2_A,  // Wiring to the 2nd 7-segment display.
   output o_Segment2_B,
   output o_Segment2_C,
   output o_Segment2_D,
   output o_Segment2_E,
   output o_Segment2_F,
   output o_Segment2_G,
   output o_LED_1,       // Wiring to the LEDs
   output o_LED_2,
   output o_LED_3,
   output o_LED_4,
   output io_PMOD_1,     // Wiring to the ADC board ~ chip select (active low)
   input  io_PMOD_2,     // data from ADC 0
   input  io_PMOD_3,     // data from ADC 1  (not used)
   output io_PMOD_4,     // clock to read out data
   input i_Switch_1,     // Wires to the switches.  Not used now, but useful for debugging.
   input i_Switch_2,
   input i_Switch_3,
   input i_Switch_4);
 
  wire w_Segment1_A, w_Segment2_A;
  wire w_Segment1_B, w_Segment2_B;
  wire w_Segment1_C, w_Segment2_C;
  wire w_Segment1_D, w_Segment2_D;
  wire w_Segment1_E, w_Segment2_E;
  wire w_Segment1_F, w_Segment2_F;
  wire w_Segment1_G, w_Segment2_G;
  wire w_LED_1, w_LED_2, w_LED_3, w_LED_4;

  wire [11:0] w_ADC_Data;
  wire w_ADC_Data_Valid;
  reg [31:0] r_Readout_Count  = 0;
  parameter Readout_Period = 125000;  // 125000 gives 200 per second (25 MHz clock / 125000 )
  reg [11:0] r_ADC_Data;   // Only low order 12 bits are used.   Four high order bits always zero
  reg r_ADC_Data_Requested = 1'b0;
 
  // Code to read the ADC and put the results on the 7-segment display (upper 8 bits)
  // and the LEDS (lower 4 bits)
  always @(posedge i_Clk) begin
    if (r_Readout_Count >= Readout_Period) begin
      r_Readout_Count <= 0;
      r_ADC_Data_Requested <= 1'b1;    //  Tell Read_ADC we want data
    end else begin
      r_Readout_Count <= r_Readout_Count + 1;
      r_ADC_Data_Requested <= 1'b0;
    end
    if (w_ADC_Data_Valid == 1'b1) begin
    //if (1'b1 == 1'b1) begin
      r_ADC_Data <= w_ADC_Data;    // Get the data from Read_ADC
    end  
  end
 
  // Interface (instantiation) to the code that triggers the ADC and reads its data.
  // parameter ADC_CLKS_PER_BIT determines the readout speed of the bits from the ADC.
  // Its the GoBoard's clock frequency divided by the parameter.
  // Example: 25,000,000 / 25 would give 1 mega-baud.
  // Maximum, according to Analog Devices AD7476 data sheet is 20 mega-baud
  Read_ADC #(.ADC_CLKS_PER_BIT(4)) Read_ADC_Inst   // 4 is gets me 6.25 Mbaud, 160 ns clock
  (.i_Clock(i_Clk),
   .i_ADC_Data_Serial(io_PMOD_2),
   .i_ADC_Data_Requested(r_ADC_Data_Requested),
   .o_ADC_Data_Valid(w_ADC_Data_Valid),
   .o_ADC_Data(w_ADC_Data),
   .o_ADC_Chip_Select_Not(io_PMOD_1),
   .o_ADC_Clock(io_PMOD_4));

  // Binary to 7-Segment Converter for Upper Digit, highest nibble of ADC data
  Binary_To_7Segment SevenSeg1_Inst
  (.i_Clk(i_Clk),
   .i_Binary_Num(r_ADC_Data[11:8]),
   .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;
   
  // Binary to 7-Segment Converter for Lower Digit, middle nibble of ADC data
  Binary_To_7Segment SevenSeg2_Inst
  (.i_Clk(i_Clk),
   .i_Binary_Num(r_ADC_Data[7:4]),
   .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;

  assign o_LED_1 = r_ADC_Data[3];   // Lowest nibble of ADC data will be displayed on the four LEDs
  assign o_LED_2 = r_ADC_Data[2];
  assign o_LED_3 = r_ADC_Data[1];
  //assign o_LED_3 = i_Switch_3;
  //assign o_LED_4 = i_Switch_4;
  //assign o_LED_4 = r_Readout_Count[22];
  assign o_LED_4 = r_ADC_Data[0];
   
endmodule // Read_ADC_Top

========================================================

// Filename:  Read_ADC.v
// ADC Readout for Digilent PMOD AD1, which has two Analog Devices AD7476A 12-bit ADCs
// Written by Peter Halverson and posted on http://halverscience.net/  Nov. 4, 2019
// Based on UART RX from http://www.nandland.com

module Read_ADC
  #(parameter ADC_CLKS_PER_BIT = 25)
  (
   input        i_Clock,
   input        i_ADC_Data_Serial,
   input        i_ADC_Data_Requested,// Set this to True when you want an ADC readout
   output       o_ADC_Data_Valid,    // When this is true, it means the adc data is ready
   output [11:0] o_ADC_Data,         //Its a 12 bit ADC
   output       o_ADC_Chip_Select_Not, // The ~CS line is active when False
   output       o_ADC_Clock          // Falling edge requests next bit, bit is read on rising edge
   );
   
  parameter IDLE                 = 2'b00;   // State Machine states
  parameter ADC_CONVERSION_DELAY = 2'b01;   // Not needed? Datasheet says 10 ns minimum is needed.
  parameter READ_DATA_BITS       = 2'b10;
  parameter CLEANUP              = 2'b11;
 
  reg [7:0]     r_Clock_Count    = 0;
  reg [3:0]     r_Bit_Index      = 15; //4 zero bits + 12 data bits = 16 bits total
  reg [15:0]    r_ADC_Data       = 0;  // First 4 bits always zero
  reg           r_ADC_Data_Valid = 0;
  reg [2:0]     r_SM_Main        = 0;
  reg           r_ADC_Chip_Select_Not = 1;
  reg           r_ADC_Clock      = 1;
  reg [7:0]    r_Delay_Clock_Count = 0;
  reg           r_Got_The_Bit    = 1'b0;
 
  // Read ADC state machine
  always @(posedge i_Clock)
  begin
    case (r_SM_Main)
      IDLE : begin
          r_ADC_Data_Valid    <= 1'b0;
          r_Clock_Count       <= 0;
          r_Delay_Clock_Count <= 0;
          r_Bit_Index         <= 14;  // ADC send 15 bits (not 16)   High order bits first
          r_ADC_Clock         <= 1'b1;
          if (i_ADC_Data_Requested == 1'b1) begin
            r_SM_Main <= ADC_CONVERSION_DELAY;
            r_ADC_Chip_Select_Not <= 1'b0;        // Start the conversion
          end else begin
            r_SM_Main <= IDLE;
            r_ADC_Chip_Select_Not <= 1'b1;        // Don't start the conversion
          end
      end // case: IDLE
      ADC_CONVERSION_DELAY : begin
        if (r_Delay_Clock_Count < 0) begin   // Adjust to give time for ADC to start a conversion
          // 0 gives 80 ns (Three cycles of 25 MHz clock), 1 gives 120 ns, 2 gives 160 ns, etc
          r_Delay_Clock_Count <= r_Delay_Clock_Count + 1;
          r_SM_Main <= ADC_CONVERSION_DELAY;
        end else begin
          r_Delay_Clock_Count <= 0;
          r_SM_Main <= READ_DATA_BITS;
        end
      end // case: ADC_CONVERSION_DELAY
      READ_DATA_BITS : begin
        if (r_Clock_Count < ADC_CLKS_PER_BIT-1) begin
          if (r_Clock_Count < (ADC_CLKS_PER_BIT/2)) begin
            r_ADC_Clock <= 1'b0;     // Falling edge tells ADC to output a bit
            r_Got_The_Bit <= 1'b0;
          end else begin
            r_ADC_Clock <= 1'b1;     // Rising edge is when we get the bit.  (It is now stable)
            if (r_Got_The_Bit == 1'b0) begin
              r_ADC_Data[r_Bit_Index] <= i_ADC_Data_Serial;  // GET THE DATA BIT!!
              r_Got_The_Bit <= 1'b1;   //We want to latch the bit only once
              // Check if we have received all bits
              if (r_Bit_Index > 0) begin      // We need to get more bits (We get 16 bits, 1st four are zero)
                r_Bit_Index <= r_Bit_Index - 1;  //ADC send bit 15 first, then bit 14, ...
                r_SM_Main   <= READ_DATA_BITS;
              end else begin
                r_ADC_Data_Valid <= 1'b1;         // All 15 bits are now valid. Tell caller to get the data.
                r_ADC_Chip_Select_Not <= 1'b1;    // Stop the conversion
                r_SM_Main     <= CLEANUP;
              end
            end
          end
          r_Clock_Count <= r_Clock_Count + 1;
        end else begin
          r_Clock_Count <= 0;
        end
      end // case: READ_DATA_BITS      
      CLEANUP : begin      // Stay here 1 clock
         // r_Delay_Clock_Count <= 0;
          r_SM_Main <= IDLE;
          r_ADC_Data_Valid   <= 1'b0;
      end // case: CLEANUP       
      default :
        r_SM_Main <= IDLE;
    endcase
  end    

  assign o_ADC_Data_Valid   = r_ADC_Data_Valid;
  assign o_ADC_Data = r_ADC_Data[11:0];
  assign o_ADC_Chip_Select_Not = r_ADC_Chip_Select_Not;
  assign o_ADC_Clock = r_ADC_Clock;
endmodule // Read_ADC

==============================================================

// Filename:  Binary_To_7Segment.v
///////////////////////////////////////////////////////////////////////////////
// File downloaded from http://www.nandland.com
///////////////////////////////////////////////////////////////////////////////
// This file converts an input binary number into an output which can get sent
// to a 7-Segment LED.  7-Segment LEDs have the ability to display all decimal
// numbers 0-9 as well as hex digits A, B, C, D, E and F.  The input to this
// module is a 4-bit binary number.  This module will properly drive the
// individual segments of a 7-Segment LED in order to display the digit.
// Hex encoding table can be viewed at:
// http://en.wikipedia.org/wiki/Seven-segment_display
///////////////////////////////////////////////////////////////////////////////
 
module Binary_To_7Segment
  (
   input       i_Clk,
   input [3:0] i_Binary_Num,
   output      o_Segment_A,
   output      o_Segment_B,
   output      o_Segment_C,
   output      o_Segment_D,
   output      o_Segment_E,
   output      o_Segment_F,
   output      o_Segment_G
   );
 
  reg [6:0]    r_Hex_Encoding = 7'h00;
   
  // Purpose: Creates a case statement for all possible input binary numbers.
  // Drives r_Hex_Encoding appropriately for each input combination.
  always @(posedge i_Clk)
    begin
      case (i_Binary_Num)
        4'b0000 : r_Hex_Encoding <= 7'h7E;
        4'b0001 : r_Hex_Encoding <= 7'h30;
        4'b0010 : r_Hex_Encoding <= 7'h6D;
        4'b0011 : r_Hex_Encoding <= 7'h79;
        4'b0100 : r_Hex_Encoding <= 7'h33;          
        4'b0101 : r_Hex_Encoding <= 7'h5B;
        4'b0110 : r_Hex_Encoding <= 7'h5F;
        4'b0111 : r_Hex_Encoding <= 7'h70;
        4'b1000 : r_Hex_Encoding <= 7'h7F;
        4'b1001 : r_Hex_Encoding <= 7'h7B;
        4'b1010 : r_Hex_Encoding <= 7'h77;
        4'b1011 : r_Hex_Encoding <= 7'h1F;
        4'b1100 : r_Hex_Encoding <= 7'h4E;
        4'b1101 : r_Hex_Encoding <= 7'h3D;
        4'b1110 : r_Hex_Encoding <= 7'h4F;
        4'b1111 : r_Hex_Encoding <= 7'h47;
      endcase
    end // always @ (posedge i_Clk)
 
  // r_Hex_Encoding[7] is unused
  assign o_Segment_A = r_Hex_Encoding[6];
  assign o_Segment_B = r_Hex_Encoding[5];
  assign o_Segment_C = r_Hex_Encoding[4];
  assign o_Segment_D = r_Hex_Encoding[3];
  assign o_Segment_E = r_Hex_Encoding[2];
  assign o_Segment_F = r_Hex_Encoding[1];
  assign o_Segment_G = r_Hex_Encoding[0];
 
endmodule // Binary_To_7Segment