The #1 Verilog Testbench Mistake That Ships Bugs to Silicon
Most Verilog testbenches I review have the same critical flaw: they check outputs with $display and eyeball verification.
// ❌ The pattern that ships bugs
initial begin
send_spi_byte(8'hA5);
@(posedge spi_done);
$display("SPI output: %h", spi_data_in);
// "looks right" — move on
endThis only works when someone is watching. It doesn't catch the timing-dependent bug that appears with seed 48271 on the third SPI Mode 2 transaction after a DMA abort.
The fix — a self-checking pattern:
// ✅ Catches bugs at 3am unattended
`define CHECK(name, cond) \
if (!(cond)) begin \
$error("[FAIL] %s at %0t", name, $time); \
error_count++; \
end else pass_count++;
initial begin
send_spi_byte(8'hA5);
@(posedge spi_done);
`CHECK("SPI loopback", spi_data_in === 8'hA5);
end
final begin
$display("%0d passed, %0d failed", pass_count, error_count);
if (error_count > 0) $fatal("VERIFICATION FAILED");
endThree principles:
Every output gets compared to an expected value — no eyeballing
Non-zero exit code on failure — CI/CD catches regressions automatically
Pass/fail counts — "347 checks passed, 0 failed" means something
Once you have self-checking, you can layer on constrained-random stimulus, coverage-driven verification, and regression automation. Without it, none of that matters.
If you're verifying ESP32 peripherals or any FPGA design, audit your testbenches now. Search for $display statements that print values nobody checks. Replace every one with an assertion.
