Implementing Analog-to-Digital Converter (ADC) acquisition is a fundamental and common task in FPGA design. The method depends heavily on the type of ADC interface.
Here’s a breakdown of the most common ADC types and how to acquire data from them using an FPGA.
1. Serial Interface ADCs (SPI / Microwire)
This is one of the most common types for lower-speed, lower-channel-count applications. Examples: ADCs from Analog Devices (AD7xxx series), Texas Instruments (ADS7xxx series).
Key Signals:
- SCLK (Serial Clock): Generated by the FPGA (SPI controller master).
- CS_n (Chip Select): Active-low signal generated by the FPGA to initiate a conversion and frame the data transfer.
- SDATA (Serial Data/MISO): The data line from the ADC to the FPGA. Data is shifted out MSB-first or LSB-first on each SCLK edge.
- Sometimes: DIN (MOSI): For configuring the ADC's internal registers.
Acquisition Method (Finite State Machine - FSM):
The FPGA implements an SPI master controller, typically with a state machine.
Example Verilog FSM for a 16-bit SPI ADC:
verilog
module spi_adc_controller (
input wire clk, // FPGA system clock (e.g., 100 MHz)
input wire reset_n,
input wire start_conv, // Signal to start a new conversion
output reg cs_n, // To ADC
output reg sclk, // To ADC
input wire sdata, // From ADC
output reg [15:0] data, // Parallel output data
output reg data_valid // Pulses high when 'data' is valid
);
// States for the FSM
localparam [1:0] STATE_IDLE = 2'b00;
localparam [1:0] STATE_CONV = 2'b01; // Conversion time (CS_n low to first SCLK)
localparam [1:0] STATE_READ = 2'b10; // Shifting in data
reg [1:0] current_state, next_state;
reg [4:0] bit_counter; // Counts 16 bits (0 to 15)
reg [15:0] data_reg; // Shift register to accumulate serial data
reg [7:0] clk_divider; // Counter to generate slower SCLK
reg sclk_enable; // Enable signal for SCLK generation
// State Register
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
current_state <= STATE_IDLE;
else
current_state <= next_state;
end
// Next State Logic
always @(*) begin
next_state = current_state;
case (current_state)
STATE_IDLE: if (start_conv) next_state = STATE_CONV;
STATE_CONV: if (clk_divider == 8'd50) next_state = STATE_READ; // Wait for conversion time
STATE_READ: if (bit_counter == 5'd16) next_state = STATE_IDLE; // Done reading 16 bits
endcase
end
// Output Logic & Counters
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
cs_n <= 1'b1;
sclk <= 1'b0;
clk_divider <= 8'b0;
bit_counter <= 5'b0;
data_reg <= 16'b0;
data_valid <= 1'b0;
sclk_enable <= 1'b0;
end else begin
data_valid <= 1'b0; // Default value
case (current_state)
STATE_IDLE: begin
cs_n <= 1'b1;
sclk <= 1'b0;
sclk_enable <= 1'b0;
bit_counter <= 5'b0;
end
STATE_CONV: begin
cs_n <= 1'b0; // Pull CS_n low to start conversion
// Wait for a period to meet ADC T_CONV spec
if (clk_divider < 8'd50)
clk_divider <= clk_divider + 1;
else begin
clk_divider <= 8'b0;
sclk_enable <= 1'b1; // Enable SCLK for the read phase
end
end
STATE_READ: begin
// Generate SCLK by dividing the system clock
if (sclk_enable) begin
if (clk_divider < 8'd25) begin
clk_divider <= clk_divider + 1;
end else begin
clk_divider <= 8'b0;
sclk <= ~sclk; // Toggle SCLK
// On the falling edge of SCLK (ADC data is stable), sample the data
if (sclk == 1'b1) begin
data_reg <= {data_reg[14:0], sdata}; // Shift left
bit_counter <= bit_counter + 1;
if (bit_counter == 5'd15) begin
// On the last bit, latch the data and signal it's valid
data <= {data_reg[14:0], sdata};
data_valid <= 1'b1;
sclk_enable <= 1'b0; // Stop SCLK
end
end
end
end
end
endcase
end
end
endmodule
2. Parallel Interface ADCs (Wide-Bus)
Used for high-speed, high-resolution applications. Examples: AD92xx, AD96xx series.
Key Signals:
- D[0:N-1]: Parallel data bus.
- DCO (Data Clock Output): A clock output by the ADC that is synchronous to the data. The FPGA uses this to capture the data.
- FCO (Frame Clock Output): Signals the start of a data frame (often aligns with the first or last sample).
Acquisition Method (Double Data Rate - DDR):
The challenge is that the data rate is very high. The DCO is often DDR (Double Data Rate), meaning data is valid on both its rising and falling edges.
FPGA Primitives Used:
- IDDR (Input Double Data Rate): A primitive inside the FPGA's I/O logic (IOB). It captures data on both clock edges and presents it as two parallel words on the FPGA's internal clock rate.
- IDELAY: Used to finely adjust the timing of the input data to ensure it meets the setup/hold requirements of the DCO.
Simplified Flow:
- The DCO from the ADC is routed to a global clock buffer (BUFG/BUFIO) inside the FPGA.
- The parallel data lines (D[0:N-1]) are routed to IDDR primitives, clocked by the DCO.
- The FCO is similarly captured and used to frame the parallel data from the IDDRs.
- The two parallel words from the IDDR are combined into a single data stream at the internal clock rate.
Example Snippet (Conceptual - using Xilinx IDDR):
verilog
// This is a conceptual example. Actual implementation uses vendor-specific primitives.
wire dco; // From ADC
wire [7:0] adc_din; // 8-bit data bus from ADC
reg [15:0] captured_data;
// For each bit in the bus, an IDDR is used.
IDDR #(
.DDR_CLK_EDGE("OPPOSITE_EDGE"),
.SRTYPE("SYNC")
) IDDR_inst [7:0] (
.Q1(rise_data[7:0]), // Data on rising edge of DCO
.Q2(fall_data[7:0]), // Data on falling edge of DCO
.C(dco), // DDR clock from ADC
.CE(1'b1),
.D(adc_din[7:0]), // From ADC pin
.R(1'b0),
.S(1'b0)
);
// Now, combine the two edges into one word at half the DCO rate.
always @(posedge fpga_sys_clk) begin
captured_data <= {rise_data, fall_data}; // Form a 16-bit word
data_valid <= 1'b1; // This is valid every cycle of fpga_sys_clk
end
3. Delta-Sigma (ΔΣ) ADCs (e.g., Audio CODECs)
These are often serial interface ADCs but with a specific continuous data stream.
Key Signals:
- BCLK (Bit Clock): Continuous serial clock.
- LRCLK (Left/Right Clock): Word select clock (High = Left channel, Low = Right channel).
- DIN/DOUT: Serial data, typically transmitted MSB-first on one BCLK edge and captured on the opposite edge.
Acquisition Method (Shift Register):
The FPGA synchronizes to the LRCLK to know which channel is being received and uses the BCLK to shift in the data.
verilog
always @(posedge bclk) begin
// Shift data in on each BCLK pulse
audio_shift_reg <= {audio_shift_reg[22:0], sdata};
// When LRCLK changes, the shift register is full
if (lrclk != lrclk_prev) begin
lrclk_prev <= lrclk;
if (lrclk == 1'b1)
left_channel_data <= audio_shift_reg;
else
right_channel_data <= audio_shift_reg;
// Reset the shift register for the next word
audio_shift_reg <= 24'b0;
end
end
General Workflow for Any ADC Interface
- Read the Datasheet Meticulously: Understand the timing diagrams, setup/hold times (t_SU, t_HD), and protocol.
- Choose the Acquisition Method: Based on the interface (SPI, Parallel, LVDS, etc.).
- Design a State Machine (FSM): For SPI and other controlled protocols, an FSM is almost always the correct solution.
- Generate Precise Timings: Use counters clocked by the system clock to generate CS_n pulse widths, SCLK frequencies, and meet t_CONV requirements.
- Synchronize Asynchronous Signals: If an ADC signal (like a Data Ready interrupt) is not synchronous to the FPGA's clock, use a synchronizer chain (two flip-flops) to prevent metastability.
verilog
reg [1:0] sync_reg;
always @(posedge clk) sync_reg <= {sync_reg[0], adc_drdy};
wire adc_drdy_sync = sync_reg[1]; // Now synchronized
- Simulate Thoroughly: Write a testbench that models the ADC's behavior. This is crucial for verifying your controller's state machine and timing.
- Constrain Your Design: In your FPGA toolchain (Vivado/Quartus), provide timing constraints for the ADC clocks to ensure the physical implementation meets timing.
Top comments (0)