⚠ WIP ⚠
← projects

Mirage — A Transformer Accelerator Designed by an AI Agent

Mirage is an open-source transformer accelerator designed by an AI agent, specifically Kimi K3. It runs Transformer encoder inference for English to Arabic and Arabic to English machine translation. It targets the Digilent Cora Z7-07S (Zynq-7007S), a board that carries 14,400 LUTs, 66 DSP48E1 slices, 50 RAMB36 blocks and one 667 MHz ARM Cortex-A9. This page documents the agentic workflow that built it against a 19-task plan, a week-by-week schedule of module-specific features in which each core was verified before the next, from the matrix multiply array through multi-head attention to the fully integrated encoder.

Mirage top-level block diagram, four quadrants: a 12 by 5 DSP matrix multiplication array, multi-head attention with Q K V weight BRAMs and a finite state machine, the softmax path with max finder, Taylor approximation and quantizers, and the feed-forward network with projection, hidden and output layers behind BRAM caches.
Fig. 1 · The provided Mirage top-level block diagram, cropped to the diagram body from Mirage/images/mirage-top-level-bd.png. The four cores it names are where the plan's audits and drafts land. Select any image on this page for its full-resolution file.

01 The problem

In the silicon budget, a 6-layer encoder with 256-dimensional activations has to run on a part that offers 66 DSP48E1 multipliers and about 225 KB of BRAM. Since the full stack cannot fit, one physical encoder layer stands in for all six and is walked six times per sentence. Pre-synthesis, the multiplier budget can then be expressed like this:

DSP48E1 floorplan of the XC7Z007S: 65 of 66 slices allocated A grid of 66 cells, one per DSP48E1 slice. 60 cells are allocated to the gemm_array matrix multiply tile, 4 to the taylor_exp exponential unit, 1 to the drafted softmax modules, and exactly 1 cell is free. Pre-synthesis estimate from reports/w11-resource-map.md. XC7Z007S · DSP48E1 BUDGET · PRE-SYNTHESIS ESTIMATE (W11)

◂ swipe to pan the diagram ▸

Fig. 2 · 65 of 66 DSP48E1 slices allocated before synthesis (reports/w11-resource-map.md).

The other constraint is trust, since a chip is a long chain of decisions where a week-3 arithmetic error can quietly poison week 9. Every task closes on evidence for that reason, and every claim on this page carries the tier of evidence behind it.

02 Architecture

The four roles

The structure of this project is a chain of four parts, and each part has exactly one job. The agent is the harness loop in workflow/ and harness/, which picks one task at a time from the plan and refuses to close it until the files it promised exist on disk. The model is Kimi K3, which takes each brief and does the engineering work of writing the reference models, drafting and repairing the VHDL, and leaving a report behind. The Mirage transformer is the accelerator itself, the design that all of that work lands in. Together the agent, the model and the transformer constitute the workflow, while I steer the design process by writing the spec, building the gates, and reviewing what comes out of each phase:

The four roles: agent, model, and Mirage transformer form the workflow, with the human steering A box labelled the workflow contains three nodes in a chain: the agent, which briefs and gates, the model Kimi K3, which drafts and repairs, and the Mirage transformer, the accelerator design the work lands in. Above the box, a node labelled human, Fahad, sends a dashed steering edge into the workflow, carrying the spec, the gates and review. human steers the design spec · gates · review THE WORKFLOW the agent briefs · gates · retries the model · Kimi K3 drafts · audits · repairs Mirage transformer the accelerator design brief drafts

◂ swipe to pan the diagram ▸

Fig. 3 · agent → model → Mirage transformer, together the workflow, with the human steering from outside it.

Substantial work on Mirage predated my involvement, since the starting point was a provided minimal codebase that was roughly 40% complete. That base contributed the matrix multiplication, attention and softmax cores, the block diagram in Fig. 1 and the model scripts, and it is credited directly as the upstream Mirage repository. The workflow treated all of that code as read-only for the entire run.

The harness

The harness is the half of the workflow that never trusts a transcript, and each block below can be selected for the note recorded against it:

