Blog › End-to-end FPGA development with Claude

End-to-end FPGA development with Claude

In July 2026 an AI coding agent — Anthropic’s Claude Code — took an association project from empty repository to measured neural-network inference on field-programmable gate array (FPGA) silicon in eight days. The agent wrote the plan documents, the register-transfer-level (RTL) code, the testbenches, the build scripts, and the debug drivers; ran the vendor toolchain; programmed the board; and diagnosed a silicon-level memory defect. One engineer supplied the hardware, the review, and the direction. This post documents the repository conventions that made an agent productive on real hardware, with a link to every measured number. The full project is public: agilex_3_ai_benchmarks.

Published 2026-07-28.

The result

The project goal: run MLPerf Tiny inference workloads on the Arrow AXC3000, a $129 board carrying an Altera Agilex 3 A3CY100BM16AE7S — a part with no hard processor system and no DDR memory, only a 16 MB HyperRAM on an 8-bit bus. The repository’s first commit landed 2026-07-03. The board arrived 2026-07-09. The first measured on-silicon inference — correct, input-dependent compute, verified against a software reference — is dated 2026-07-11. In between: 41 issues, 30 pull requests, and a plan that survived contact with the toolchain only after two major revisions.

Measured quantityValueRecord
resnet8-cifar10 INT8 top-1 accuracy, 100 CIFAR-10 test images (software reference: 86.64 %) 86.0 % result JSON
CoreDLA engine throughput at a measured 200.0 MHz compute clock 409.3 frames/s result JSON
HyperRAM sustained write bandwidth, 768-word bursts, 175 MHz clock, double data rate (DDR) 342 MB/s result JSON
On-chip M20K aggregate bandwidth, banked, output registers enabled, 300 MHz 38.4 GB/s (100 % of theoretical) result JSON

Honest caveats, straight from the project README: 409 frames/s is the inference engine’s own rate, measured from its on-chip cycle counters; end-to-end system throughput is currently bounded near 12 frames/s by a Joint Test Action Group (JTAG) input path that remains from bring-up. And a parallel bring-up track that removed the external memory entirely is still blocked at a compute-stage failure, with its findings committed rather than buried. Both limitations are stated in the repository in the same place as the headline numbers.

The repository is the interface

The engineer never pasted design context into a chat window. Everything the agent needed to know lives in the repository, in three documents that were written before any RTL.

A canonical numbers document. docs/PLAN.md holds every silicon budget, memory-wall estimate, and derating assumption, each with its provenance. The working rule is one sentence: never invent a number; cite the PLAN section instead. When the agent later found one of those numbers wrong — a HyperRAM bank voltage — it corrected the document with the versioned source cited and the discrepancy flagged in the pull request, because the conventions file requires exactly that.

A conventions file. AGENTS.md encodes the discipline gates: SystemVerilog-2017, one module per file; reset-less datapath registers with synchronous reset only on architectural state, per the device’s retiming guidance; clock-domain crossings only through shared wrappers; Avalon signal naming; Python tools as deterministic, seeded, argparse command-line programs with pytest tests. None of this is agent-specific — it is the same conventions document a new human hire would get, written down instead of transmitted by folklore.

An issue roadmap. The work was cut into self-contained GitHub issues with explicit dependencies, and the flow is mechanical: one issue, one branch, one pull request, and the pull request must walk every acceptance criterion and state how it was verified. The rule that carries the most weight is quoted here in full: “Never mark a checkbox you did not verify. ‘It should work’ is not verification.” When a criterion needs the physical board, the agent implements up to that point, checks off only what it proved, and lists the remainder under a “Hardware handoff” heading for the engineer.

Estimates never masquerade as measurements

The failure mode everyone fears from a language model is the confident invented number. The project’s defense is structural, not hopeful. Every number lands as a JSON file conforming to a results schema that makes the configuration fields mandatory — the plan’s phrasing is “numbers without configs are noise” — and forces a declared kind: estimate or measured. An estimate may never overwrite a measurement. The discipline shows in the artifacts: the clock-frequency sweep report opens by declaring that every one of its numbers is an Altera Quartus Prime timing estimate, worst corner, not a silicon measurement. The agent maintained that boundary for eight days because the schema, the review, and the conventions file all enforce it independently.

Microbenchmarks changed the plan twice

