Emulating a board

Emulation runs the published firmware image - the same file that is flashed onto hardware - on a model of the chip it was built for, against MeshBench's radio channel.

It answers a question the native path cannot: does this release come up on this board, configure itself, and put a correctly formed packet on the air.

emulated microcontroller MeshCore application RadioLib SX126x driver SPI peripheral, EasyDMA GPIO: NSS GPIO: DIO1 QEMU for Xtensa, Renode for Cortex-M4 commands IRQ SX1262 model registers, opcodes, busy and IRQ lines out of process the channel waveforms, delay, terrain, noise every other node The boundary is the SPI bus, which is where a real SX1262 sits. Nothing above it is modified.
The firmware, its radio driver and the chip's register interface are all unchanged. The substitution happens at the bus, so the image under test is the image that ships.

Why the boundary is the SPI bus#

MeshCore reaches its radio through SPI: command opcodes, register reads and writes, a busy line and an interrupt line. Modelling the chip at that boundary means the firmware above it needs no modification, and what gets verified is the binary people flash rather than a re-implementation of what it should do.

The chip model#

radioserver presents the SX1262 interface and turns transmit commands into waveforms for the channel. It runs as a separate process and is shared by all three ways of running firmware:

firmwareconnection to the chip model
native buildlinked in process, no socket
QEMUUnix domain socket
RenodeTCP

One model rather than three means the radio behaves the same whichever way a node is run.

ESP32, under QEMU#

The Xtensa path uses QEMU with an SX1262 device attached to the SPI controller. Two build requirements:

nRF52, under Renode#

The Cortex-M4 path models more of the chip, because more of it is touched before the radio is reached.

SPIM3 and EasyDMA#

The nRF52840 has four SPI master instances; these boards use SPIM3 at 0x4002F000. It is identifiable from its register offsets: 0x118 is EVENTS_END, 0x304 and 0x308 are INTENSET and INTENCLR.

SPIM has no byte-at-a-time data register. The firmware writes a buffer pointer and a length into TXD.PTR and TXD.MAXCNT, the same for RXD, and triggers TASKS_START. The peripheral model reads the transmit buffer out of guest memory, runs the transaction, writes the reply into the receive buffer and raises EVENTS_END. A model that does not implement EasyDMA transfers no bytes, and the firmware waits indefinitely for a reply.

Chip select is a GPIO#

An SX1262 transaction is delimited by NSS going low and then high. Renode's SPI infrastructure does not signal the end of a transaction to the peripheral, so NSS is wired as what it physically is: a GPIO pin. On the RAK4631 that is gpio1 pin 10, with the DIO1 interrupt on gpio1 pin 15.

These pin assignments are a property of the board and are recorded per board rather than per emulator.

SEVONPEND#

Published nRF52 firmware sets SEVONPEND and executes WFE, which requires the core to wake when an interrupt becomes pending whether or not it is enabled to fire. The emulator's CPU core must implement that distinction, and the wake path has three parts: the pending-interrupt query, the binding that exposes it to the core, and the call that wakes the sleeping thread. Without all three the firmware sleeps and the node is silent while appearing healthy.

Peripherals touched during boot#

peripheralwhy the firmware needs it
TEMPtemperature read at start-up
CLOCKHFCLK and LFCLK start, waited on before anything proceeds
SAADCbattery voltage
TWIMI2C, for displays and sensors on some boards
SX1262 over SPIMthe radio

Which boards have been run#

Every row is a measurement rather than a claim: the firmware is the released image from MeshCore's own releases, and a blank cell means nobody has watched that board do that thing.

BoardMCUEmulatorbuildbootradiotxrxfloodfempower
Generic_E22_sx1262ESP32QEMU
Heltec_t114nRF52840Renode
Heltec_t096nRF52840Renode?
RAK_4631nRF52840Renode
Xiao_nrf52nRF52840Renode
Heltec_mesh_solarnRF52840Renode
Xiao_S3_WIOESP32-S3QEMU?
Heltec_v3ESP32-S3QEMU
LilyGo_TDeckESP32-S3QEMU
Ebyte_EoRa-S3ESP32-S3QEMU
Station_G2ESP32-S3-
Heltec_v2ESP32-

✓ passed ✗ failed – not applicable ? not measurable yet blank not attempted

What the columns ask for:

columnwhat it means
builda published image whose digest checks out
bootthe emulator attached and the node did not spend the run restarting
radio, txit put its own unprompted advert on the air
rxit heard another node
floodit forwarded somebody else's packet, judged at the board itself
fema front-end module was switched in
powerit still answered after being left idle

flood is the column worth reading closely, because it is the one that says the board is a working member of a mesh rather than a node that talks to itself. It is judged at the board: the packet has to arrive, be recognised as somebody else's, and go back out.

The two blanks have never been attempted rather than tried and failed.

Measured one board at a time on an idle machine. Several emulators at once will make a twelve core machine fall behind the wall clock, and a board that is running late looks exactly like a board that is broken - so a row measured beside seven others is not a measurement.

Cost and limits#

Each emulated node is a separate emulator process running in real time, costing roughly one core and 150 MB.

Around eight nodes is the practical ceiling on a twelve core machine. Past it nothing reports an error: boot times stretch, simulated time falls behind the wall clock, and the network appears to go quiet.

Because execution is tied to the wall clock, two runs of the same seed do not produce the same result. Emulation is therefore used to verify that a release works, and native firmware is used for anything that is measured.

Assigning an emulated build#

In the firmware library, board images are listed alongside native builds. use for role assigns one to every node of that role, and reports how many nodes that will be before it does. Each of those nodes becomes its own emulator.

The firmware library, emulated builds included

MeshBench documentation. Built from the running application, not from mock-ups. Screenshots are window-only captures; see CLAUDE.md for the rule that keeps them current. Edit this page.