FPGA-based photon counter for the Red Pitaya STEMlab 125-14. Real-time pulse counting at 125 MSPS using the onboard FPGA, with a TCP server and Python client for remote control and live monitoring.
Designed for SiPM/SPAD single-photon detectors. Tested with the Thorlabs PDA42 SiPM amplified detector for Raman spectroscopy and low-light applications.
- 125 MSPS continuous pulse detection on FPGA (no dead gaps)
- Configurable threshold discriminator with adjustable dead time
- 32-bit pulse counter + gated count rate measurement
- 64-bin pulse height histogram for amplitude analysis
- TCP server on Red Pitaya ARM for remote control
- Python client with real-time matplotlib live monitoring
- Runs alongside the standard Red Pitaya v0.94 ecosystem (web UI still works)
- Red Pitaya STEMlab 125-14 (tested on Pro v2.0, model
z10_125_pro_v2) - SiPM or SPAD detector with voltage pulse output (e.g., Thorlabs PDA42)
- SMA cable connecting detector to IN1
- Direct Ethernet connection between Red Pitaya and PC
Set the HV jumper (right position) behind the IN1 SMA connector for the +-20V input range. This is required for the PDA42 which outputs up to +-2V.
- Xilinx Vivado 2020.1 WebPACK (free, for building FPGA bitstream)
- Python 3.12+ with uv
- SSH access to Red Pitaya (
root/rootdefault credentials)
-
Clone the Red Pitaya FPGA repo into this project:
cd ~/rp-photon-counter git clone --depth 1 https://github.com/RedPitaya/RedPitaya-FPGA.git
-
Patch the FPGA project to add the photon counter module:
bash fpga/apply_patch.sh
-
Build the bitstream (takes ~20 minutes):
source /opt/Xilinx/Vivado/2020.1/settings64.sh cd RedPitaya-FPGA make PRJ=v0.94 MODEL=Z10
Note: The build will report an error about
xsct— this is expected (FSBL compilation, not needed). -
Convert the bitstream:
cd prj/v0.94/out echo "all:{ red_pitaya.bit }" > red_pitaya.bif bootgen -image red_pitaya.bif -arch zynq -process_bitstream bin -o red_pitaya.bit.bin -w
-
Deploy to Red Pitaya:
scp red_pitaya.bit.bin root@<RP_IP>:/root/photon_counter.bit.bin ssh root@<RP_IP> mount -o rw,remount /opt/redpitaya cp /root/photon_counter.bit.bin /opt/redpitaya/fpga/z10_125_pro_v2/v0.94/fpga.bit.bin sync mount -o ro,remount /opt/redpitaya reboot
-
Verify deployment after reboot:
ssh root@<RP_IP> /opt/redpitaya/bin/monitor 0x40700014 # Should return 0x07735940 (gate period default)
-
Start the TCP server on the Red Pitaya:
ssh root@<RP_IP> '/root/start_photon.sh'
-
Run the live monitor on your PC:
cd client uv run python3 live_monitor.py --threshold 28 --histogram -
Or use the Python client programmatically:
from photon_client import PhotonCounter pc = PhotonCounter("169.254.32.2") pc.set_threshold(28) pc.set_deadtime(16) pc.enable() rate = pc.get_rate() print(f"Count rate: {rate.cps:.0f} cps") histogram = pc.get_histogram() pc.close()
Run a threshold scan to find the optimal discrimination point for your detector:
# With detector connected and covered (dark counts only):
# Sweep threshold and observe where count rate drops sharply.
# For PDA42 SiPM in HV mode: optimal threshold ~28 ADC units
# Dark count rate at threshold 28: ~5,000 cps (typical for 3mm TEC-cooled SiPM)rp-photon-counter/
fpga/
rtl/photon_counter.sv # FPGA module (SystemVerilog)
apply_patch.sh # Patches Red Pitaya top module
server/
photon_server.py # TCP server (runs on RP ARM)
client/
photon_client.py # Python client library
live_monitor.py # Real-time matplotlib plotting
pyproject.toml # Python project config
test_devmem.sh # Low-level register test
The FPGA module (photon_counter.sv) taps into the Red Pitaya's ADC data stream at 125 MSPS and performs real-time threshold discrimination:
- Threshold crossing detection: Fires when
ADC[n] >= thresholdandADC[n-1] < threshold - Dead time: Ignores subsequent crossings for a configurable number of clock cycles
- Counting: Increments a 32-bit counter per detected pulse; also computes gated count rate
- Histogram: Bins the ADC value at each threshold crossing into a 64-bin histogram
All configuration and readout happens through memory-mapped registers at base address 0x40700000, accessible from Linux via /dev/mem.
MIT