Working without Hardware

You don’t always need physical hardware to develop Zephyr applications. QEMU and native_sim let you build, test, and debug on your host machine. This is invaluable for CI/CD pipelines, early prototyping, and testing logic before hardware is available.

QEMU

QEMU is a hardware emulator that can run Zephyr binaries for various architectures.

Supported Targets

Board Target Architecture Typical Use
qemu_cortex_m3 ARM Cortex-M3 General embedded testing
qemu_cortex_m0 ARM Cortex-M0 Constrained device testing
qemu_cortex_a53 ARM Cortex-A53 64-bit ARM testing
qemu_x86 x86 x86-specific features
qemu_riscv32 RISC-V 32-bit RISC-V development
qemu_riscv64 RISC-V 64-bit RISC-V development
qemu_xtensa Xtensa DSP-like workloads

Building and Running

# Build for QEMU Cortex-M3
west build -b qemu_cortex_m3 samples/hello_world

# Run in QEMU (launches automatically)
west build -t run

# Exit QEMU: Ctrl+A, X

QEMU with Networking

# Build networking sample
west build -b qemu_x86 samples/net/sockets/echo_server

# Run with host networking (TAP interface)
west build -t run

# From host, connect to the echo server
echo "Hello" | nc 192.0.2.1 4242

QEMU with Debugging

# Start QEMU with GDB server
west build -t debugserver

# In another terminal, connect GDB
arm-zephyr-eabi-gdb build/zephyr/zephyr.elf
(gdb) target remote localhost:1234
(gdb) break main
(gdb) continue

QEMU Limitations

  • No real peripherals — GPIO, I2C, SPI are limited or absent
  • Timing differences — QEMU timing doesn’t match real hardware
  • Limited peripheral emulation — some drivers won’t work
  • No power management — PM states aren’t meaningfully simulated

native_sim

native_sim (formerly native_posix) compiles Zephyr as a native Linux executable. This is the fastest way to iterate on application logic.

How It Works

Instead of cross-compiling for an embedded target, native_sim:

  • Compiles your Zephyr app as a Linux process
  • Uses POSIX threads to simulate Zephyr threads
  • Simulates timers using host time
  • Can use host networking, Bluetooth (via HCI), and file systems

Building and Running

# Build as native executable
west build -b native_sim samples/hello_world

# Run directly
./build/zephyr/zephyr.exe

# Or via west
west build -t run

# Pass arguments
./build/zephyr/zephyr.exe --stop_at=10  # Stop after 10 simulated seconds

Advantages Over QEMU

Feature QEMU native_sim
Build speed Normal cross-compile Fast (native compiler)
Execution speed Emulated (slower) Native (fast)
Debugging Remote GDB Native GDB (direct)
Host integration Limited Full (files, networking)
Code coverage Complex setup Easy (gcov/lcov)
Address sanitizer No Yes (ASan, UBSan)

native_sim with Debugging

# Build with debug info
west build -b native_sim -- -DCONFIG_DEBUG=y

# Debug natively — no remote GDB needed!
gdb ./build/zephyr/zephyr.exe
(gdb) break main
(gdb) run

native_sim with Address Sanitizer

# Detect memory errors
west build -b native_sim -- -DCONFIG_ASAN=y

# Run — ASan will report memory errors
./build/zephyr/zephyr.exe

Emulated Devices

Both QEMU and native_sim support emulated devices for testing without hardware.

Emulated I2C/SPI Devices

Zephyr provides an emulation framework for testing drivers:

# prj.conf
CONFIG_EMUL=y
CONFIG_I2C_EMUL=y
/* Overlay for emulated sensor */
/ {
    emul_i2c: emul-i2c {
        compatible = "zephyr,i2c-emul-controller";
        #address-cells = <1>;
        #size-cells = <0>;

        bme280_emul: bme280@76 {
            compatible = "bosch,bme280";
            reg = <0x76>;
        };
    };
};

Emulated GPIO

