Blog | About

Zig extension for Zephyr - Enter LLM

Sep 14, 2026

One of the things that has been worrying me in the Zig extension for Zephyr RTOS series is the fact that only a few of the syscalls had been properly translated to Zig.

And there is a lot of them. Once the first ones were done (and improved), the task gets to be a bit tedious. Not without challenges lurking at every corner, but the novelty and curiousity factor drops.

Talking about novelties, it’s 2026, so it’s time to use some LLM. Seems the perfect usage - the trailblazing has been done, now it has to just do more of the same. Or has it?

A wild FIFA World Cup appears

I’ve starting playing with it a few months ago. I’ve got the basic plan for Claude and started playing (not without trying to keep it somewhat tame). Initially, I’ve used Sonnet 4.6 model, with a prompt that basically asked to expand the manual_imports.zig file, where I’ve “curated” some Zephyr API, to a parallel file called “ai-imports.zig”.

But then the 2026 FIFA World Cup started, and I ended up losing some focus (yes, shame on me). When I finally tried to come back, I’ve noticed I messed up my container environment1 and basically had to start over.

So I’ve restarted the work, now using Opus 5 as the model.

A bolder model?

Since I was taking notes during the experiment, I could use the same prompt2 and somewhat compare the models. And it was different.3

While Sonnet 4.6 basically followed what I’ve said (and found some bugs in my code in the process), Opus 5 “decided” to question the manual approach (was it swayed by the “manual” word in the filename?) and suggested using a “generator” to create the Ziggy4 syscalls. Amusing!

The whole point about the “manual curation” was that I could see what was the intent of the API and translate it more properly using Zig facilities when applicable. An example is to check what each error return actually means and use a more meaningful name for the errors. I was curious about how it proposed generator would preserve this.

So I had to steer it outside its confusion when I pointed it these points. Initially, it was considering that a non-natural Zig was better. When I pressed it, it complained it had to have too many exceptions in the generator code to handle different APIs (not exactly a surprise). But I eventually managed to point it where I wanted it to go.

Which wasn’t the same place I thought I wanted to go when I started. You see, one of the points it used for the generator was that all syscalls would be available, even if not in a Zig ergonomic way. And that is useful. So the final approach was to have the generator create a “C-style Zig” set of APIs, and then a “curated” layer that wouldn’t change when the generator runs. If some “curated” API changes on Zephyr, the curated layer would break on build (probably) and help catch any issues. And an LLM would be the first pass of curation. So we got to work!

(And as Sonnet did, it also found issues on my code. A few of them indeed...)

The process

Doing this actually took me some time over several days - I do this on my spare time after all, hence using an AI code assistant made sense. And it was definitely not a brain-dead process.

After setting the goals, it got to ask me a few questions, which was nice. For instance, it asked me which style a given API call would have. So I could have sem_take(sem) or sem.take(), and I opted for the latter.

It did some... interesting things along the way. An example is that it proposed tryTake for the non-blocking semaphore take (using K_NO_WAIT in k_sem_take). I’m guessing it got this from other programming languages, as “try taking” a semaphore is not a Zephyr idiom.

And I needed to keep a close eye on it. It would sometime drop things without explanation (like when it dropped the removal of .ARM.exidx sections). Another time it added build artifacts to git.

However it also did some nice things, like comptime checks for flags so that if they change in Zephyr, that can be caught during build.

It also amused me on some moments - it seemed to be throwing a tantrum at a certain point:

(...) the API could create threads but never not start them, and the way Zephyr expresses that is genuinely nasty: K_FOREVER in the delay slot means "never schedule", not "wait forever".

- Opus 5 about K_FOREVER meaning on k_thread_create

Seriously, in what kind of text was this trained on?

At some point it started having trouble figuring out some APIs, like pipes and timers. But by then I was a bit tired to properly think through about each problem it stumbled upon. I ended up settling for accepting what it generated with the promise that I will, at some point, carefully review these APIs.

Maybe a contributing factor for becoming tired is that the experiment lost a sense of awe that it had before, when I was actually making the board do new things - like blinking LEDs and such. With the assistant, it was easy to do the boring stuff, like having tests for everything. But it was still the boring stuff.

In the end, it was really helpful, and while it took some time, it was also really fast. It was estimating around 150 API calls to “curate” - and that was somewhat complete after a few days for me, but only a few hours of API usage.

And if you are curious about the errors it spotted, they were of the nature one expects from a somewhat repetitive work: I’ve copied the wrong syscall number some times, inverted some parameters, that kind of thing =D

The good

  • Really fast way to get and try new ideas
  • Doesn’t get bored doing the boring stuff
  • Catches intent typos (like using wrong syscall number)

The bad

  • Ridiculously verbose
  • Wastes too many tokens on git mistakes

The ugly

  • It can be... judgmental?

Future work

My idea here is to slowly review the patches and bring them to my Zephyr fork. I’ll probably rewrite the comments that I deem necessary and get rid of all useless verbosity. If in the meanwhile you want to check the results of this LLM powered experiment, they live at this branch.


tags: zig, zephyr, zig-zephyr-series, llm

  1. I’ve probably changed the underlying directory (by running some git checkout and or git stash on it). I knew I couldn’t do it, but after several weeks, I’ve messed it up. 

  2. Not exactly, as I had updated the container environment and wanted to let the assistant know what was there, but the core of the instructions were the same. 

  3. Not that I hadn’t noticed during my day job, but it was more impressive here. 

  4. Ziggy in the sense of more Zig natural, Zig-like, Zig-ergonomic or so.