SiliconBench Academy

Master ESP32 firmware development and Verilog testbench self-checking patterns. Hands-on coaching from circuit simulation to deployment-read...
New Washington, PH
Created byProfile picturesosterdeira
39 joined
Profile picture
@sosterdeiraProfile pictureJun 11

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
end


This 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");
end


Three principles:


  1. Every output gets compared to an expected value — no eyeballing

  2. Non-zero exit code on failure — CI/CD catches regressions automatically

  3. 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.