Harness architecture: plan, gate CLI, model, and evidence tools Four zones left to right. PLAN: the spec, the 19-task plan, and the state file. GATE: the workflow CLI whose complete command exits 2 unless declared artifacts exist on disk. MODEL: the Kimi K3 model with pre-tool scope guards beneath it. EVIDENCE: ghdl, Python golden models, report artifacts, and the trace log. Task briefs of at most 500 tokens flow from the state file through the CLI to the model. The gate re-reads the disk before closing a task and writes results back to the state file. PLAN GATE · workflow.cli MODEL EDA · EVIDENCE mvp spec 19-task plan state file workflow.cli replies ≤500 tokens artifact gate Kimi K3 scope guards ghdl goldens artifacts trace parse brief ≤500 tok one task gate re-reads the disk veto log verdict

◂ swipe to pan the diagram ▸

harness Select any block. The artifact gate is the one that matters.

data · artifacts gate checks purple = the model

Fig. 4 · The harness (workflow/, harness/hooks.py). Briefs come from the state file and evidence comes from ghdl and spec-first goldens, while completion is decided by a gate that reads the disk.

Write-up for this section is still in progress.

The datapath

In the datapath, int8 weights stream in from DDR while int16 activations stay inside the fabric, and four of the blocks below carry real defects that the loop found:

Mirage datapath: processing system, AXI stream, and the encoder layer instantiated once Top zone: the Zynq processing system with the ARM Cortex-A9, AXI DMA and DDR3, feeding an AXI4-Stream rail. Bottom zone: the programmable logic holding token embedding, weight stream controller, score BRAM, and the encoder layer blocks: qkv score calculation, taylor exponential, softmax core, two add-and-norm stages, feed-forward network, and output projection. A return edge marks the same physical layer being reused six times. Dashed outlines mark model-drafted modules. PROCESSING SYSTEM · ZYNQ XC7Z007S ARM Cortex-A9 PS · driver + tiling AXI DMA · HP0 weights + activations DDR3 all 6 layers of weights UART console demo host · never run AXI4-STREAM · 100 MB/s planning assumption PROGRAMMABLE LOGIC · 14 400 LUT · 66 DSP48E1 · 50 RAMB36 ENCODER LAYER · INSTANTIATED ONCE token embedding int8 · 256-dim weight stream prefetch layer n+1 score BRAM 128 × 21 bit qkv_score_calc Q·Kᵀ · 8 heads taylor_exp_approx Q9 · |x| ≤ 0.5352 smx_core softmax · V add & norm ① int16 inter-stage ffn_core 256 → 1024 → 256 add & norm ② int16 inter-stage output proj → logits logits ×6 · time-multiplexed layer reuse

◂ swipe to pan the diagram ▸

datapath Select any block for the audit note recorded against it.

data flow control · prefetch heavy edge = layer reuse ×6 dashed outline = model-drafted

Fig. 5 · Datapath from spec/mirage.state.json and the W6 to W11 reports. Reusing one layer six times is what makes it fit.

03 The loop & the ledger

A task the loop can act on has to be falsifiable, so "verify the softmax" cannot be a task while "smx_core matches the Python golden for 8 heads and 4 tokens" can be one. The loop below runs five verbs against 19 such tasks, and the timeline under it records when each closure landed:

The agent loop as a cycle, with the decision that terminates it Five nodes in a cycle: next, start, execute, verify, then a decision diamond asking whether the declared artifacts exist on disk. Yes leads to task closed, 19 of 19 with none forced, and back to next. No leads back to execute for at most two retries, and a third failure leads to blocked, which only a human force flag can clear. Each node can be selected for its note. next state file picks start locks one open execute golden → RTL verify run the check artifacts on disk? task closed 19 / 19 · none forced blocked human --force only yes next task · ×19 no · retry ≤2 3rd failure

◂ swipe to pan the diagram ▸

Fig. 6 · The only path from "I think I'm done" to closed runs through the gate, and the only way past a block is a human. Contract: harness/loop.py, workflow/query.py.
The 19-task timeline in closure order, grouped by phase Nineteen circles on a horizontal axis in closure order from W1 to W19, grouped into six phase bands: P1 attention core, P2 FFN and encoder, P3 platform, P4 translation, P5 optimization, P6 hardening. The run opened on July 24 at 17:37, paused overnight for 11.7 hours between W13 and W14, and froze the MVP on July 25 at 13:28. Each circle can be selected for that task's outcome, closing time and artifact. P1 · ATTENTION P2 · FFN P3 P4 P5 P6 · FREEZE W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 run opened Jul 24 · 17:37 overnight pause · +11.7 h between W13 and W14 MVP frozen Jul 25 · 13:28

