No. 77 / project of 147 on the ladder

AtomVM input FIFO IRQ bridge

introduces — FIFO-not-empty machine external IRQ; M-mode ack handler; AtomVM IRQ observability NIFs

harden statelast run2026-05-05
signoff
  • DRCNOT RUN
  • LVSNOT RUN
  • antennaNOT RUN

P76 made host input practical by batching the MMIO FIFO drain. P77 adds the missing low-level interrupt proof: the FIFO can now raise the core’s machine external interrupt line, the bare-metal runtime can take the trap, ack the source, and return to AtomVM without crashing.

P77 AtomVM input IRQ bridge starting
rendered frames: 1
input events: 6
input batches: 1
input max batch: 6
input irq count: 1
input irq seen: true
input fifo status: 0
P77 AtomVM input IRQ bridge PASS

What changed

The input FIFO gained a control register at 0x10001010. Software writes bit 0 to enable the source. The trap handler writes bit 1 to ack and disable it. That makes the interrupt edge useful without letting a nonempty FIFO retrap forever while AtomVM is still running ordinary C and Erlang code.

layernew piece
RTLFIFO-not-empty pending bit and IRQ enable/ack register
top-levelFIFO IRQ ORed with the existing external IRQ input
runtimeM-mode external IRQ handler with scratch register save/restore
C platformp77_input_irq_init() enables mie.MEIE, mstatus.MIE, and the MMIO source
NIFschip:input_irq_count/0 and chip:input_irq_seen/0 expose the trap result
gamesmoke log reports IRQ count next to FIFO/batch counters

The scripted smoke queued six events. The harness delivered all six, the input IRQ fired once, the game drained the events as one batch, the FIFO status returned to 0, and the run halted PASS after 14,774,406 post-load cycles.

Still not the final shape

This is interrupt delivery into the runtime, not yet an AtomVM-native event source. The IRQ handler intentionally does almost nothing: count, ack/disable, restore registers, and mret. The Erlang Tetris server still calls chip:input_events/0 from the game tick, and that NIF re-enables the source after draining the FIFO.

The next AtomVM step is to connect this IRQ to scheduler wakeup or a real port/listener path so a running Erlang process can receive input messages without being polled from render code.

Verification

cd projects/77_atomvm_input_irq/test
make all
make verilator-run-input-smoke
SDL_VIDEODRIVER=dummy uv run --with pygame --with pillow ../viewer.py \
  captured/frames/frame_0000.bin 128 96 --scale 4 \
  --png captured/p77-input-irq-smoke-frame.png

Honest Status

checkstatus
AtomVM input IRQ bridge startsPASS
Host FIFO input reaches harnessPASS
RTL MMIO input FIFO buffers eventsPASS
FIFO-not-empty input IRQ reaches M-mode trap handlerPASS
IRQ ack/disable prevents trap stormPASS
chip:input_events/0 drains batched host eventsPASS
chip:input_irq_count/0 reports nonzero IRQ countPASS
Tetris framebuffer rendersPASS
Scripted quit via input batchPASS
AtomVM scheduler wakeup from input IRQNOT RUN
F/D architectural complianceNOT RUN
LibreLane hardeningNOT RUN

What just happened?

The chip can now take an input interrupt while AtomVM is running and come back alive. That is the low-level bridge P76 was missing. The remaining work is making the interrupt wake an Erlang-side event path instead of merely making the next polling NIF safe and observable.