---
# System prepended metadata

title: How to implement ADC on an FPGA?
tags: [FPGA]

---

There are three practical ways to “implement ADC on an [FPGA](https://www.ampheo.com/c/fpgas-field-programmable-gate-array),” depending on what you really need:
![FPGA-kit-with-the-ADC-DAC-data-conversion-board-emphasizing-the-dip-switches-and-the](https://hackmd.io/_uploads/Bykjpfxsxl.png)

**1) Use an external ADC and interface it (most common)**

You don’t synthesize the analog converter in LUTs—you capture its digital outputs.

**Pick an interface by speed:**

* Low speed (≤1–2 MSPS): SPI/I²C SAR ADC (e.g., [MCP3008](https://www.onzuu.com/search/MCP3008), [ADS7042](https://www.onzuu.com/search/ADS7042)).
* Mid speed (10–125 MSPS): Parallel CMOS/LVDS data + data clock (source-synchronous).
* High speed (100 MSPS–Gsps): JESD204B/C over high-speed serial transceivers.

**FPGA tasks:**

Clean clocking (PLL/MMCM), source-synchronous capture (IDDR/ISERDES), lane alignment, FIFO, optional digital filtering and calibration.

**Minimal SPI SAR example (MCP3008-style, 10-bit):**

```
module spi_adc_mcp3008 (
  input  wire       clk,           // e.g., 50 MHz
  input  wire       start,         // one-shot start conversion
  input  wire [2:0] ch,            // channel 0..7
  output reg        busy,
  output reg [9:0]  data,
  // SPI pins
  output reg        cs_n, sclk, mosi,
  input  wire       miso
);
  localparam IDLE=0, CMD=1, READ=2, DONE=3;
  reg [1:0] state;
  reg [5:0] bitcnt;
  reg [15:0] shifter;

  always @(posedge clk) begin
    case (state)
      IDLE: begin
        sclk<=0; cs_n<=1; busy<=0;
        if (start) begin
          // Start bit=1, SGL/DIFF=1, D2..D0=channel
          shifter <= {5'b11000 | ch, 11'd0}; // command frame
          bitcnt  <= 0;
          cs_n    <= 0; busy <= 1; state <= CMD;
        end
      end
      CMD: begin
        // SPI mode 0
        sclk <= ~sclk;
        if (!sclk) mosi <= shifter[15];
        else begin
          shifter <= {shifter[14:0],1'b0};
          bitcnt  <= bitcnt + 1;
          if (bitcnt==15) begin bitcnt<=0; state<=READ; end
        end
      end
      READ: begin
        sclk <= ~sclk;
        if (sclk) begin
          shifter <= {shifter[14:0], miso};
          bitcnt  <= bitcnt + 1;
          if (bitcnt==11) begin // 1 null + 10 bits
            data  <= shifter[9:0];
            state <= DONE;
          end
        end
      end
      DONE: begin cs_n<=1; busy<=0; state<=IDLE; end
    endcase
  end
endmodule
```


**Parallel/LVDS capture sketch:**

* Use the ADC’s output data clock (DCO) as input to an IDDR/ISERDES slice to sample data lines.
* Phase-shift DCO with MMCM for optimal setup/hold.
* Constrain with XDC: input delays, clock groups, and false paths across clock domains.

```
# Example XDC snippets
create_clock -name dco_clk -period 5.000 [get_ports ADC_DCO]     ;# 200 MHz
set_input_delay  -clock dco_clk 1.0  [get_ports {ADC_D[*]}]
set_false_path -from [get_clocks dco_clk] -to [get_clocks sys_clk]
```


**JESD204B/C:**

* Use FPGA GT/GTH transceivers + vendor JESD IP (link layer, transport).
* Tasks: CGS/ILAS bring-up, lane deskew, deterministic latency, LMFC alignment, subclass selection.

**2) Use the FPGA’s built-in ADC hard block (if available)**

Some devices have on-chip ADCs (e.g., [Xilinx 7-series](https://www.vemeko.com/product/#xilinx) XADC, [Intel MAX 10](https://www.vemeko.com/max-10-cplds/) ADC, some Lattice parts).

* Great for board monitoring (voltages, temperature) or low-speed sensors.
* Instantiate vendor IP core; read samples via AXI/APB or simple registers; add a small moving-average/IIR filter.

**3) Build a discrete ADC around the FPGA (hybrid)**

You can implement the digital side of certain ADC architectures and use a few external analog parts:

**Sigma-Delta (1st/2nd-order):**

* External: 1-bit comparator + simple RC (integrator).
* FPGA: generate 1-bit DAC (PWM/DS), read comparator, run CIC/decimation to produce N-bit samples.
* Pros: excellent resolution for audio/slow sensors; minimal analog; Cons: latency, needs good clocking.

**SAR-style loop:**

* External: comparator + [resistor](https://www.onzuu.com/category/resistors) ladder/DAC;
* FPGA: binary search over DAC codes, read comparator to converge on N-bit result.
* Good for learning/medium speeds; not trivial to make precision-grade.

**Tiny 1-bit ΣΔ decimator (CIC) example (conceptual):**

```
// Sum 1-bit stream (signed) and decimate by R
module cic_decimator #(parameter R=256, W=24)(
  input  wire clk,               // modulator clock
  input  wire sd_bit,            // 1-bit stream: 1->+1, 0->-1
  output reg  valid,
  output reg  signed [W-1:0] y
);
  reg signed [W-1:0] acc;
  reg [$clog2(R)-1:0] cnt;
  wire signed [1:0] s = sd_bit ? 2'sd1 : -2'sd1;
  always @(posedge clk) begin
    acc <= acc + s;
    cnt <= cnt + 1;
    if (cnt==R-1) begin
      y <= acc; valid <= 1'b1; acc <= '0; cnt <= '0;
    end else valid <= 1'b0;
  end
endmodule
```

**System design checklist**

* Clocking & Jitter: ADC SNR is clock-jitter sensitive. Use low-jitter clocks (XO + PLL).
* IO Standards: Match ADC IO (LVDS, CMOS 1.8/2.5/3.3 V), set proper termination.
* Timing Closure: For source-synchronous, use IDELAY/ISERDES and constrain correctly.
* Throughput buffering: CDC FIFOs from ADC domain → system/AXI domain.
* Digital filtering: Moving average/IIR for slow SAR; FIR/CIC/halfband for ΣΔ or decimation chains.
* Calibration: Offset/gain/phase correction; background calibration for multichannel.
* Board layout: Short, impedance-controlled pairs for LVDS; isolate analog ground; solid power decoupling near ADC.

**Quick “which path should I take?” guide**

* You need real performance (audio–RF, reliable specs): Use a commercial ADC and capture it in [FPGA](https://www.ampheoelec.de/c/fpgas-field-programmable-gate-array).
* You need quick board telemetry or very low speed: Use the FPGA’s internal ADC (if present).
* You want to learn/experiment, audio-band: Try a sigma-delta with comparator + RC and do the [DSP](https://www.ampheo.com/c/dsp-digital-signal-processors) in FPGA.