◂ swipe to pan the timeline ▸

ledger Select any task, or step through all 19 in closure order.
Fig. 7 · All 19 closures from spec/mirage.state.json in order. Select a task, or step through the run, for what it found and when it closed.

The gate itself is one command, and a refusal is recorded verbatim as follows:

the gate, refusing
# close a task whose declared report does not exist yet
$ python -m workflow.cli complete p1/w3 --artifacts reports/w3-exp-error-histogram.md
REFUSED p1/w3: acceptance not verified
  artifact missing: reports/w3-exp-error-histogram.md
  fix, or re-run with --force to override
$ echo $?
2

Format as implemented in workflow/cli.py, where --force was used zero times.

What the run spent

Both sittings were logged end to end, so the model's spend is measured from the session logs rather than estimated, and the numbers below are rounded from those logs:

live session time

≈9.2 h5.6 h on Jul 24 and 3.6 h on Jul 25, around the overnight pause

model calls

308Kimi K3 requests across the 19 tasks

fresh tokens

≈583k≈416k read in as prompts and ≈167k written out

cached reads

≈30Mprompt tokens re-read from cache to keep context warm

Provider-measured counts from the Kimi wire logs, summed over the two sittings that closed all 19 tasks. A 6.2 h session after the freeze is not counted.

Where the phases landed

The plan's phases land inside the four cores of the block diagram, which are shown here as direct crops from Fig. 1:

Multi-head attention quadrant of the Mirage block diagram: Q K V weight BRAMs, token caches and a finite state machine.
Fig. 8 · The multi-head attention core, where P1 opened. W1 found the score_sum overflow, and W6 integrated all 8 heads at worst 3.98 LSB.
Softmax quadrant of the Mirage block diagram: max finder, Taylor approximation and quantizers behind BRAM caches.
Fig. 9 · The softmax path, where W3 exposed the clamp defect and W4 and W5 drafted smx_cache and smx_core.
Feed-forward network quadrant of the Mirage block diagram: projection, hidden and output layers behind BRAM caches with a finite state machine.
Fig. 10 · The feed-forward network core, where P2 ran. W7 found it failing 10 of 10 trials and closed it with a redesign spec.
Matrix multiplication quadrant of the Mirage block diagram: a 12 by 5 array of multiply-accumulate DSP cells.
Fig. 11 · The 12×5 DSP array every phase leans on, measured in W14 at 36 of 36 dot products with start to done in 1 clock.

04 Results

Golden model first and RTL second kept finding real upstream defects, which the table records module by module:

Defects found in upstream RTL (reports W1 to W9)
ModuleAs foundRoot causeAfter the loop
taylor_exp64/64 sampled points ≥100% errorimplicit clamp swallowed the input (Q-format)0.9683% max error (Q9, |x| ≤ 0.5352)
smx_core e2eattention 87–107 int8 LSB from goldenexp input scaling off by ≈2¹⁸worst 2.99 LSB, RMS 0.52
ffn_core10/10 trials fail and it won't compileno declaration for "mac_array", 278 KB vs 225 KB BRAMredesign spec: 16-bit activations, streamed weights
6-layer stackdrift >1 LN unit by layer 1softmax chaotic under int8 inter-stage quantizationint16 inter-stage: 0.0062 LN worst over 3 layers

Composition

DSP multipliers: 65 of 66 allocated Pie chart of all 66 DSP48E1 slices. The gemm array takes 60, the taylor exponential takes 4, the drafted softmax modules take 1, and 1 is free. Each slice is named by a label traced to it with a leader line. DSP MULTIPLIERS · 65 OF 66 ALLOCATED all 66 DSP48E1 slices · pre-synthesis gemm_array · 60 taylor_exp · 4 smx drafts · 1 free · 1
Fig. 12 · Where the 66 DSP multiplier slices go: 60 to the matrix tile, 4 to the exponential, 1 to the drafted softmax and 1 free (reports/w11-resource-map.md).
Block RAM: 42 of 50 allocated Pie chart of all 50 RAMB36 blocks. Q K V and Wo weight buffers take 32, the FFN takes 5, projection and score buffers take 4, the softmax cache takes 1, and 8 are free. Each slice is named by a label traced to it with a leader line. BLOCK RAM · 42 OF 50 ALLOCATED all 50 RAMB36 blocks · pre-synthesis QKV/Wo · 32 FFN · 5 score/proj · 4 smx cache · 1 free · 8
Fig. 13 · Where the 50 block RAMs go: 42 allocated and 8 free. The split only holds because the FFN's 262 KB of hidden and output weights stream from DDR instead of sitting on chip.

