I originally intended to continuously log my progress on hackaday so others can take part in my learning process. Unfortunately I got carried away in the development and the project is therefore not a good reference to follow for newcomers. But I think it can still be of value for people interested in FPGA based signal processing.

Using the SingularitySurfer to measure the change in phase due to a change in distance of two speakers (one serves as a crappy microphone):

Block diagram:

Digital architecture:

All of the FPGA cores run at a 36MHz base clock and the numeric oscillator, DDS, PLL and DACs also run at this sample frequency. However the ADC samples at 900kHz and the digital filters further downsample the signal by a factor of 1024. The DDS and the ADC produce signed 16 bit signals, while the output of the multipliers and filters are signed 32 bit. The phase signals from the PLL and oscillator are 18 bit. Due to FPGA LUT constraints, the CORDIC core only uses signed 16bit precision and the algorithm is terminated after 9 iterations. Most of the cores, especially the CORDIC core, make use of pipelining to maintain the 36MHz clock. The full design takes up about 85% of the FPGA LUTs.


The Lattice ICE40UP5K is an ideal platform for a small DSP project like this. It offers:

  • sufficient LUTs for all of the wiring, UI, display and housekeeping
  • 8 16x16 hardware multipliers for signal processing
  • 1Mbit of internal SRAM for storing a high fidelity sinus lookup table
  • QFP package which is easily soldered by hand
  • affordable price at just 5€
  • fully open source toolchain!

Analog frontend:

As all of the digital signal processing is deterministic and can be upgraded at a later point, the Lock-In’s performance is ultimately determined by the analog frontend. The goal is to transfer the signal into the digital domain with little disturbance. This requires high dynamic range, low noise and high linearity. But as cost was also a factor in the development, compromises had to be made. The design employs:

  • ADC: MCP33131D, 1MSps, 16bit, 91.3dB SNR 
  • Voltage reference: REF5050, low noise, low drift
  • PGA: AD8253, 0-60dB gain, low distortion, low noise, instrumentation amplifier
  • OpAmp: AD8675, low noise
  • Voltage regulator: LT3032-15, dual +-15V, low noise

At the input there is a 100kHz RC lowpass and the ADC AA filter is tuned to 100kHz as well. The PGA doubles as the ADC driver, using the AD8675 to make the signal differential. This setup was used to cut the cost of dedicated ADC driver. Unfortunately it does limit performance, as it does introduce some distortion due to the PGA output not being fast enough to drive the ADC in this configuration (which is not critical as distortion is essentially the same as noise well outside the passband of the digital lowpass). Broadband noise from the analog components was below the quantization noise and therefore not visible at the ADC output.

DDS and numeric oscillator:

The direct digital synthesis core generates a digital representation of a sinusoidal using a 1/4 sinus look-up table stored in the 1Mbit SRAM. The other 3/4 of the sine and the cosine can be easily generated from the first 1/4 using symmetries. Therefore a high fidelity, 16bit amplitude, 18bit phase representation of the sinusoidals can be generated. When using an external reference signal, the DDS uses the phase of the digital PLL locked to the external reference signal. When generating its own reference signal, a numeric oscillator with an internal 32bit phase accumulator is employed. 

In order to avoid spurious tones in the output spectrum of the DDS when using the internal oscillator, dithering of the last phase-bit is applied using a linear-feedback shift register. Unfortunately the ICE40 SRAM blocks don't support initial values in the FPGA bitstream. Therefore the look-up table is permanently stored in the external SPI flash and loaded into SRAM after every startup.


As the Lock-In’s...

Read more »