Dieser Guide hilft bei der systematischen Fehlersuche in FPGA-Projekten, von einfachen Syntax-Fehlern bis zu komplexen Timing-Problemen.
# Diagnose
openFPGALoader --detect
lsusb | grep Future
dmesg | tail -20
# Lösungsansätze
sudo usermod -a -G dialout $USER # User zu dialout-Gruppe
sudo udevadm control --reload-rules # udev rules neu laden
# Neustart erforderlich nach Gruppenänderung
# USB-Permissions prüfen
ls -la /dev/ttyUSB* /dev/ttyACM*
# Alternative JTAG-Verbindung
openFPGALoader --cable ft232 --detectSymptome: Sporadische Resets, instabile Operation
Diagnose mit Multimeter:
- 3.3V Rail: 3.135V - 3.465V (±5%)
- 2.5V Rail: 2.375V - 2.625V (±5%)
- 1.1V Rail: 1.045V - 1.155V (±5%)
Lösungen:
- USB-Kabel wechseln (Spannungsabfall)
- USB-Hub mit externer Stromversorgung
- Externes 5V Netzteil verwenden
// Debug-Checkliste:
// 1. Clock-Signal vorhanden?
assign debug_led[0] = clk;
// 2. Reset-Logic korrekt?
reg [15:0] reset_counter;
always @(posedge clk) begin
if (reset_counter < 16'hFFFF)
reset_counter <= reset_counter + 1;
end
assign debug_led[1] = reset_counter[15]; // Shows reset completion
// 3. PLL locked?
assign debug_led[2] = pll_locked;# Fehler-Beispiel:
ERROR: Module `my_module' not found!
# Debugging:
yosys -p "read_verilog src/*.v; hierarchy -check -top top_module"
# Lösungen:
1. Alle .v Files in yosys command einschließen
2. Module-Namen überprüfen (case-sensitive)
3. File-Pfade verifizierenWarning: Wire is used but has no driver
→ Unconnected input, assign default value
Warning: Wire has multiple drivers
→ Bus contention, use tri-state logic
Warning: Found latch for signal
→ Incomplete case/if statement, add default
# Timing-Report analysieren:
nextpnr-ecp5 --85k --package CABGA381 --json design.json \
--lpf constraints.lpf --report timing_report.txt
# Critical Path Analysis:
grep -A 20 "Critical path report" timing_report.txt
# Lösungsansätze:
1. Clock-Frequenz reduzieren
2. Pipeline-Stufen hinzufügen
3. Logic-Optimierung (weniger LUT-Ebenen)
4. Register-BalancingERROR: Failed to route net 'signal_name'
Lösungen:
1. Design vereinfachen (weniger Logik)
2. Pin-Assignments prüfen
3. Floorplanning constraints
4. Alternative FPGA-Pinout
// FALSCH - erzeugt Latch:
always @(*) begin
if (condition)
output = input_a;
// Fehlt: else clause!
end
// RICHTIG:
always @(*) begin
if (condition)
output = input_a;
else
output = input_b; // oder default value
end// GEFÄHRLICH - Metastability:
reg signal_crossed;
always @(posedge clk_domain2) begin
signal_crossed <= signal_from_domain1; // Direkte Verwendung!
end
// SICHER - Synchronizer:
reg [2:0] sync_reg;
always @(posedge clk_domain2) begin
sync_reg <= {sync_reg[1:0], signal_from_domain1};
end
assign signal_crossed = sync_reg[2];// FALSCH in sequential logic:
always @(posedge clk) begin
a = b; // Blocking assignment
c = a; // Uses NEW value of a (combinational)
end
// RICHTIG für sequential logic:
always @(posedge clk) begin
a <= b; // Non-blocking
c <= a; // Uses OLD value of a (register chain)
end
// RICHTIG für combinational logic:
always @(*) begin
temp = a & b; // Blocking OK
output = temp | c; // Uses NEW value of temp
end// Häufige Ursachen:
1. Uninitialized Registers:
// Simulation: reg defaults to 'x'
// Hardware: reg can power up to any value
reg [7:0] counter = 8'h00; // Explicit initialization
2. Race Conditions:
// Simulation ist deterministisch
// Hardware kann unterschiedliche Delays haben
3. Clock Domain Issues:
// Setup/Hold Violations in Hardware
// Simulation sieht ideale Timing// Bessere Debug-Signale:
module debug_wrapper(
input clk,
input reset,
input [7:0] data_in,
output [7:0] data_out
);
// Original design
my_design dut(.*);
// Debug-Ausgaben für Simulation
always @(posedge clk) begin
if (debug_enable) begin
$display("Time=%0t: data_in=%h, data_out=%h, internal_state=%h",
$time, data_in, data_out, dut.internal_signal);
end
end
// VCD Dump mit mehr Details
initial begin
$dumpfile("detailed_debug.vcd");
$dumpvars(0, debug_wrapper); // Alle Signale
$dumpvars(1, dut.internal_module); // Interne Module
end
endmodule// Große Designs in Teile zerlegen:
// Stufe 1: Minimaler Test
module minimal_test(
input clk,
output [7:0] led
);
reg [23:0] counter;
always @(posedge clk) counter <= counter + 1;
assign led = counter[23:16];
endmodule
// Stufe 2: Ein Modul hinzufügen
module add_one_module(
input clk,
output [7:0] led
);
minimal_test base(.clk(clk), .led(led[3:0]));
// Neues Modul testen
new_module test(.clk(clk), .out(led[7:4]));
endmodule// Interne Signale nach außen routen:
module debug_signals(
input clk,
input [7:0] data_in,
output [7:0] data_out,
// Debug-Ausgänge
output [7:0] debug_state,
output debug_valid,
output debug_error
);
// Hauptlogik
main_logic ml(
.clk(clk),
.data_in(data_in),
.data_out(data_out),
// Interne Signale exportieren
.internal_state(debug_state),
.valid_flag(debug_valid),
.error_flag(debug_error)
);
endmodulemodule bist_wrapper(
input clk,
input test_mode,
input [7:0] normal_input,
output [7:0] normal_output,
output test_pass
);
// Test-Pattern Generator
reg [7:0] test_patterns [0:15];
reg [3:0] test_index;
reg [7:0] expected_results [0:15];
initial begin
test_patterns[0] = 8'h00; expected_results[0] = 8'h00;
test_patterns[1] = 8'hFF; expected_results[1] = 8'hFF;
test_patterns[2] = 8'h55; expected_results[2] = 8'hAA;
// ... weitere Test-Vektoren
end
// DUT with muxed inputs
wire [7:0] dut_input = test_mode ? test_patterns[test_index] : normal_input;
wire [7:0] dut_output;
design_under_test dut(
.clk(clk),
.input(dut_input),
.output(dut_output)
);
// Test-Logik
reg test_running;
reg all_tests_pass;
always @(posedge clk) begin
if (test_mode && !test_running) begin
test_running <= 1;
test_index <= 0;
all_tests_pass <= 1;
end else if (test_running) begin
if (dut_output != expected_results[test_index]) begin
all_tests_pass <= 0;
end
if (test_index == 15) begin
test_running <= 0;
end else begin
test_index <= test_index + 1;
end
end
end
assign normal_output = test_mode ? 8'h00 : dut_output;
assign test_pass = all_tests_pass;
endmodule// Lange kombinatorische Pfade vermeiden:
// SCHLECHT - langer kombinatorischer Pfad:
always @(*) begin
result = ((((a + b) * c) - d) >> 2) & mask;
end
// BESSER - Pipeline mit Registern:
always @(posedge clk) begin
stage1 <= a + b;
stage2 <= stage1 * c;
stage3 <= stage2 - d;
stage4 <= stage3 >> 2;
result <= stage4 & mask;
end# Resource-Report analysieren:
yosys -p "synth_ecp5 -top design; stat" design.v
# Ausgabe verstehen:
Number of cells:
TRELLIS_FF 150 # Flip-Flops (Register)
TRELLIS_LUT4 75 # 4-Input LUTs
TRELLIS_CARRY 8 # Carry-Chain Elemente
# Optimierung:
1. Unused logic eliminieren
2. Resource-sharing (gemeinsame Ressourcen)
3. Pipeline-Balancing// Timing-Constraints definieren:
// In .lpf file:
FREQUENCY PORT "clk" 25 MHZ;
MAXDELAY FROM CELL "input_reg*" TO CELL "output_reg*" 35 ns;
// Multi-Cycle Paths:
MULTICYCLE FROM CELL "slow_logic*" TO CELL "result_reg*" 2;
// False Paths (asynchrone Signale):
FALSEPATH FROM CELL "async_input*" TO CELL "*";// Clock-Distribution verbessern:
// SCHLECHT - manueller Clock-Tree:
wire clk_div2 = clk_counter[0];
wire clk_div4 = clk_counter[1];
// BESSER - dedizierte Clock-Resources:
EHXPLLL pll_inst(
.CLKI(clk_input),
.CLKOP(clk_main), // Primary clock
.CLKOS(clk_div2), // Secondary clock
.CLKOS2(clk_div4), // Tertiary clock
.LOCK(pll_locked)
);// Korrekte BRAM-Inferenz:
// Funktioniert - synchroner Read:
always @(posedge clk) begin
if (we)
ram[addr] <= data_in;
data_out <= ram[addr]; // Registered output
end
// Funktioniert NICHT - asynchroner Read:
assign data_out = ram[addr]; // Inferiert Distributed RAM
// Dual-Port korrekt:
always @(posedge clk_a) begin
if (we_a) ram[addr_a] <= data_in_a;
data_out_a <= ram[addr_a];
end
always @(posedge clk_b) begin
data_out_b <= ram[addr_b]; // Read-only port
endmodule comprehensive_testbench;
// Clock and Reset
reg clk = 0;
reg reset = 1;
always #5 clk = ~clk; // 100 MHz
initial begin
#100 reset = 0;
end
// DUT instantiation
wire [7:0] dut_output;
reg [7:0] dut_input;
design_under_test dut(.*);
// Test vectors
reg [15:0] test_vectors [0:1023];
integer test_count;
initial begin
$readmemh("test_vectors.hex", test_vectors);
test_count = 0;
end
// Directed tests
initial begin
wait(!reset);
// Test 1: Basic functionality
test_basic_operation();
// Test 2: Edge cases
test_edge_cases();
// Test 3: Random inputs
test_random_inputs();
// Test 4: Stress test
test_stress_conditions();
$display("All tests completed!");
$finish;
end
// Test tasks
task test_basic_operation;
begin
dut_input = 8'h00;
#20;
assert(dut_output == 8'h00) else $error("Basic test failed");
dut_input = 8'hFF;
#20;
assert(dut_output == 8'hFF) else $error("Basic test failed");
end
endtask
task test_edge_cases;
begin
// Test overflow, underflow, etc.
dut_input = 8'hFE;
#20;
// Check expected behavior
end
endtask
// Coverage monitoring
reg [7:0] input_coverage [0:255];
always @(posedge clk) begin
if (!reset) begin
input_coverage[dut_input] <= 1;
end
end
// Check coverage at end
final begin
integer i, covered = 0;
for (i = 0; i < 256; i = i + 1) begin
if (input_coverage[i]) covered = covered + 1;
end
$display("Input coverage: %d/256 (%d%%)", covered, covered*100/256);
end
endmodule"Cannot assign to wire in procedural block"
→ Verwenden Sie 'reg' statt 'wire' für procedural assignments
"Multiple drivers for wire"
→ Bus contention - verwenden Sie tri-state logic
"Signal is used but never assigned"
→ Unconnected input - assign default value
"Timing constraint not met"
→ Reduce clock frequency oder add pipeline stages
"Unsupported or unknown PLI function"
→ Simulation-only code in synthesis - use `ifdef SIMULATION
- Clock-Signal vorhanden und stabil?
- Reset-Logic korrekt (active high/low)?
- Pin-Assignments in constraints file?
- Alle Module-Instanzen verbunden?
- Clock-Domain-Crossings synchronisiert?
- Timing-Constraints definiert?
- Resource-Utilization im Rahmen?
- Testbench validiert das Design?
Dieser Guide wird kontinuierlich erweitert basierend auf häufigen Problemen und Community-Feedback.