How to debug FPGA board with live inputs?

Here’s a practical playbook for debugging an FPGA with live inputs (sensors/cameras/encoders/high-speed I/O). It balances non-intrusive capture, real-time visibility, and safety—plus copy-paste examples for Xilinx and Intel.
0) Before you touch anything (live = real hardware!)
Safety first: If your design can drive motors/relays, add a hard E-STOP and an “outputs-inhibit” bit you can force during debug.
Clock & voltage sanity: Verify input levels, terminations (50 Ω SE / 100 Ω diff), VREF/VCCO, and that your sampling clock is the right domain for the signals.
Repro knob: Add a way to freeze/slow the source (pause camera, halt encoder) or record & replay traffic.
1) Use an embedded logic analyzer (non-intrusive visibility)
Xilinx (Vivado): ILA + VIO (a.k.a. ChipScope)
- Tag nets so the tool keeps them and routes them to debug:
// Tag nets for debug
(* mark_debug = "true" *) wire vsync, hsync, data_valid;
(* mark_debug = "true" *) wire [7:0] rx_data;
- Instantiate an ILA (IP Catalog → ILA). Connect it in your RTL:
ila_0 u_ila (
.clk (pclk), // sample in the same clock domain
.probe0 (rx_data),
.probe1 (vsync),
.probe2 (hsync),
.probe3 (data_valid)
);
- (Optional) VIO for live control (force test modes, thresholds, mux selects):
wire [1:0] dbg_sel;
vio_0 u_vio (.clk(pclk), .probe_out0(dbg_sel));
- In Hardware Manager, set triggers (e.g., “data_valid drops while rx_data=0xBC”), pick a depth (e.g., 8–128 k samples), run captures without halting the design.
Tips
Use the same clock as the signals you probe.
Start small (few probes, shallow depth) → scale up.
If nets get optimized away, add
(* keep = "true" *)orset_property DONT_TOUCH true ….
Intel (Quartus): SignalTap + In-System Sources/Probes
Add a SignalTap file (.stp), select the clk domain, choose signals, set triggers, and compile.
For runtime controls, use In-System Sources/Probes IP (similar to VIO) to drive internal test muxes, resets, etc.
Synthesis keepers
- Verilog:
(* preserve *)on regs/wires, or/* synthesis keep */.
Lattice: Reveal Analyzer
- Same idea: instantiate Reveal, choose probes/clock, set triggers, capture.
2) Design-for-debug patterns (so live debug is easy)
- Debug MUX on inputs: switch between live and known test patterns with a register/VIO.
wire [7:0] prbs_out;
wire [7:0] in_mux = (dbg_sel==2'b00) ? live_rx
: (dbg_sel==2'b01) ? prbs_out
: 8'h55; // fixed
Record & Replay: Tap the raw input → FIFO → DDR, then play it back via a traffic generator. Lets you repeat rare glitches.
Sticky flags & counters: Latch “bad things” once (CRC error, underflow, CDC violation) and expose them via a CSR/AXI-Lite block.
Timestamps: Free-running counter sampled on events; makes cross-domain correlation possible.
Heartbeat LEDs/UART prints: A 1 Hz LED per clock domain, and a minimal UART-TX for hexdumps (when you can’t hook a scope).
3) CDC & timing when inputs are live
Synchronize async inputs: at least 2-FF synchronizers for single-bit strobes; use async FIFOs or handshake for buses.
Prove timing: Constrain input delays (
set_input_delay/set_output_delay) and check timing to the pin—don’t rely on “it seems fine.”Catch metastability symptoms: Duplicate comparators in parallel and XOR the results; if they differ, you’re on the edge → slow edges/add sync.
4) External instruments (when you must)
MSO/logic analyzer: Clip on buffered test pads (don’t load the net).
Pattern generator: Feed a golden pattern to the FPGA while keeping the environment running (splitter/fixture).
Differential probes/terminations: Respect 100 Ω for LVDS; avoid long stubs.
5) Triggers that actually catch the bug
Protocol-aware conditions: e.g., “VSYNC while active line,” “gap > N cycles,” “CRC bad then good.”
Pre-trigger depth: Large pre-trigger helps you see why it broke, not just the crash.
Multi-stage triggers: Trigger A arms B; use it for “rare after common” sequences.
6) Minimal, reusable examples
A) Xilinx: mark, probe, trigger (camera-like stream)
RTL
(* mark_debug = "true" *) wire dv, hs, vs;
(* mark_debug = "true" *) wire [7:0] d;
ila_0 u_ila (.clk(pclk), .probe0(d), .probe1(dv), .probe2(hs), .probe3(vs));
// Optional: swap live input for test pattern during debug
wire [7:0] prbs;
prbs8 u_prbs(.clk(pclk), .en(test_en), .dout(prbs));
wire [7:0] d_in = test_en ? prbs : live_d;
Constraints (keep nets if needed)
set_property MARK_DEBUG true [get_nets -hier {d[*] dv hs vs}]
Trigger idea: dv falling edge AND d == 8'hBC within 3 cycles after hs—set in Hardware Manager.
B) Intel: quick SignalTap preserve
Attributes
(* preserve *) reg [7:0] d_q;
(* preserve *) reg dv_q;
always @(posedge pclk) begin
d_q <= live_d;
dv_q <= live_dv;
end
Select d_q, dv_q in SignalTap, set clock = pclk, compile, capture.
C) One-wire UART-TX for quick prints (any FPGA)
module uart_tx #(parameter CLK=100_000_000, BAUD=115200) (
input wire clk, input wire strobe, input wire [7:0] data, output reg tx=1
);
localparam DIV = CLK/BAUD;
reg [15:0] cnt=0; reg [3:0] bitn=0; reg [9:0] sh=10'h3FF; // idle=1
always @(posedge clk) begin
if (strobe && bitn==0) begin sh <= {1'b1, data, 1'b0}; bitn <= 10; cnt <= DIV-1; end
else if (bitn!=0) begin
if (cnt==0) begin tx <= sh[0]; sh <= {1'b1, sh[9:1]}; bitn <= bitn-1; cnt <= DIV-1; end
else cnt <= cnt-1;
end else tx <= 1;
end
endmodule
Hook tx to a header and view in a serial terminal for quick hex/status traces.
7) “It only fails at speed” tactics
Capture in the native domain (run ILA/SignalTap at the real input clock).
Relax probe fanout: Re-register tapped nets before probing to meet timing.
Reduce debug load: Fewer probes/depth → easier timing closure.
If still marginal, floorplan the ILA beside the logic (same SLR/column).
8) Quick checklist (print me)
Inputs have correct voltage/termination, clock domain identified.
Add debug MUX (live/test), sticky flags, counters, timestamps.
Tag suspect nets (
mark_debug/preserve), instantiate ILA/SignalTap.Set pre/post-trigger, capture at native clock.
Verify I/O timing; audit CDC paths.
(Optional) Record → DDR → replay.
Keep an outputs-inhibit bit during experiments.




