rtl, alongside lm-eval and evalscope.
Scores land in the same dashboard, so an RTL score and a GSM8K score sit on one row for the same
model.
What you can run
One evaluation uses one runner, so RTL benchmarks and GSM8K-style ones go in separate jobs.
rtl-repo’s metric is not comparable to the others. It runs no simulation, because there is
no testbench, and reports text similarity. Ranking its exact_match next to VerilogEval’s
pass@1 compares two different things.Check it worked
pass@1 in the requested columns.
A job that fails with the RTL harness produced no rtl_report.json means the image the evaluation
ran in has no harness where TUNEPLANE_RTL_HARNESS points. That is the next section.
What the image needs (an administrator does this once)
The simulators are indeploy/docker/Dockerfile.evalkit: iverilog and verilator. That image
is a GPU image — it carries torch, lm-eval and evalscope, and it exists to generate.
The evaluators, the formal tools and the pinned upstream problem sets live in a second image,
deploy/docker/Dockerfile.eda-oss, which carries a toolchain and no model runtime and exists to
check what was generated. Keeping them apart is not tidiness: an offline benchmark step can
release its GPUs before the checking starts, and one image would force the cards to be held for
the duration of a simulation that cannot use them. It also lets the EDA toolchain be pinned on its
own cadence — a simulator version is part of what a result means, and upgrading one because a CUDA
wheel moved would silently change every hardware score in the archive.
What is in the eda-oss image
Fromubuntu:24.04, with the distribution’s own versions:
Plus VerilogEval at
/opt/verilog-eval and RTLLM at /opt/rtllm, cloned at pinned commits and
never vendored into this repository — a copy of somebody’s problem set becomes a different
benchmark the moment they edit a problem.
Three things it deliberately does not carry:
- No vendor tools. Synopsys, Cadence and Siemens flows need licences this image cannot hold
and a licence server it must not reach on its own. A deployment that has them builds its own
image
FROMthis one and registers it as its own Toolchain Profile. What this image is, is the floor: the open-source flow that makes the public benchmarks reproducible without anybody’s entitlement. - No model. Generation happens in the evalkit image or on a Model Deployment; this image receives RTL and returns verdicts.
- No
tuneplaneinstall. The Job Capsule provides the SDK at run time, the same arrangement evalkit uses, so the contract and the adapter that speaks it cannot drift.
TUNEPLANE_CVDP_DIR and TUNEPLANE_RTL_REPO_DIR, defaulting to /opt/cvdp and /opt/rtl-repo;
an image without those directories refuses those suites rather than scoring them.
The harness is an adapter that scores nothing
The platform still defines only the contract:TUNEPLANE_RTL_HARNESS now has a default, tuneplane-rtl-eval, a console
script the SDK ships. It is an adapter, not a benchmark. It loads a suite’s problems from the
upstream evaluator’s own JSONL files, asks the model for samples, hands each sample to that
evaluator, and turns what comes back into the report contract. Every judgement about whether a
circuit is correct is made by the upstream tool, in the upstream’s own way.
An evaluator that is not installed at all raises and names the variable to set, rather than being
reported as a tool failure: a missing evaluator is a deployment error an operator has to see,
where a simulator that crashed on one design is a per-task fact.
TUNEPLANE_RTL_TIMEOUT_S bounds
one simulation — 600 seconds by default, with a floor of 10 — because a testbench’s runtime is the
customer’s business, not the adapter’s.
The adapter reports
pass@1, pass@<samples> and tool_failures per suite. The func_pass
and exact_match metrics that the rtllm-v2 and rtl-repo packs declare are not among them; a
deployment that wants those numbers needs a harness that computes them and points
TUNEPLANE_RTL_HARNESS at it.What the report carries
The adapter writes<output-dir>/rtl_report.json. Version 2 adds one verification report per task
beside the scores; version 1, {"results": {…}} alone, still parses.
lint, compile,
simulate, formal, synthesize, coverage, and custom for a deployment’s own flow — and
ordered as a flow runs them, cheapest first, because a design that does not lint has no simulation
result worth waiting for. There is no stage for “the model’s answer looked plausible”: a judgement
with no tool behind it is a Rubric, and rubrics have their own path.
Two shapes are refused when a stage is constructed, because each would be a lie in one direction:
a stage that says its tool ran but gives no verdict, and a stage that carries a
pass or fail
verdict from a tool that did not run.
A tool that did not run is not a design that failed
Every stage can end three ways rather than two:
A licence server that was down, a simulator that segfaulted, a container without
iverilog on its
PATH: none of those is evidence about the design. Scoring them as a failure is the single most
damaging thing an evaluation of generated engineering work can do. In training it teaches the
model to avoid whatever it was doing when the licence queue was busy; in a report it is a claim
about a circuit nobody checked.
So the rule runs all the way through:
- A report’s verdict is derived, never declared. No stages, or any tool failure anywhere, and
the whole report is
unknown— even when every stage that did run passed. A design that compiles and was never simulated has not been shown to work, and reporting the compile as the answer is how “it builds” becomes “it works”. - A report with any tool failure refuses to become a reward.
as_reward()raises and names the broken stages rather than returning0.0. A corpus that scores tool failures as zero teaches a policy to avoid whatever it was doing when the tool broke, and the model has no way to tell that apart from the circuit being wrong. tool_failurestravels as its own count. It is reported beside the pass rate rather than folded into it, the same wayrows_scannedtravels with every data-scan count. A suite with tool failures reads as incomplete, not as lower-scoring.pass_rateisnullwhen nothing could be scored, never0.0. An empty denominator is not a pass rate of nought, and that difference is what stops an image with no simulator from publishing a perfect record of failure.
tool_ok: false, a simulation that times out becomes a simulate stage with tool_ok: false
(a testbench with no $finish hangs on a correct module too), and only a non-zero exit from the
evaluator becomes a fail.
What is not proven
This section is longer than it would be if the answer were shorter.Dockerfile.eda-osshas never been built in CI. The release workflow builds the console image and nothing else.scripts/image_smoke.pycarries checks for this image — every simulator and formal tool on PATH,cocotbimportable, the upstream problem sets cloned — and no workflow runs them.- The build cannot succeed unpinned.
SBY_COMMIT,VERILOG_EVAL_COMMITandRTLLM_COMMIThave no defaults and the build fails at the line that checks them, so there is no known-good invocation to copy out of this repository. - The adapter’s call into the upstream evaluator has never run against a real checkout. Its tests stub the subprocess, so loading a suite, building prompts, assembling stages, writing the report and every failure branch are covered, and the actual invocation of VerilogEval’s or RTLLM’s evaluator is not.
- Generation is a GPU path. The one function that calls vLLM is isolated on purpose and runs in no CI, because no runner this project uses has a GPU.
- No score in this repository has been produced against a real simulator or a real GPU. Every number in the tests came from a stub.
- The per-task
verificationsblock is written but not yet read. The SDK can parse it back out of a report; nothing in the control plane or the console consumes it, so the stages do not appear on any page.
CVDP’s agentic track runs outside the scheduler
CVDP has two tracks. The non-agentic one is everything above. The agentic track has the model use tools repeatedly while solving: read files, run simulation, inspect waveforms, edit, retry. Two properties put it outside a training job:- It launches containers. The official harness pulls a Docker container per problem. An evaluation job here already runs inside a container, and Docker-in-Docker on KubeRay or Slurm needs a privileged container or sysbox, which most internal clusters do not grant.
- It needs a callable endpoint, not a weights path.
--train-run is what makes the evaluation gate and the model registry attribute these scores to
that training run. Without it they are an isolated number.
Two things about that flow:
- The ingestion entry point accepts structured scores only and does not parse harness report
text. CVDP emits
report.txtandcomposite_report.txt, so keep the conversion in your own script. Then an upstream formatting change is a change to your script rather than a broken ingestion path. - The deployment token carries one permission, reporting scores. It gets pasted into an evaluation host’s environment, so it must not also be able to write logs, change job state or upload artifacts.