How to generate sine wave in FPGA?

 Here are the most practical ways to generate a sine wave on an FPGA—from “quick & clean” to “resource-lean.” I’ll give you design rules, formulas, and drop-in HDL.



The main approaches (pick one)

MethodWhen to useNotes
DDS/NCO + LUTEasiest, precise frequency controlPhase accumulator → lookup table. Best with an external DAC or 1-bit ΣΔ + RC.
CORDICYou want to save BRAM / make sin/cos on the flyUses add/shift stages; needs scaling factor compensation.
PWM or ΣΔ DACNo external DAC availableFilter the pin with RC/active LPF. ΣΔ generally cleaner than plain PWM.

1) DDS / NCO + LUT (golden path)

How it works

  • Phase accumulator: phase <= phase + phase_inc;

  • Top M bits of phase index a sine LUT (ROM/BRAM).

  • Frequency:

    fout=phase_inc2Nfclkf_\text{out} = \frac{\text{phase\_inc}}{2^N}\,f_\text{clk}
  • Resolution example (N=32, fclk=100 MHz): ~0.0233 Hz steps.

Compute phase_inc: round(f_out * 2^N / f_clk)
– 1 MHz @ 100 MHz, N=32 → 42949673
– 440 Hz @ 100 MHz, N=32 → 18898

Minimal SystemVerilog (NCO + LUT, optional linear interpolation)

module nco_sine #( parameter int N = 32, // phase accumulator width parameter int ADDR_BITS = 12, // LUT points = 2^ADDR_BITS parameter int AMP_BITS = 12 // sample width )( input logic clk, input logic [N-1:0] phase_inc, output logic [AMP_BITS-1:0] sample // unsigned 0..(2^AMP_BITS-1) (shift if you need signed) ); // Phase accumulator logic [N-1:0] phase; always_ff @(posedge clk) phase <= phase + phase_inc; // Use top ADDR_BITS as LUT address; next L bits as frac for interpolation localparam int FRAC_BITS = 4; // tweak 0..4; 0 = no interpolation logic [ADDR_BITS-1:0] addr = phase[N-1 -: ADDR_BITS]; logic [FRAC_BITS-1:0] frac = phase[N-1-ADDR_BITS -: FRAC_BITS]; // ROM: 2^ADDR_BITS × AMP_BITS; quarter-wave compression is optional. // Initialize from a hex file generated offline (0..(2^AMP_BITS-1) sine). logic [AMP_BITS-1:0] lut [0:(1<<ADDR_BITS)]; initial $readmemh("sine12_4096.hex", lut); // Fetch two adjacent samples for linear interpolation logic [AMP_BITS-1:0] y0, y1; always_ff @(posedge clk) begin y0 <= lut[addr]; y1 <= lut[addr + 1]; // ROM has one extra guard entry copied from index 0 end // Linear interpolation (optional) logic signed [AMP_BITS:0] diff = $signed({1'b0,y1}) - $signed({1'b0,y0}); logic [AMP_BITS+FRAC_BITS:0] interp = ({y0,{FRAC_BITS{1'b0}}}) + (diff <<< FRAC_BITS) * frac; // scaled always_ff @(posedge clk) sample <= interp[AMP_BITS+FRAC_BITS -: AMP_BITS]; // drop frac bits endmodule

Notes

  • If you prefer quarter-wave LUT to save BRAM: store 1/4 cycle and mirror/negate by the two MSBs (quadrant).

  • To output signed samples, center them around 0 (e.g., subtract 2^(AMP_BITS-1)).

  • For sin & cos, just tap different phase offsets (cos = sine with +90° phase).

Getting it out of the FPGA

  • Best quality: send samples to a parallel or SPI DAC, then analog-filter.

  • No DAC? Use a 1-bit ΣΔ DAC to a pin + RC/active low-pass filter (see below).

  • Clocking: Derive f_clk from a PLL/MMCM with low jitter if spurs matter.