Comparison

Time to translate one sentence: golden software model versus GHDL verification Two horizontal bars. The golden software model in Python prices the 6-token sentence at 25.98 milliseconds. The GHDL-verified VHDL figure is 16.74 milliseconds, a 36 percent reduction. Cycle-model figures at 200 megahertz. TIME TO TRANSLATE ONE SENTENCE ms · cycle model at 200 MHz · lower is better golden software model (Python) 25.98 GHDL verification (VHDL) 16.74 · −36%
Fig. 14 · The baseline came from the software planning model, and the W15 pass re-priced it with the gemm rate verified in GHDL while overlapping weight streaming with compute (reports/optimization-report-v1.md).
Tokens translated per second: golden software model versus GHDL verification Two horizontal bars. The golden software model in Python projects about 231 tokens per second. The GHDL-verified VHDL figure is about 360 tokens per second, up 56 percent, against a real-time need near 15 tokens per second. TOKENS TRANSLATED PER SECOND cycle model · 6-layer encoder · higher is better golden software model (Python) ≈231 GHDL verification (VHDL) ≈360 · +56%
Fig. 15 · Real-time NMT needs about 15 tok/s, so the optimized figure carries roughly 24× headroom, and the pipeline is now DDR-bound with the crossover near 192 MB/s.
Resource utilization against the device, pre-synthesis Three horizontal bars. DSP48E1 at 65 of 66 is 98 percent. RAMB36 at 42 of 50 is 84 percent. LUT at about 3,600 of 14,400 is 25 percent. Pre-synthesis estimates, since no Vivado run exists. HOW MUCH OF THE CHIP MIRAGE USES device budget share · pre-synthesis DSP48E1 65/66 · 98% RAMB36 42/50 · 84% LUT ≈3.6k/14.4k · 25%
Fig. 16 · DSP at 98% is expected to fail the acceptance check (W15 · GHDL), and the documented mitigation time-multiplexes the gemm as 2×30 (reports/w11-resource-map.md).

Across layers and across the run

Per-layer drift for each inter-stage activation format, log scale Line chart over layers 0, 1 and 2 on a logarithmic drift scale in LayerNorm units. The three int8 formats climb from 0.039 to 0.163 at layer 0 up past 2 LN units by layer 2, all far above the 0.01 budget line. The int16 format stays flat at 0.0043 to 0.0062, inside budget. Model-level results from reports/w9-multilayer.md. ROUNDING ERROR AS LAYERS STACK drift from the software reference · log scale 0.001 0.01 0.1 1 budget 0.01 L0 L1 L2 int8 ×8 / ×16 / ×32 int16 ×512
Fig. 17 · Drift is how far the hardware's numbers wander from the software reference as rounding errors compound across stacked layers, measured in LayerNorm units. Every int8 format crosses the 0.01 budget by layer 1, while int16 stays flat, which is why the datapath keeps int16 between stages (reports/w9-multilayer.md).
Cumulative task closures over the elapsed hours of the run Step line from 1 to 19 closures over 19.85 elapsed hours. Six closures land in the first half hour, seven more land in a burst near hour 5, the line pauses overnight, and the last six land between hours 17 and 19.85. Two shaded bands mark Kimi K3's live sessions, and the unshaded gap between them is the human pause and review. TASKS CLOSED OVER THE RUN each step up is one task closing · 19.85 h total 0h 5h 10h 15h 20h 0 19 W6 W13 W19 overnight pause

purple band = Kimi K3 live session unshaded gap = my pause · review

Fig. 18 · Each step is one task closing, timestamped in spec/mirage.state.json. The shaded bands are Kimi K3's two live sessions, and the unshaded gap is where I paused the run overnight and reviewed.

Comparison against golden reference, FPGA model and hardware

