CVDP RTL Agent: what passes the hidden testbench
An open LangGraph agent writes and fixes SystemVerilog for the 92 free problems of NVIDIA's CVDP benchmark, checks its own work in simulation, and is scored only by hidden cocotb testbenches: it solves 52 to 56 of the 92; none of six changes to how it checks its work, a frozen memory of lessons, two model-free checks run as fixed steps, or making it test before it submits moved that number by a detectable amount, while a richer harness (Deep Agents) added about +4.7 points on the 86 reachable problems, confirmed by a pre-registered run on this one model.
This is a real failure, replayed from the committed run data. The agent's design compiled, linted and synthesised, so it submitted after one turn, at a cost of $0.0024 over 52.6 s. The hidden testbench then reached for a clock called pclk. The design calls it clk. The test failed before it had checked any behaviour.
What CVDP is
CVDP (Comprehensive Verilog Design Problems) is a hardware-design benchmark published by NVIDIA researchers in 2025: 783 problems in 13 categories when it was released. Each problem asks for RTL to be written, changed or fixed. The result is scored by a hidden testbench, usually a cocotb test, that the solver never sees.
What this project is
An open LangGraph agent, run end to end in GitHub Actions on the free, open-tool split of CVDP 1.1.0: 92 problems in four categories (spec to RTL 34, RTL modification 25, spec to RTL with module reuse 22, bug fixing 11). Every cost is provider-reported (via OpenRouter), every score comes from the hidden tests, and no LLM ever scores anything.
How it works
A six-node graph plans, edits, runs deterministic checks (compile, lint, synthesis, its own testbench) and decides whether to edit again or submit, under hard caps on turns, dollars and wall time, inside a Docker image that a GitHub Actions sweep runs on all 92 problems.
The graph is read_task → plan → edit → check → reflect → (edit | finalize). The LLM writes the plan, the edits and the decision. Everything else is code: the file tool writes only under rtl/, verif/ and docs/, and the checks are real tools (Icarus iverilog -g2012, verilator --lint-only -Wall, a Yosys read and synth, and a testbench the agent writes itself). The replay below is a long problem from the same run: nine turns, ending in a submit after the agent's own testbench had failed.
Turns 1 to 8 are drawn from the problem's totals (each one ended in edit_again, because another turn followed); their individual events are not in the committed excerpt. Turn 9 and the hidden-test line are quoted from it.
The sweep, as a schematic
Stop rules, checked in code before every step
- Turns: 10 edit, check and reflect cycles by default.
- Dollars: $0.10 per problem, provider-reported cost only. A call whose cost is not reported stops the problem (
cost_unreported), because the budget can no longer be enforced. - Wall time: 480 s, under the harness's 600 s agent timeout. Each LLM call has a hard deadline 2 s before the cap.
- On any stop or crash, the last edit that compiled is kept, the summary is written, and the agent exits 0.
WHYWhy sweeps run in GitHub Actions, not locally
The CVDP harness needs a Docker daemon, and Actions has one on every runner, free for a public repository, with jobs of up to 6 hours. GitHub's runners come in x86-64 and Arm64, so every sweep can run natively on both with no emulation. Every run leaves a public run ID and a downloadable artifact (per-test logs, traces, summaries), so anyone can check a number against its run. And the heavy Docker work stays off the development machine.
WHYWhy the LLM never scores
Pass or fail comes only from the hidden tests the harness runs after the agent exits. A judge model can be argued with by the thing it judges, and its mistakes would be correlated with the agent's own, since they are the same kind of model. The failure labels are also made by deterministic rules (see the taxonomy), so no number on this page depends on a model's opinion.
WHYWhy cost is taken only from provider-reported usage
Price lists drift, and the provider that serves a call can charge a different price from the list: DeepSeek's calls went to 15 to 19 different providers per sweep. So the agent records the cost OpenRouter reports for each call, asks the generation endpoint if that is missing, and otherwise records null and stops the problem. Nothing is estimated. Where a cost is missing, every table shows a lower bound marked with ≥.
Milestones, M0 to M3d
Each milestone asked one question and answered it with runs anyone can download: does the harness run (M0), does the agent work (M1), how do models and reasoning settings compare (M2a, M2), do the agent's own checks pay for themselves (M3), does a richer harness or a memory of lessons help (M3b, then a confirming run), do two model-free checks run as fixed steps help (M3c), why do the problems no model solves fail (the unsolved analysis), and does making the agent test before it submits help (M3d).
max_tokens truncation. DeepSeek at off: 6/10 for $0.0867; at low: 7/10 for $0.3546 with 14 empty replies. Retries and seeds added.M2: three models, 92 problems, two runners
Haiku 5.5 and gpt-6-luna solve the same share of the 92 problems, Luna at half the cost per solved problem, DeepSeek V4.1 Flash solves about 10 fewer, and two identical runs of one model differ on about 10 problems, so a gap of a few problems means nothing.
Each model ran all 92 problems once on an x86-64 and once on an Arm64 runner, with the same image and settings, against the null agent's floor of zero.
The capture: every problem, every full run
Below, each row is one problem and each column one full sweep of all 92, like the channels of a logic analyser. A filled cell is a pass; a bare baseline is a fail. The six M2 sweeps come first, then the 13 new M3 sweeps (M3's run A1 is M2's Luna Arm64 sweep, so it is not repeated). Rows are sorted by how often the problem passed.
- Run-to-run noise is large. Between its x86-64 and Arm64 runs, Haiku changed result on 10 problems, Luna on 9 and DeepSeek on 12. The direction is not consistent, so this reads as the model's sampling, not the runner.
- Haiku against Luna is a tie. Counting each problem's passes over both runners, Haiku is ahead on 12 problems and Luna on 12. Both beat DeepSeek (24 against 6, and 21 against 6).
- 29 problems passed in all six M2 sweeps and 28 in none. 24 failed in every one of the 20 full runs of M2 and M3.
- 3 seeds on a 30-problem subset vary by up to 4 problems for one model (seeds table).
WHYWhy Wilson intervals, and why repeat runs
With 92 trials the normal approximation to a binomial interval is poor near 0% and 100% and can go outside [0, 1]; the Wilson score interval keeps its stated coverage at small n. But a Wilson interval only covers sampling over problems. It says nothing about the same problem coming out differently on a second run, which is why M2 ran every model twice and M3 measured the noise floor directly.
M3: six changes to the agent, none detectable
Turning the self-check or lint off, capping turns at 5 or 20, or cutting the budget to $0.02 changed the pass rate by less than two identical runs differ, though turning lint off made each solved problem 38 to 59% dearer.
Each arm is an ablation: one setting changed, everything else held. Every arm uses gpt-6-luna (the cheapest per solved problem in M2), all 92 problems, Arm64 and 4 threads, and runs twice. Each arm is compared with the base (A) problem by problem: a problem's rate in an arm is its passes over the arm's two runs, so 0, ½ or 1. The effect is the mean of the differences, with a paired bootstrap interval and an exact sign test.
Intervals are from tools/ablate.py as committed in docs/reports/m3/arms_paired.md; the point effects and better / worse counts are recomputed from the CSVs by site/data/derive.py and agree.
F (20 turns) is the only arm worth a second look, ahead in both runs (62.0% and 58.7% against 57.6% and 55.4%). But only 6 of its 184 problem-runs used more than 10 turns, and 1 of those passed. The extra turns account for at most 1 of its 7 extra passes; the rest is noise.
What the agent did with its own testbench
WHYWhy a paired bootstrap plus a sign test, not an unpaired comparison
Problems differ far more from each other than arms do: in M2, 29 problems passed every time and 28 never. An unpaired comparison of two pass rates puts all that between-problem spread into the error bar, and a 2-point effect disappears inside it. Pairing compares each problem with itself, so only within-problem change counts. The bootstrap resamples problems and keeps each problem's runs together, so it does not pretend the 184 problem-runs are independent. The exact sign test needs no distributional assumption at all: on the problems that changed, it asks whether "better" and "worse" are unbalanced.
WHYWhy the noise floor is measured on the same runner (A2 against A1)
An arm's effect means something only if it is bigger than the noise floor: the difference between two runs with nothing changed. Running the base twice on the same runner removes the runner as a cause. The result, −2.2 points [−8.7, +4.3] (3 better, 5 worse), is as wide as M2's Arm64 against x86 (−1.1 [−7.6, +5.4]), so the runner adds no noise of its own and 2 runs per arm resolve about ±5 points.
M3b: a richer harness, or a memory of lessons?
At a fixed model, a frozen memory of 40 lessons did not detectably change the pass rate; the Deep Agents harness added a few points, first as a hypothesis on 3 runs an arm, then confirmed by a pre-registered test on 6 new runs an arm (B − A +4.7 [+1.0, +8.5]), for this one model on this one benchmark.
M3b holds the model (gpt-6-luna), the Arm64 runner and every M2 default fixed, and changes two things in a 2×2 design: the harness that drives the model (M2's hand-built LangGraph graph, or Deep Agents), and whether the agent is given a frozen lessons memory. Each arm ran 3 times on the 86 problems of the evaluation set: the 92 minus 6 that no agent can reach, by a rule fixed before any scored run. Arm A is a fresh run of M2's agent on the same image, so all four arms share an image and a period.
The paired effects, null findings first
Intervals are tools/ablate.py's paired problem bootstrap as committed in docs/reports/m3b/; the point effects, the better / worse / same counts and the exact sign tests are recomputed from the run CSVs by site/data/derive.py and agree.
- The lessons memory did not help. C − A is +1.6 [−3.1, +6.6]: 10 / 8 / 68 problems better / worse / the same, sign test p = 0.82. C's three runs solved 51, 51, 51 of 86. On Deep Agents, D − B is +3.1 [−0.4, +7.0], p = 0.15: not detectable either.
- The harness may help a little. B − A is +4.3 [+0.0, +8.5], p = 0.21: the interval's lower end rounds to zero and the sign test is far from significant. On top of the memory the harness is clearer: D − C is +5.8 [+1.9, +10.1], 9 / 1 / 76, p = 0.022, the one comparison whose interval excludes zero.
- Why that was a hypothesis, not yet a finding. With four pre-registered comparisons, a Bonferroni threshold of 0.0125 on the sign tests passes none of them, and the bootstrap intervals are not adjusted. D − A (+7.4 [+2.3, +12.8]) was not pre-registered, so it is exploratory only. For scale: on these 86 problems the noise floor is −2.3 [−9.3, +3.5], and M3's largest effect (20 turns) is +4.1 [−0.6, +9.9]. So M3b named one hypothesis, the harness gain B − A, and a separate run was fixed to test it.
The confirming run: B − A on fresh data
On 6 new runs an arm, B − A is +4.7 [+1.0, +8.5] points. The interval excludes zero and the effect is positive, so by the rule fixed in advance the harness effect is confirmed, for gpt-6-luna on these 86 problems.
The test was written down and committed before any of its runs was dispatched (pre-registration). It is a single pre-registered comparison, B − A, so there is no multiple-comparison correction. It uses 6 new runs per arm (seeds 4 to 9) with M3b's settings unchanged, and M3b's own runs are not pooled in, so the data that suggested the hypothesis does not also test it. The decision rule: if the paired 95% interval excludes zero and the effect is positive, the effect is confirmed; if it excludes zero and is negative, reversed; otherwise not confirmed, with the size it rules out.
- How strong. 16 / 7 / 63 problems better / worse / the same; the exact sign test gives p = 0.093. The sign test was not part of the rule. It is shown so that the evidence reads as moderate, not overwhelming.
- The size repeats. Pooled pass@1 is A 57.9% (299/516) and B 62.6% (323/516), close to M3b's +4.3.
- So does the mechanism. Deep Agents leaves fewer failures untested (
tests_failed_no_selfcheck: A 121, B 79) and stops on its round cap far more often (A 28, B 101 problem-runs), at about the same cost per solved problem (≥ $0.0126 against ≥ $0.0119; one call in each arm had no reported cost). - What it covers. One model (gpt-6-luna), one problem set (the 86 free agentic problems) and one Deep Agents version. The memory arms C and D, and D − C, were not re-run: the memory finding stands as M3b reported it.
Why Deep Agents differs: fewer untested failures, more round-cap stops
failure_label column of the committed CSVs and match docs/reports/m3b/taxonomy_by_arm.md.Deep Agents runs a free tool-call loop: the model calls compile, lint, synthesis and its own testbench as tools, as often as it likes, up to a cap of 22 model calls (the most any M2 problem made in 10 turns). It leaves far fewer failures untested (tests_failed_no_selfcheck: A 58, B 39, D 33), but stops more often on its round cap (failures labelled stopped_by_cap: A 11, B 29, D 33). A cap stop still submits the best edit kept so far, and some pass: B hit the cap 49 times and 20 of those passed. With the lessons, the LangGraph agent has more failures whose own testbench passed (C 21 against A 15); with both, the fewest (D 8).
What the memory learned
The builder read 87 attempts by arm A on 29 heavy-split problems (none in the evaluation set; 19 of the attempts passed), asked the model for general lessons in batches, and froze 40 of them into snapshot b783931cbf160595 for $0.0031. At run time the agent gets the top 5 by BM25 over its task text, with no model in the retrieval. The lessons are generic process advice, several near-duplicates, and 6 are never retrieved for any of the 86 problems (L07, L08, L17, L28, L30, L38: about compile diagnostics and turn budgets, words the problem statements do not use). The four retrieved most often, quoted as built:
The interface lessons did not move the interface failures: compile_fail_hidden stays at 12–13 and signal_missing_hidden at 2–3 on every arm. A null C − A says that this memory does not help, not that no memory could.
Cost
All four arms cost about the same: $0.0067 to $0.0074 per problem-run, and $0.0124 (A), $0.0119 (B), $0.0113 (C) and $0.0106 (D) per solved problem, with no unreported call in any of the 1,032 problem-runs. The arms differ in pass rate, not in price. The scored sweeps cost $7.27 in all, the learning runs at least $3.41.
WHYWhy the evaluation set leaves out unreachable problems
If the hidden testbench needs a name that appears nowhere in what the agent is given, no change to the agent can make that problem pass. Keeping it adds only noise and shrinks every effect towards zero. So the rule was committed before any scored run: a problem is unreachable when a name its hidden testbench reads is given nowhere to the agent and M3 labelled it an interface failure in at least half of its 14 runs. Both halves are needed: two problems read names the agent is not given but failed on them in only 1 of 14 runs, so they stay. Six problems are excluded, five of them for internal register or instance names and one whose context is empty.
WHYWhy the memory is frozen, and learned only on other problems
A memory that has seen the evaluation problems' outcomes or traces can carry their answers into the test. That is leakage, and it would make any gain meaningless. So the memory learns only from 29 heavy-split problems whose files to change all lie inside the agent's edit roots; the builder refuses, in code and under test, any work directory that holds one of the 92 agentic problems; the snapshot is built once in Actions, committed as built with no hand edits, mounted read-only and checked against its hash on load; and every result row names that hash. The memory does not grow during evaluation, so C − A measures one fixed memory.
M3c: two model-free checks, as fixed steps
A mutation-score gate on the agent's own testbench and an interface diff by elaboration, each run in code at a fixed point of the graph, left the pass rate where it was (M − A +0.8, I − A −1.6 points, both intervals across zero); the gate changed the kind of failure, not the number.
M3 and M3b found the failures in two places: interface mismatches with the hidden testbench, and weak self-checks (no testbench of its own, or one that passes on code the hidden tests fail). M3c adds one deterministic check for each, with no model in it, to the LangGraph agent, and runs each against a fresh base A, 3 runs an arm on the same 86 problems. M − A and I − A are the two pre-registered comparisons.
- M, a mutation-score gate before submit. Yosys
mutatemakes 12 mutated copies of the agent's design, and the agent's last testbench must fail on at least half of those that compile. With no testbench, the gate refuses the submit. After 2 gate failures the agent may submit anyway. - I, an interface diff by elaboration after every edit. pyslang elaborates the design, and its modules, ports and parameters are compared with the interface the agent was given. It never reads the hidden testbench. A mismatch turns one submit into one more edit, then lets it through.
The gate changes the kind of failure, not the number
failure_label column of the committed CSVs and match docs/reports/m3c/taxonomy_by_arm.md.The gate did what it was built to do: it will not let the agent submit without a testbench, so failures with no testbench of the agent's own fell from 56 on A to 0 on M. But they did not become passes. Failures where the agent's own testbench passed and the hidden tests still failed rose from 14 to 50, and stops on a cap from 4 to 19, as the gate's extra rounds used up turns. Requiring a testbench is not requiring a correct one. The interface check moved neither of its own labels: the two interface labels together are 16 failures on A and 16 on I.
What the checks found
The gate scores the agent's testbench, not the design. A testbench that checks the wrong behaviour thoroughly kills mutants and passes the gate, and the hidden tests then fail the design. The interface check reads the given interface from prompts and documents by rules, not by a parser of the specification, so a port listed in an unusual format is missed and a document can produce a false finding. Neither check costs tokens itself; M's extra cost is the extra rounds of reflect and edit it causes.
WHYWhy the new checks are fixed steps, not tools
A check the model may skip is a check it often skips. In M3, with self-check available as a tool, the agent ran its own testbench on only 31–32 of 92 problems, and the largest failure label in M3b is still "hidden tests failed, no testbench of its own" (58 of arm A's failures). So M3c's checks run in code at fixed points of the graph; the model sees their results but does not decide whether they run.
WHYWhy a mutation score, and why a threshold of one half
A testbench that passes says nothing about whether it could fail. Mutation testing asks that directly: change the design a little, and see whether the testbench notices. Some mutants change nothing the outputs can show (equivalent mutants), so no testbench can kill all of them; that is why the bar is half of the compiled mutants, not all. The gate's settings (12 mutants, seed 1, threshold 0.5, 60 s, 2 failures) were fixed before any scored run.
Beside it: Claude Code as the whole agent
Exploratory, one run per model: Claude Code with Opus 5.5 solved +11.6 points more of the 86 problems than our LangGraph agent with gpt-6-luna, the only clear gap, and since both the harness and the model change, it says how good that pairing is here, not which change is responsible.
Here the whole agent is swapped. Claude Code runs headless (claude -p) inside the harness's container, with file and shell tools and a 60-turn budget, on four Claude models, one run each, on the same 86 problems and the same 480 s wall cap. Each is paired problem by problem with M3c's arm A (LangGraph with gpt-6-luna, 3 runs, 61.6%). It was not pre-registered.
- Not in order of model size. Opus 73.3%, Sonnet 69.8%, Fable 67.4%, Haiku 66.3%.
- Fable is held back by the wall cap. It stopped on the 480 s cap on 17 of 86 problems, and 10 of those failed; its median wall time is 265 s against 24 to 83 s for the others. Under this cap its score is a lower bound, not a measure.
- Two things change at once. Each arm differs from the base in both the harness and the model. The LangGraph agent with a Claude model was not run.
- Cost is not comparable. These runs used a subscription plan, so no call has a provider-reported price. Claude Code's own estimates at API rates are in the report, labelled as estimates; they are not money spent, and are not drawn here.
- At least one Claude model solved 74 of the 86 problems, and all four solved 45.
The problems no Claude model solved
Every attempt on these problems, by the 4 Claude models and the 3 runs of arm A, was rerun locally against the hidden tests in the harness's own simulation image, and all but one gave the same result as CI. A counterfactual rerun then changed one detail of a failing attempt: the detail the test needs and the task does not give. These reruns are diagnosis, not scores, and no model wrote the change. The finding: 7 of the 12 are benchmark-limited, with strong evidence. Two examples:
- A file name given nowhere. In
sync_serial_communication_0001the hidden build compilessync_serial_communication_top.sv, a name that appears nowhere in the prompt, the specification or the given testbench. Every Claude attempt passes once its RTL is put in a file of that name. - Default parameters the prompt never gives. In
traffic_light_controller_0001the hidden test counts cycles against default timer values of 10 and 20, while the prompt names the parameters and gives no values. Every attempt rerun with those defaults set, and nothing else changed, passes.
2 more look benchmark-limited on weaker evidence (one hidden test is not deterministic), 3 are agent-limited, and none is an environment failure. M3b's reachable.py kept the file-name problem in the evaluation set because it checked the names the hidden test reads, not the file paths it compiles.
rc5_0001, so every rate rises and none falls. The full-86 score stays the reported one.The same report compared the 7 problems that at least 2 Claude models solved and arm A never did: in 14 of arm A's 20 valid attempts on them, it submitted without running any testbench, while the passing Claude attempts wrote or ran one and fixed what it showed. That is the question M3d tests next.
M3d: verify before submit
Forcing the agent to pass a testbench before it submits, with or without extra cases written blind from the specification, left the pass rate where it was (V − A −1.9, S − A −4.7 points, both intervals across zero): the agent now tests almost every time, and its failures move to "own testbench passed, hidden tests failed".
Our agent submits after one turn on about half its problem-runs (47% on arm A) and runs no testbench of its own on 55%, because its reflect step accepts a clean compile, lint and synthesis. Claude Code iterates: it writes or runs a testbench and fixes what it shows. Does making our agent verify before it submits close that gap? M3d adds two fixed graph steps the model cannot skip, and runs each against a fresh base A, 3 runs an arm on the same 86 problems and settings as M3c.
- V, verify before submit. A submit is refused until the design, as it stands after the last edit, has passed a testbench the agent ran: its own, or one the task gives. After 2 refusals, or when no edit can follow, the submit goes through.
- S, V plus specification cases. Before planning, one LLM call writes cycle-level test cases from the task and its documents only. It never sees the RTL, the given testbenches or anything the agent wrote; a unit test checks that on the recorded request. Verify applies the cases too.
The failure mix moves, again
failure_label column of the committed CSVs and match docs/reports/m3d/taxonomy_by_arm.md. "Other" holds llm_error: calls abandoned at the wall-cap deadline (A 2, V 14, S 9).The step did what it was built to do. Failures with no testbench of the agent's own fell from 55 on A to 0 on V and 2 on S. They did not become passes. Failures where the agent's own testbench passed and the hidden tests still failed rose from 16 to 48 and 44, and cap stops from 7 to 27 and 43, as the extra turns ran into the 480 s wall cap. This is M3c's lesson a second time: a testbench the model writes agrees with the model's own misreading. The blind specification cases, written by the same model, carry the same misreadings.
- The verify step refused a submit on 155 of V's 258 problem-runs, mostly because no testbench had run on the current files, and the agent then edited. The refusals fell mostly on problems the agent was going to get right anyway.
- S's guard caveat. 8 of S's problem-runs were stopped by the whole-run cost guard, which charges the per-problem budget for every call whose cost was not yet reported, and S makes more calls. They count as failures, as pre-registered. Counted at A's pass rate instead, they would move S − A by about +1.9 points: still negative, and the verdict does not change.
- Without the 7 benchmark-limited problems (79 problems, beside the full-86 result, not instead of it): A 66.2%, V 64.1%, S 61.2%; V − A −2.1 [−7.2, +2.5], S − A −5.1 [−12.7, +1.7].
- The problems Claude Code won stay lost. On the 7 problems that 2 or more Claude models solved and M3c's arm A never did, V and S pass 1 and 1 problem-runs of 21, as A does (1): making the agent run a testbench is not what Claude Code did there. It wrote a testbench that checks the right behaviour.
What it means
On this model, changes to the agent's process are now null three times: a memory of lessons (M3b), a mutation gate and an interface check (M3c), and verify before submit (M3d). What has moved the score is the harness (Deep Agents, M3b, confirmed by a pre-registered run) and the model (the Claude Code ladder, where harness and model change together). That is one model, gpt-6-luna, on one benchmark, at a resolution of a few points. It does not say that process never matters; it says that, here, checks the model writes for itself do not catch the model's own misreadings.
WHYWhy the spec call is kept blind to the design
Cases written by a call that has seen the design tend to describe the design, not the specification, so they pass whatever was built. S's call gets the task, the files under docs/ and a port table extracted in code from the given RTL, and nothing else. A unit test checks the recorded request for markers placed inside the given module body and a given testbench, and finds neither. Blind to the design is not blind to the model: the same model reads the same specification the same way.
Why it fails
Of 565 failures, about half are hidden-test failures where the agent never ran a testbench, a quarter are interface mismatches with names the hidden testbench uses, and when the agent did test its own work, its testbench passed on code the hidden tests failed 61 times out of 73.
Every failed problem-run in M3's 14 runs gets one label: the first of a fixed list of rules that holds, read from the harness's per-test log, the agent's trace and its summary. The rules are in tools/taxonomy.py; no model is involved.
Counts are recomputed from the failure_label column of the committed CSVs and match the M3 report.
The interface story: a handle the agent was never given
Two labels are interface failures: the hidden build failed on code that compiled for the agent (74 failures, on 8 problems), or the hidden testbench reached for a port, signal, instance or parameter by a name the design does not have (73 failures, on 9 problems). The agent cannot see either from its own checks, because the names are only in the hidden testbench.
trace: compile exit=0 iverilog -g2012 -o /code/rundir/agent_tb/compile.vvp -I/code/rtl /code/rtl/csr_a trace: lint exit=0 verilator --lint-only -Wall -Wno-fatal -Wno-MULTITOP --timing -I/code/rtl /code/ trace: synth exit=0 yosys -q -p read_verilog -sv /code/rtl/csr_apb_interface.sv; hierarchy -auto-top trace: llm reflect decision="submit" log: AttributeError: csr_apb_interface contains no child object named pclk. Did you mean: 'clk'? log: ======================== 2 failed, 3 warnings in 1.13s =========================
For 6 of the 9 missing-name problems, the main name the hidden testbench reads is not in the prompt or in any file the agent is given. No change to the agent can be expected to match it. Those failures belong to the benchmark items, and M3b leaves them out of its evaluation set.
| Problem | Name the hidden testbench reads | Given to the agent? | Errors |
|---|---|---|---|
phase_rotation_0038 | control_fsm.state_ff | NO | 840 |
phase_rotation_0031 | detect_sequence.curr_state | NO | 550 |
phase_rotation_0010 | phase_rotation_viterbi.data_i4_ff | NO | 234 |
poly_decimator_0001 | poly_decimator.poly_branches | NO | 52 |
direct_map_cache_0001 | direct_map_cache.INDEX_WIDTH | YES | 48 |
ttc_lite_0001 | ttc_counter_lite.enable | YES* | 24 |
digital_stopwatch_0001 | dig_stopwatch_top.reset, .second_pulse; .hour | NO; YES for hour | 12, 4; 4 |
csr_using_apb_interface_0001 | csr_apb_interface.pclk | NO | 2 |
dynamic_equalizer_0008 | equalizer_top.uu_awgn_real, .uu_awgn_imag | NO | 1 each |
*A common word that may not name the signal. "Errors" counts every AttributeError line over the 14 runs, so a parametrised test counts many times. The search is a word search of each problem's prompt and context files; it is not yet a committed tool.
Self-check passed, hidden tests failed: 61 of 73
When a submission failed and the agent had run its own testbench on it, the last run of that testbench had passed in 61 of the 73 cases. Its testbenches check less than the hidden ones. These 61 failures are spread over 26 problems.
WHYWhy deterministic rules plus hand labels, with a separate hold-out set
Rules give every failure exactly one label by a fixed precedence, from logs and traces, the same way every time, so anyone can rerun them on the committed CSVs. Hand labels check whether the rules say what a person reading the log would say. The rule-development set (15 real failures) found that the earlier rules missed name mismatches: they agreed on 12 of 15, the misses led to the signal_missing_hidden rule, and with it they agree on 15 of 15. Because that set shaped the rule, it cannot score it. So a hold-out set of 15 other failures was drawn after the rule was fixed (seed 20261024): 15 of 15 agree. It contains no signal_missing_hidden case, so the new rule is checked only on the development set, and the same person wrote both the labels and the rule.
Read as a verification engineer would
The agent's self-check is a testbench with no coverage and no quality measure, which is why it passed on code the hidden tests failed in 61 of 73 cases; the planned M3c milestone answers that with a mutation-score gate on the agent's testbench and an interface diff by elaboration.
The hidden cocotb tests play the role of a sign-off regression the designer cannot see. The agent's own testbench plays the designer's unit test. Mapped onto the usual verification concepts, here is what the project does and does not do.
| Concept | What this project does | What it does not do | Status |
|---|---|---|---|
| Reference model and scoreboard | The hidden tests compare the design with a model; a DES failure reads DUT o_data does not match model o_data. The harness's pass or fail is the only score. |
The agent's own testbenches are not inspected for whether they have a model; that is not measured. | HIDDEN TESTS ONLY |
| Coverage | Nothing measured. The 61-of-73 result is the symptom: a testbench that passes on wrong code did not exercise what the hidden tests exercise. | No code or functional coverage on the agent's testbench. The M5 track plans functional coverage in cocotb. | NOT MEASURED |
| Testbench quality | Measured indirectly, from outcomes: the own testbench passed in 61 of 73 failed submissions where it ran (84% false passes on those). | No direct measure yet. M3c plans a mutation score: does the agent's testbench catch deliberately broken copies of its own design? | INDIRECT |
| Constrained-random stimulus | Not asked of the agent; its prompt lists the checks it may run. | No constrained-random generation and no seed sweep of stimulus. | NOT DONE |
| Assertions (SVA) | None. Static checks are Verilator lint and a Yosys read and synth. | No SVA. The M5 track plans SVA properties checked with SymbiYosys. | PLANNED (M5) |
| Formal: BMC, equivalence, mutation | None run yet. | Planned for M3c: a mutation-score gate on the agent's testbench (Yosys mutate) and an interface diff by elaboration (pyslang), both fixed steps in the graph. Formal checks of SVA properties with SymbiYosys are planned for the M5 track. No equivalence checking is planned. |
PLANNED |
| Regression triage | Done: a deterministic label for each of 565 failures, checked against hand labels; a measured noise floor (flaky results: 10 problems flip between identical runs); a null-agent floor and a golden check (M0). | The hidden build's own compiler message is not in the log, so the cause of each compile_fail_hidden is not shown one by one. |
DONE |
The two M3 failure modes have verification answers. A testbench that passes on wrong code is a testbench-quality and coverage problem: a mutation score asks whether the testbench fails on mutated copies of the design, and so measures what it can catch. A name the hidden testbench needs but the design lacks is an interface problem: elaborating the design and diffing its ports, parameters and hierarchy against the specification catches it before submission, when the name is given. Neither has results yet.
How this compares with others
Three published rows use exactly these 92 problems in the same release, and they land in the same range as ours (48 to 53 of 92 against our 41 to 56); everything else published on CVDP uses other problems, other modes or commercial tools, and is context only.
Every number here was checked against its primary source on 24 October 2026. Our runs use only the free, open-tool split (92 problems, four categories), specific models, one run per setting in M2 and pass@1 from a single sample.
Why these compare: the same problem file, the same release, and the same score (the hidden tests, one run, pass or fail). Why only roughly: different models and agents, two of them with no agent loop at all; all three are marked "not independently verified" on the leaderboard; only one gives any cost figure; and the leaderboard's harness and dataset commits may differ from this project's pins (harness 8e894cf, dataset revision 5b807d9). Wilson intervals on their rows are computed here, from k and n. Source: LBC-bench, data file leaderboards.json at b835413.
The plain reading: Haiku 5.5 and gpt-6-luna inside this agent (52 to 56) land where two frontier models land in a single turn on the same problems (52 and 53), and near Claude Code with Sonnet 4.6 (48). With one run each and intervals about 20 points wide, none of these differences is established.
M3b's four arms (57.8% to 65.1% pooled pass@1), its confirming run, M3c's three arms and the Claude Code ladder (66.3% to 73.3%) are not drawn beside these rows: they were scored on 86 problems, the 92 minus 6 that no agent can reach, so their rates are higher by construction and do not sit on the same 92 as the LBC-bench rows. The LBC-bench row for Claude Code (Sonnet 4.6) is a different model and a different run from our ladder.
One further paper evaluates small models with an agentic framework on CVDP (Shankar et al., arXiv:2512.05073); its abstract gives no pass rates, and none are quoted here.
Threats to validity: what this does not show
These results hold for one cheap model per ablation arm, on 92 open-tool problems (86 in M3b, M3c and M3d), at a resolution of a few points; they say nothing about smaller effects, other models, the heavy or commercial splits, or verification-writing tasks.
- One model per arm. Every M3, M3b, M3c and M3d arm uses gpt-6-luna. An ablation that does nothing for it may matter for another model.
- Sampling is not seeded.
CVDP_SEEDis a label only; OpenRouter sampling cannot be seeded across providers. Both runs of every M3 arm carry the label seed 1. - The free, open-tool split only. 92 problems in four categories. The verification-writing categories (testbench stimulus, checkers, assertions: cid012–014) need Cadence Xcelium and are not run.
- 2 runs per arm resolve about ±5 points in M3, and M3b's 3 runs on 86 problems do little better. A real effect smaller than that would read as "no detectable effect". The null results are not evidence of no effect.
- M3b makes four comparisons with three runs an arm. None of its sign tests passes a Bonferroni threshold of 0.0125, and the bootstrap intervals are not adjusted. The confirming run re-tested only B − A, as one pre-registered test (+4.7 [+1.0, +8.5], confirmed); D − C was not re-tested and stays a hypothesis.
- The confirmation is one model and one benchmark. gpt-6-luna on the 86 free agentic problems, with one Deep Agents version. Its sign test (p = 0.093) is moderate evidence; the rule read the interval only.
- Every Claude Code ladder arm is one run, and the ladder was not pre-registered. Read its effects against the base's run-to-run spread, on top of the interval shown.
- The ladder changes harness and model together. Claude Code with a Claude model against LangGraph with gpt-6-luna, with a 60-turn budget against 10; it does not say which change is responsible. Its plan has no per-call price, so it has no comparable cost.
- M3c's gate fails open when it cannot mutate. 61 of 390 gate runs were
skippedand let the submit through, so M is not a pure "every submit was gated" arm. - Some problems are benchmark-limited. 7 of the 86 evaluation problems need a detail the task never gives (a file name, default parameters, a lookup table), shown by counterfactual reruns. No agent can be expected to pass them, so they cap every score and shrink every effect a little. They are kept in the reported scores, and the 79-problem figures are shown beside them, never in their place.
reachable.py, which built the 86, checked the names the hidden test reads but not the file paths it compiles. - M3d's specification cases are written by the same model. The spec call is blind to the design, not to the model's way of reading a specification, so its cases share the agent's misreadings; a different, stronger model for that call was not tested. M3d also kept M3c's 480 s wall cap, and much of V's and S's loss is cap stops: a larger cap would be a different experiment.
- M3d's S lost 8 problem-runs to the whole-run cost guard, which charges unreported in-flight calls. Counted at A's pass rate they would move S − A by about +1.9 points, not enough to change its verdict.
- M3b tests one memory snapshot. 40 generic lessons from 87 attempts, only 19 of them passes, several near-duplicates, 6 never retrieved. A null C − A says this memory does not help, not that no memory could.
- M3b's caching claim is inferred. Cached prompt tokens are not recorded per call, so the reading that Deep Agents' extra tokens are mostly cached rests on its cost per prompt token, not a measured cache-hit count. Its rounds are also not turns: compare the harnesses on cost and wall time, not on turns.
- The M3b effects are paired on the 86 reachable problems. On all 92 the 6 excluded problems fail in all 72 of their runs, so every effect is a little smaller and no finding changes (B − A +4.0, C − A +1.4, D − C +5.4 points).
- The hand-label hold-out had no
signal_missing_hiddencase, so the new rule is checked only on the set it was developed on, and the same person wrote the rules and the labels. - Problems are treated as exchangeable by the bootstrap and the sign test. cid003 (34 problems) weighs most.
- Self-check pass or fail is read from truncated tool output in the trace.
- The hidden build's own error text is not in the log, so a likely cause (a renamed or missing parameter on
phase_rotation_0028) is not shown for each problem. - "Is the name given to the agent?" is a word search over prompt and context files. It cannot tell a signal from the same word in prose, and it is not yet a committed tool or done for
compile_fail_hidden. - No per-category or AMBA effects. On the 8 AMBA problems every run passed 3 or 4; two runs give no power on 8 problems.
- One run per runner in M2. The seeds measure the noise on 30 problems, not on 92, and the reasoning setting for each model was picked from one run on 10 problems.
- The heavy split (75 problems) is never scored with the agent. In 46 of them the files the reference solution changes lie outside the agent's edit roots (M1 gave 53 from a check it did not commit; M3b's committed
tools/learn_in_roots.pygives 46). M3b runs the agent on the other 29 only to build its lessons memory, and 3 of those (i2c7, 46, 49) gave no test output in any attempt. - Some costs are lower bounds: one unreported call in M2 (DeepSeek seeds), one in M3 (arm C), 2 in the M3b confirming run, 4 in M3c and 37 in M3d.
- Caps are soft by design: wall time by up to about 40 s, the budget by up to one call.
- Model serving varies: DeepSeek's calls went to 15–19 providers per sweep, so its cost and behaviour mix providers.
- One log label is wrong but harmless:
tools/log_check.pycalled a simulation that hung "harness_error" (4 in the first two M2 sweeps); the pass rates are right.
What's next: open questions
M3d is done and the harness effect is confirmed; a stronger testbench requirement has now been tested and was null, three questions stay open, and no milestone to answer them has been fixed.
- The same LangGraph agent with a Claude model. Open. The ladder changed the harness and the model together, so it cannot say which moved the score. Running our LangGraph agent with a Claude model would separate the two. It is not planned.
- The gate's
skippedcases. 61 of 390 gate runs (16%, on 38 problems) could not mutate the design and let the submit through: parameters that do not resolve to integers, and designs Yosys could not read or mutate. - Fable under a longer wall cap. It stopped on the 480 s cap on 17 of 86 problems, so its ladder score is a lower bound.
- Repeat runs for the ladder. Every Claude Code arm is one run, and not pre-registered; the base's own runs differ by a few points between identical sweeps.
A stronger testbench requirement than "has one".Tested by M3d: null. Requiring a passing testbench before submit (V − A −1.9 [−6.6, +2.3]), and adding cases written blind from the specification (S − A −4.7 [−11.2, +1.6]), moved failures to "own testbench passed" (16 → 48), as M3c's gate did (14 → 50).
Questions a reviewer might ask
The short answers, each with the file it comes from.
Glossary
The terms used above, in plain words; dotted links in the text jump here.