2) 1-bit ΣΔ DAC (cleaner than PWM)

A simple second-order ΣΔ pushes noise up in frequency; RC filter recovers the sine.

module sd_dac_1bit #( parameter int W = 16 // input width )( input logic clk, input logic [W-1:0] x, // unsigned or signed; keep consistent output logic bitout // drive to a GPIO pin ); // Two accumulators (MASH-like) logic [W:0] acc1, acc2; always_ff @(posedge clk) begin acc1 <= acc1[W-1:0] + x; // integrate input acc2 <= acc2[W-1:0] + acc1[W:0]; // integrate again bitout <= acc2[W]; // MSB as 1-bit output end endmodule

RC example for ~10 kHz cutoff: R=3.3 kΩ, C=4.7 nF (fc ≈ 10.3 kHz). Use a higher ΣΔ clock (e.g., 25–100 MHz) so most noise is above the audio band. For better THD/IMD, consider a 2-pole or active LPF.


3) CORDIC (no LUT)

Computes sin/cos by iterative rotate-add-shift. Great when BRAM is tight or you need variable amplitude/phase with small footprint.

  • Scale factor: rotation mode gains 1/K ≈ 1.64676; compensate by pre-scaling inputs or right-shifting after the pipeline (K ≈ 0.607252935).

  • Latency: ≈ number of iterations (e.g., 16–20 stages), but fully pipelinable (1 result/cycle).

  • Excellent for NCO/mixer chains (sin/cos from a single phase word).


4) Plain PWM (works, but noisier)

  • Use a fast carrier (e.g., ≥200 kHz) and modulate duty with your sine samples.

  • THD is generally worse than ΣΔ; filter must strongly attenuate the carrier.


Practical design checklist

  • Pick your clock (f_clk) and accumulator width (N). Wider N → finer frequency steps & lower phase-truncation spurs.

  • LUT depth vs. spurs: 1–4 k points is typical. Interpolation (even 4 frac bits) helps a lot.

  • Dither the truncated phase bits to reduce pattern spurs if needed.

  • Amplitude scaling: fixed-point multiply your sine by a gain for volume control/AM.

  • Output stage: prefer a real DAC if you need low noise; otherwise ΣΔ + LPF.

  • Constraints: add timing constraints for the NCO/ΣΔ paths; keep ROM and adders near each other to meet Fmax.


Tiny “full path” example (NCO → ΣΔ → pin)

module sine_out_top( input logic clk100, // 100 MHz output logic audio_pin // 1-bit DAC out ); // 1 kHz tone @ 100 MHz, N=32 localparam int N=32; localparam int ADDR_BITS=12, AMP_BITS=16; localparam int PHASE_INC = 32'd42949673; // ≈1 MHz -> change to 42950 for ~1 kHz, or compute offline logic [AMP_BITS-1:0] sample; nco_sine #(.N(N), .ADDR_BITS(ADDR_BITS), .AMP_BITS(AMP_BITS)) nco(.clk(clk100), .phase_inc(PHASE_INC), .sample(sample)); // 1-bit ΣΔ DAC sd_dac_1bit #(.W(AMP_BITS)) dac(.clk(clk100), .x(sample), .bitout(audio_pin)); endmodule

Hardware: route audio_pin through R=3.3 kΩ to an RC low-pass (e.g., 3.3 kΩ + 4.7 nF to GND). For line-level, buffer with an op-amp LPF.


Vendor IP (if you like clicks over code)

  • AMD/Xilinx DDS Compiler IP (Vivado) and CORDIC IP.

  • Intel/Altera NCO IP.
    These give you parameter GUIs, interpolation, dither, and AXI-Stream ports out of the box.

评论

此博客中的热门博文

Detailed Explanation of STM32 HAL Library Clock System

How to remove write protection of STM32 chip?

The automatic white balance algorithm of Raspberry Pi