Skip to content
Chiplab logo
Chiplab logo
Back to Now

Why your AI coding agent needs a target to run on

Coding agents write firmware just fine. Ask one for a UART driver, a clock config, a FreeRTOS task, and you'll get something plausible, often something correct. The problem shows up one step later: the agent can't run what it wrote. It can't flash your board, can't watch the pins, can't tell you whether the thing it just generated does anything at all.

The feedback loop is the product

Coding agents are useful because of a specific loop: write code, run it, read the output, fix what's wrong. Repeat. Take away the "run it, read the output" part and you're left with an agent that types confidently and hopes.

In web and backend work, that loop closes on its own. The agent runs npm test or pytest, reads a stack trace, patches the bug, runs again. The feedback is local and fast: a process on the same machine, exit code and stdout.

Embedded breaks this at the first step. There's no cargo test for "does the UART clock actually come up at 115200 baud on this exact silicon revision." Running firmware means running it on a chip, and the chip is a physical board sitting on someone's desk. The agent has no hands. It can generate code and it can compile code, but it cannot plug in a USB cable, and it cannot read a logic analyzer.

So today, the loop stops at compilation. The agent builds, sees no errors, and reports success. Compilation is not validation, though. It tells you the syntax is legal and the types check out. It says nothing about whether the RCC register you configured enables the peripheral clock you think it does, or whether the interrupt you registered fires before the DMA buffer is even initialized. Language models hallucinate register names and phantom HAL APIs with the same confidence they use for real ones, and a clean build won't catch either.

The only thing that catches it is running the firmware on the actual target and watching what comes out the other end. That's exactly the step the agent has been locked out of.

MCP as the bridge

The Model Context Protocol gives agents a standard way to call tools outside the model itself. An MCP server describes what it can do; the agent decides when and how to call it, based on what it's trying to accomplish. This is how agents already read files, run shell commands, and query APIs without every tool being hand-wired into the model.

MCP itself doesn't solve the embedded problem. It's just the pipe. The fix was never a smarter model that hallucinates fewer registers. The agent needs something on the other end of that pipe that can execute its code and send real signal back.

The build, upload, run, read contract

That target is what Chiplab is. The contract is deliberately narrow and framework-agnostic: the agent builds an ELF locally, uploads it, runs it on a virtual instance of a real chip, and reads back the UART output the firmware produced.

The "virtual instance of a real chip" part matters more than it sounds like it should. This is a simulation of the specific board rather than a generic CPU emulator: same peripheral registers, same reset and clock behavior, same interrupt timing as the physical part. Firmware that misconfigures a peripheral fails on the simulated chip for the same reason it would fail on your desk.

Runs are synchronous and bounded by simulated CPU time rather than wall clock. The agent uploads a binary, Chiplab boots the virtual chip, executes the firmware for a fixed budget, and hands back everything written to the UART. The whole cycle, upload through output, comes back in under a minute.

Here's that exchange from a live session, trimmed to the tool calls. The prompt was one line; everything below it is the agent working:

> Build and run examples/bare-metal/stm32f4-discovery on Chiplab.

$ cargo build --release
   Compiling hello-stm32f4-discovery v0.1.0
    Finished `release` profile [optimized] target(s) in 3.78s

[chiplab] issue_upload_ticket → artifact_01kyhbxcw3…
[chiplab] upload hello-stm32f4-discovery (ELF) → OK
[chiplab] run (board: stm32f4_discovery) → run_01kyhbxyqx…
[chiplab] usart2: [host: 0.23s | virt: 0s] Hello world!

What you get out of that exchange is ground truth, the same role a test suite plays in a web project. The code ran on the target, and here's what it printed. The agent reads that output the way it would read a failing assertion, and iterates from there instead of guessing.

What changes for embedded workflows

Once an agent can run its own code against real chip behavior, a few things stop being theoretical:

  • Firmware before hardware. A board that's still on order, or being spun up in a lab three time zones away, doesn't block the firmware meant to run on it. The agent writes, runs against the simulated part, and fixes what breaks before a physical unit reaches a desk.
  • Porting to boards you don't own. Shipping support for hardware you can't test on usually means a change that looks done in review and fails the first time it touches silicon. If the agent can run the ported firmware on a simulated instance of that board and read back the output, "looks done" and "is done" collapse into the same thing.
  • Chip-specific gotchas surface in runs. The nRF52840's UARTE peripheral uses EasyDMA, which can only read from RAM. Put your TX buffer in flash, a reasonable-looking choice, and the peripheral silently transmits nothing. A static read of the code won't flag that; a run will, because the agent sees zero bytes come back and goes looking for why.
  • No shared-dev-kit queue. Teams with one nRF52840 DK and three people who need it know this bottleneck well. A simulated instance doesn't have a queue.
  • A path to CI. The same build, upload, run, read contract works from a pipeline: every pull request runs its firmware on virtual boards before a human or a physical unit sees it.

None of this makes the model smarter. It gives the model something to check its own work against.

Why we built this

Chiplab exists because the agent loop needed a target on the embedded side of the wall, and there wasn't one. The contract stays small: build, upload, run, read.

If you want the hands-on version, the getting-started post walks through connecting an agent, running a known example on a simulated STM32F4 Discovery, and having the agent port it to an nRF52840 DK it's never touched.

Daniel
DanielCTO @ VeeclePublished Jul 27, 2026