/ {
    gpio_emul: gpio-emul {
        compatible = "zephyr,gpio-emul";
        gpio-controller;
        #gpio-cells = <2>;
        ngpios = <32>;
    };
};

Using Emulated Devices in Tests

#include <zephyr/drivers/emul.h>

void test_sensor_read(void)
{
    const struct emul *emul = EMUL_DT_GET(DT_NODELABEL(bme280_emul));
    const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(bme280_emul));

    /* Set emulated sensor values */
    emul_bme280_set_temperature(emul, 2350);  /* 23.50°C */

    /* Read through normal driver API */
    struct sensor_value val;
    sensor_sample_fetch(dev);
    sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val);

    zassert_equal(val.val1, 23, "Expected 23, got %d", val.val1);
}

Creating a Device Driver Emulator

For custom drivers, you can create your own emulator:

Step 1: Define the Emulator API

/* my_sensor_emul.h */
#include <zephyr/drivers/emul.h>

struct my_sensor_emul_data {
    int32_t temperature;
    int32_t pressure;
};

void my_sensor_emul_set_temperature(const struct emul *emul, int32_t temp);
void my_sensor_emul_set_pressure(const struct emul *emul, int32_t pressure);

Step 2: Implement the Emulator

/* my_sensor_emul.c */
#include "my_sensor_emul.h"
#include <zephyr/drivers/i2c_emul.h>

static int my_sensor_emul_transfer(const struct emul *emul,
                                    struct i2c_msg *msgs,
                                    int num_msgs, int addr)
{
    struct my_sensor_emul_data *data = emul->data;

    /* Respond to I2C reads with emulated data */
    if (msgs[0].flags & I2C_MSG_READ) {
        /* Return temperature register */
        msgs[0].buf[0] = (data->temperature >> 8) & 0xFF;
        msgs[0].buf[1] = data->temperature & 0xFF;
    }

    return 0;
}

void my_sensor_emul_set_temperature(const struct emul *emul, int32_t temp)
{
    struct my_sensor_emul_data *data = emul->data;
    data->temperature = temp;
}

static struct i2c_emul_api my_sensor_emul_api = {
    .transfer = my_sensor_emul_transfer,
};

static int my_sensor_emul_init(const struct emul *emul,
                                const struct device *parent)
{
    return 0;
}

#define MY_SENSOR_EMUL(n) \
    static struct my_sensor_emul_data my_sensor_data_##n; \
    EMUL_DT_INST_DEFINE(n, my_sensor_emul_init, \
                        &my_sensor_data_##n, NULL, \
                        &my_sensor_emul_api, NULL)

DT_INST_FOREACH_STATUS_OKAY(MY_SENSOR_EMUL)

CI/CD Integration

GitHub Actions with native_sim

# .github/workflows/test.yml
name: Zephyr Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/zephyrproject-rtos/ci:latest
    steps:
      - uses: actions/checkout@v4
      - name: Build and test
        run: |
          west init -l .
          west update
          west build -b native_sim tests/my_test
          ./build/zephyr/zephyr.exe

Running Twister on native_sim

# Run all tests that support native_sim
west twister -p native_sim

# Run specific test suite
west twister -p native_sim -T tests/my_tests/

# Generate JUnit report for CI
west twister -p native_sim --report-format junit

When to Use What

Scenario Recommended Why
Unit tests native_sim Fast build, native debugging, code coverage
Integration tests native_sim or QEMU Depends on peripheral needs
Network protocol testing QEMU (x86) Host networking support
Bluetooth testing native_sim HCI over UART to host
Architecture-specific code QEMU Tests actual instruction set
CI/CD pipeline native_sim Fastest build and execution
Hardware timing validation Real hardware Emulators don’t match timing

Example Code

View the native_sim example — runs entirely on your host machine with no hardware.

west build -b native_sim examples/part6/native-sim
./build/zephyr/zephyr.exe

Next Steps

Continue to Networking Overview to learn about Zephyr’s networking stack.


Back to top

Zephyr RTOS Programming Guide is not affiliated with the Zephyr Project or Linux Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.