The original plan assumed custom RTL could reach the Agilex 3 digital signal processing (DSP) block’s tensor mode — 20 INT8 multiply-accumulates per block per cycle. The first microbenchmark, two days in, disproved it: Quartus Prime Pro 26.1 does not expose tensor mode to hand-written RTL or the IP catalog on this family; the capability is reachable only through the FPGA AI Suite compiler’s internal path. This is a toolchain restriction, not a silicon limit, and it invalidated the hand-rolled-accelerator track in an afternoon. A follow-up sweep quantified the honest custom-RTL ceiling of the naive design that assumption had produced — a clock plateau near 50 MHz — and the project pivoted to the vendor’s CoreDLA inference IP, which is how tensor mode actually got used.

The pattern to copy is not the specific finding. It is that the agent built the smallest experiment that could disprove the plan, ran it before any dependent work existed, and recorded the negative result in the canonical document with a note to re-check on future tool releases. Engineers know this discipline; agents make it cheap enough to practice consistently.

Hardware in the loop

The board sat on the engineer’s desk, forwarded into the agent’s Linux environment over USB passthrough, with the vendor programmer running in a container. From there the agent programmed bitstreams and drove on-chip debug over JTAG without a human in the loop — JTAG strictly as control plane, never as the data plane of anything performance-measured. The unglamorous findings got written down too: this cable configures reliably at a 6 MHz JTAG clock while 15 MHz fails silently, a detail that would otherwise cost the next session an hour.

Because several agent sessions ran in parallel on different tracks, the repository carries a file-based board lock that any on-hardware work must hold — mutual exclusion for a shared devkit, the same way a team of humans would schedule lab time. The cadence this enabled: the board arrived 2026-07-09, and the HyperRAM and on-chip memory bandwidth measurements in the table above are dated the same day.

Debugging silicon

The first full inference attempt hung. The agent’s diagnosis, driven through on-chip capture and scripted write-shape experiments, isolated a memory-path defect: the HyperRAM controller’s double data rate input/output (DDIO) write path corrupts any 32-byte beat that is written more than once, so the per-word host writes that load configuration and weights arrived corrupt. The workaround is a write-combiner in the agent-written AXI4-to-HyperBus bridge that gathers partial writes into one full-strobe beat, making the load bit-exact; the root cause traced to a missing eye-centering delay on the HyperBus clock pin and is tracked for a proper fix.

One residual risk is documented rather than waved off: the pad-launch timing on this path is calibrated per fit and is not covered by the timing constraints, so a rebuild can pass static timing analysis and still be marginal on silicon. The project’s answer is a scripted write-shape regression run on the board after every rebuild. That is the correct engineering response to an unconstrained interface, and the agent both identified the gap and automated the mitigation.

Division of labor

The human contribution was small in hours and decisive in kind: buy the board, plug it in, power-cycle it on request, decide scope and spend, and review every pull request as sole approver. The agent produced everything expressible as text — RTL, testbenches, timing constraints, Tcl drivers, Python tooling, result records, documentation, and the analysis connecting them. The boundary was never fuzzy, because the conventions file draws it explicitly: hardware-gated steps are handed off, never simulated into a checked box.

What transfers to your project

None of the following requires this board, this vendor, or this agent. A step-by-step version, with a workflow diagram and an assessment of the complexity levels this has been demonstrated at, is in the companion post: how to run an AI agent on FPGA work.

  • A canonical numbers document the agent must cite instead of inventing, corrected loudly when wrong.
  • A conventions file holding the discipline gates: reset style, bus naming, testbench requirements, tool versions.
  • Work cut into self-contained issues with dependencies; one issue, one branch, one pull request that walks its acceptance criteria.
  • A results schema that separates estimate from measured and makes configuration mandatory.
  • The checkbox rule: nothing gets marked verified that was not verified; hardware-gated work is handed off.
  • A lock protocol when more than one session touches shared hardware.

These are systems-engineering controls, not prompt tricks — specification, configuration management, and verification evidence, applied to a collaborator that types faster than it should be trusted. That framing is developed at length in the association’s AI-Augmented Systems Engineering course and the earlier post on systems engineering for FPGA design in the age of AI. The repository itself is the fullest documentation: agilex_3_ai_benchmarks, every number traceable to a JSON record with its configuration attached.