Golden reference versus FPGA cycle model, log scale Two bars on a logarithmic scale for one identical toy-scale sentence. The measured pure Python golden reference takes about 5.43 seconds on the CPU, while the FPGA cycle model at 200 megahertz estimates 3.18 milliseconds, a ratio near 1,700. GOLDEN REFERENCE VS FPGA CYCLE MODEL same sentence, same math · log scale software · Python golden, measured on CPU ≈5.43 s FPGA · cycle model, GHDL-measured gemm rate ≈3.18 ms · ≈1,700×
Fig. 19 · The software side is the pure Python golden reference timed on the host CPU, and the FPGA side prices the design's cycle count at 200 MHz using the gemm rate measured in GHDL simulation. The 1,700× is against interpreted Python, and the report itself puts an optimized int8 CPU baseline at roughly 10 to 20× (reports/evaluation-report-draft.md).
FPGA cycle model versus silicon, where no board run exists Two horizontal bars. The FPGA cycle model prices the toy sentence at about 3.18 milliseconds. The silicon bar carries the same projection in gray and is marked not measured, since no board run exists. FPGA CYCLE MODEL VS SILICON same sentence · FPGA image on the Cora Z7 FPGA cycle model · 200 MHz ≈3.18 ms silicon · FPGA image on the board ≈3.18 ms ⚠ not measured · no board run
Fig. 20 · The bitstream build for the Cora Z7 exists and was deliberately not fabricated, so no silicon number exists in the repository. The gray bar carries the cycle model's projection and is marked not measured, since no board run exists.
Worst output drift under single-bit fault injection, against the limit Two horizontal bars against a dashed limit line at 0.5 LayerNorm units. A single bit flip in a weight word drifts the output 0.0507 LN units at worst over 20 trials, and a flip in an activation word drifts it 0.0407. Both sit far inside the limit. FAULT INJECTION · FLIP ONE BIT worst output drift over 20 trials weight bit flip · 20 trials 0.0507 activation bit flip · 20 trials 0.0407 limit 0.5
Fig. 21 · Fault injection flips one stored bit at a time, in a weight or an activation, and measures how far the output moves. Both cases absorb the hit at least 10× inside the 0.5 limit, and the matrix's one real finding is an FFN accumulator that can overflow int32 at adversarial all-max input (reports/robustness-matrix.md).

The release check is one command, and the recorded v1.0 re-run reads as follows:

reports/final-regression.log
$ bash outputs/run_regression.sh
== model-level golden checks ==
PASS  taylor_exp_model.py
PASS  smx_e2e_model.py (2.99 < 4.0)
PASS  ffn_model.py
PASS  encoder_e2e_model.py (0.0296 < 0.05)
PASS  multilayer_e2e_model.py (0.0047 < 0.01)
PASS  greedy_decode_model.py
PASS  bidirectional_demo.py
PASS  beam_search_model.py (13 >= 13)
PASS  robustness_model.py
== RTL (ghdl) checks ==
PASS  ghdl:gemm_latency_tb
==
REGRESSION: ALL GREEN
exit=0

Evidence tiers & the exit bar

ghdl-simulated

The gemm tile at 36 of 36 dot products with start to done measured at 1 clock, and every model-drafted VHDL file analyzing and elaborating clean.

Model-level golden

All accuracy claims, from LSB drift to token-identical decode, come from bit-accurate Python models against float references, not from full-pipeline RTL sim.

Estimate / projection

Latency, throughput, the 1,700× comparison and the resource map are cycle-model or pre-synthesis figures, and nothing was measured on silicon.

Spec exit criteria and where each stands
Exit criterionStandingStatus
EN→AR & AR→EN encoder translation10/10 token-identical vs golden on toy random weights, and no trained checkpoint exists in the repomodel-level
≥3× latency vs CPU baselinethe cycle model clears it with margin, and no int8-CPU comparison exists in the repomodeled
<90% LUT & <95% BRAM, timing metLUT 25% and BRAM 84% pre-synth, timing unmeasured, DSP 98% against the task's 90% barpre-synth
Open-source repo + build/sim docsthe regression script and per-week reports exist, and no LICENSE file existsblocked
Demo script + pitch deckboth existdone

Four numbers do not exist in the repository and are not charted on this page: a BLEU score, since no trained checkpoint exists, a board power figure and a measured silicon latency, since no board run exists, and post-synthesis utilization and timing, since no Vivado run exists.