Satchel Hamilton
← Writing

What hardware verification can teach LLM evaluation

For decades, hardware verification has had to answer a question the AI field is only now taking seriously: how do you know you've tested enough?

A chip tapes out once. If a bug ships, you don't patch it — you respin silicon, months and millions later. That economics forced the discipline to get rigorous about a deceptively hard question, and the answer was never "write more tests by hand." Directed tests don't scale to a state space that large. So verification moved to constrained-random stimulus, assertion-based checking, and an explicit, measurable notion of coverage closure — a number that answers "are we done?" instead of a feeling.

Most LLM evaluation today is still in the directed-test era: prompt a handful of hand-picked examples, read the outputs, ship if they look good. That's shipping on vibes. The interesting claim is that the ideas hardware used to climb out of that era port almost directly — and that the places they don't port cleanly tell you exactly what's hard about evaluating models.

I built Gatecheck to put this into practice, so the mapping below is concrete rather than hypothetical.

The four ideas that port

Constrained-random stimulus → generated, distribution-aware inputs. Instead of a fixed list of prompts, you sample inputs from a distribution that covers the behaviors you care about. The point isn't randomness for its own sake; it's escaping the bias of the examples you happened to think of.

Assertions → deterministic graders. An assertion is a property the design must satisfy on every run, checked mechanically. In the harness these are the deterministic graders — exact, contains, regex, json_schema. They're cheap, reproducible, and free, which is exactly why they can run on every commit.

The subjective layer → an LLM-as-judge. Some qualities — faithfulness, tone, whether an answer is actually helpful — no assertion can see. For those, a second model scores the output against a rubric. This is powerful and also the weakest link, which matters for the next idea.

Regression gating → fail the build on a drop. Snapshot today's pass rates as a baseline; later, after a prompt edit or a model swap, re-run and fail (non-zero exit) if any suite regresses past a tolerance. This is a verification regression suite, aimed at a prompt instead of an RTL block.

Coverage closure → an honest "are we done?" The hardest and most valuable import: a tracked, reported measure of how much of the behavior space your suites actually exercise — not a gut call.

What a real run looks like

None of this needs a cluster. Here is a complete test from the classification suite Gatecheck ships — the whole thing, no code:

- id: sentiment-positive
  system: "You are a precise classifier. Answer with ONE word: positive, negative, or neutral."
  prompt: |
    Classify the sentiment.
    Text: Absolutely loved it -- best purchase I've made all year.
  graders:
    - type: contains
      any_of: ["positive"]
      ignorecase: true

And here is every bundled suite — 19 tasks spanning classification, JSON extraction, summarization, RAG faithfulness, tool selection, refusals, and pairwise preference — against two local models on my desktop (Ollama, --concurrency 2, about three minutes wall-clock, $0.00, judge included and also local):

| suite | llama3.1:8b | qwen2.5:7b-instruct | | --- | --- | --- | | classification | 1.00 [0.44–1.00] | 1.00 [0.44–1.00] | | structured-output | 1.00 [0.34–1.00] | 1.00 [0.34–1.00] | | summarization | 1.00 · judge 4.0 | 1.00 · judge 4.0 | | faithfulness | 1.00 · judge 4.8 | 0.67 [0.21–0.94] · judge 3.5 | | tool-use | 0.50 [0.15–0.85] · judge 1.5 | 0.50 [0.15–0.85] · judge 1.5 | | red-team | 1.00 · judge 4.3 | 1.00 · judge 4.5 | | preference | 1.00 [0.34–1.00] | 1.00 [0.34–1.00] |

Three honest readings, in increasing order of importance:

  • The intervals are loud on purpose. 1.00 [0.44–1.00] is what "3 for 3" actually proves: not much. A pass rate without an interval is a vibe with a decimal point.
  • The differential is the product. qwen dropped a faithfulness task llama held on identical inputs. A per-model split like that is exactly what a baseline gate catches on a model swap — before your users do.
  • Every failure in this run was issued by a judge. All five came from llm_judge graders, and the transcripts say the judged behavior was largely correct: on decline-when-no-tool-fits, both models rightly declined (there is no booking tool to call), and the local 8B judge failed them anyway — one rationale concedes the response "accurately states that none of the provided tools can be used" in the same breath as the fail verdict. The deterministic graders issued zero false verdicts. Hold that asymmetry for the next section.

The one decision that carries the whole idea

Which signals are safe to gate on?

In hardware you would never block tape-out on a flaky check — a test that sometimes passes and sometimes fails on the same input is worse than no test, because it trains everyone to ignore red. The same rule decides the architecture of an eval harness: deterministic graders gate CI; the judge does not.

In Gatecheck, CI runs --deterministic-only. The LLM judge captures real quality, but it isn't reproducible enough to gate a pull request on, so it runs locally or on a schedule and informs rather than blocks. Getting that line in the right place — what's a gate versus what's a gauge — is most of the design.

A couple of smaller decisions follow the same instinct toward honesty:

  • An explicit verdict rule. A task passes only if every deciding grader passes; it's undecided when nothing produced a definite verdict, and a failure on error. Pass rate is computed over deciding tasks only — no quietly padding the denominator to make a number look better.
  • An honest cost model. Local and unlisted models are counted as $0 rather than estimated, because a made-up cost is worse than an admitted unknown.

Where the analogy breaks — the interesting part

If this were a clean one-to-one mapping it wouldn't be worth writing about. It isn't, and the seams are where the real research is.

There's no clean coverage model for natural language. An RTL block has a finite, enumerable structure: lines, branches, FSM states, cross-products you can literally count. "Coverage" there is rigorous. The input space of a language model is unbounded and has no canonical decomposition, so "coverage closure" for LLM eval is, today, an analogy reaching for a metric it doesn't yet have. Naming that honestly is more useful than pretending a pass-rate is coverage.

The device under test is non-deterministic. Even at temperature 0, providers don't guarantee identical outputs across runs, and seed support is partial. HW verification mostly assumes a deterministic DUT; here the DUT is part of the noise, which is why reproducibility has to be engineered, not assumed.

Your checker is itself a model. An assertion is trusted. An LLM judge is a fallible system being used to evaluate another fallible system, with its own variance and biases — and single-sample judging doesn't even measure that variance. Quantifying judge reliability (ensembling, repeated sampling, confidence intervals on small-N pass rates) is the obvious next step, and it's squarely on the harness's roadmap.

The takeaway

The transferable thing isn't a tool — it's a posture: treat a model-backed system like any other system under test. Generate inputs instead of curating them, separate what you can gate on from what you can only watch, commit to the test before you read the results, and be loud about the gaps in your own measurement. Hardware verification has spent decades learning that posture under unforgiving economics. AI reliability is about to need all of it.