7.4. Zephyr RTOS
7.4.1. Overview
7.4.1.1. What is an RTOS?
A Real-Time Operating System (RTOS) is a lightweight OS designed to provide deterministic scheduling, minimal latency, and efficient resource management. Unlike general-purpose operating systems like Linux, an RTOS prioritizes real-time constraints, ensuring critical tasks meet strict timing deadlines. RTOSs are commonly used in embedded systems, microcontrollers, and simulation environments where fast boot times and efficient execution are essential.
7.4.1.2. Zephyr Use Cases
Zephyr provides a lightweight build flow and execution environment that supports running complex end-to-end workloads without the overhead of Linux.
Key benefits: * Multicore & Threading Support: Zephyr provides a task scheduler with multithreading and multicore support. Heterogeneous SoCs can be used via task pinning. * POSIX-like API: Zephyr provides POSIX APIs for threading and synchronization, useful for migrating Linux-based workloads. * Fast Boot Times: Zephyr has a significantly faster startup time in RTL simulations (~20K cycles for RocketConfig). * Small Footprint: Zephyr ELF files are 20-100KB (excluding application code), suitable for bringup and prototyping targets. * Simplified Virtual Memory: While Zephyr does not support full virtual memory like Linux, it does provide basic memory mapping and demand paging.
7.4.2. Installation and Basic Usage
Before beginning, ensure that your Chipyard RISCV toolchain is installed, and env.sh is sourced. Next, initialize the Zephyr submodule.
git submodule update --init software/zephyrproject/zephyr/
Next, run the following commands to initiaize the Zephyr workspace. west` is Zephyr’s CMake-based build tool and dependency manager, designed to streamline project setup, compilation, and firmware deployment. It automates building Zephyr applications into ELF binaries and manages multiple repositories and submodules. In Chipyard, west` is used to build ELF files for simulation on Spike, software RTL simulation, or FireSim
cd software/zephyrproject/zephyr
west init -l .
west config manifest.file west-riscv.yml
west update
Next, set the followwing environment variables:
export ZEPHYR_BASE=$(pwd)
export ZEPHYR_TOOLCHAIN_VARIANT=cross-compile
export CROSS_COMPILE=$RISCV/bin/riscv64-unknown-elf-
After initializing the workspace, you can build the Zephyr kernel and sample applications. For example, to build the hello_world sample application, run the following commands within the Zephyr directory:
west build -p -b spike_riscv64 ./samples/chipyard/hello_world
This will generate a build directory with the compiled ELF file, with example output below:
[0/128] Preparing syscall dependency handling
[128/128] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
RAM: 36868 B 256 MB 0.01%
IDT_LIST: 0 B 2 KB 0.00%
You can run the ELF file on Spike using the following command:
spike ./build/zephyr/zephyr.elf
This should print the following output:
*** Booting Zephyr OS build 6c1e6f64895b ***
Hello World! spike_riscv64/spike_virt_riscv64
To simulate the Zephyr application in RTL simulation, follow the instrutions in the Simulation Guide. Use the path to the Zephyr ELF file as the BINARY argument to the RTL simulator.
7.4.3. Zephyr Core Concepts
Below are useful concepts and terms to understand when working with Zephyr.
7.4.3.1. KConfig: Configuring Zephyr
Zephyr uses KConfig, a configuration system that allows developers to enable or disable features, select drivers, and tune system parameters. KConfig files are used to specify options that influence the build process.
Located in Kconfig files within the Zephyr source tree.
Used to enable hardware drivers (e.g., CONFIG_UART_HTIF=y for HTIF UART support).
Managed using the menuconfig or guiconfig tools.
Example: .. code-block:: kconfig
- config UART_HTIF
bool “Enable HTIF UART driver” select SERIAL_HAS_DRIVER depends on RISCV
- help
Enable the HTIF (Host-Target Interface) UART driver for RISC-V Spike simulation.
To modify configuration: .. code-block:: shell
west build -t menuconfig
This launches an interactive menu to configure Zephyr features.
7.4.3.2. Device Trees: Hardware Description
Zephyr uses Device Tree Source (DTS) files to describe hardware components, memory layouts, and peripherals in a structured manner.
Key components: - Board-level DTS files (e.g., spike_riscv64.dts) define enabled devices. - SoC-level DTS files (e.g., virt-riscv.dtsi) provide shared hardware descriptions. - Bindings map devices to their respective drivers.
7.4.3.3. Device Drivers: Enabling Hardware Support
Device drivers in Zephyr provide abstraction layers that interface with hardware components. Each driver is responsible for initialization, communication, and handling interrupts if applicable.
Drivers are located in: .. code-block:: shell
zephyr/drivers/<subsystem>/ # e.g., serial/
To register a driver: 1. Implement driver functions (e.g., poll_in, poll_out). 2. Define the DEVICE_DT_DEFINE() macro to initialize the driver. 3. Add the driver to CMakeLists.txt to be compiled when enabled in KConfig.
7.4.3.4. Driver Bindings: Connecting DTS to Drivers
Zephyr uses YAML bindings to map Device Tree nodes to their respective drivers. These bindings define required properties such as memory addresses, compatible strings, and configurations.
Adding a binding ensures that Zephyr correctly associates hardware definitions with driver implementations.
7.4.3.5. Zephyr Subsystems
Zephyr includes several subsystems for handling standard OS functionality, such as logging, input/output, and multi-threading.
7.4.3.5.1. Console: Standard Output Interface
The console subsystem provides a standard output interface for logging and debugging.
To enable a UART device as the console:
chosen {
zephyr,console = &htif;
};
Zephyr will automatically redirect printf-like output to the chosen console device.
7.4.4. Adding a New Zephyr Driver: HTIF UART
This tutorial guides you through the process of adding a Host-Target Interface (HTIF) UART driver to Zephyr. This driver enables serial output in Spike/FESVR simulations and can be used for debugging or system interaction. This driver has already been integrated; this guide provides an example of how to add a new driver to Zephyr.
7.4.4.1. Prerequisites
Before proceeding, ensure you have:
A working Zephyr workspace set up in Chipyard.
west installed and initialized.
Familiarity with Device Tree (DTS), CMake, and Zephyr driver configuration.
7.4.4.2. Define the HTIF UART in the Device Tree
To integrate HTIF as a serial device, update the Spike board’s Device Tree Source (DTS).
Edit boards/spike/riscv64/spike_riscv64.dts to enable HTIF:
/ {
chosen {
zephyr,console = &htif;
zephyr,shell-uart = &htif;
zephyr,sram = &ram0;
};
};
// Disable the default ns16550 UART
&uart0 {
status = "disabled";
};
&htif {
status = "okay";
};
In addition to enabling the HTIF device, this snippet sets the HTIF UART as the console and shell UART. The zephyr,console and zephyr,shell-uart properties specify the device node for the console and shell UART, respectively.
For the full file, refer to [spike_riscv64.dts](https://github.com/ucb-bar/zephyr/blob/chipyard-port/boards/spike/riscv64/spike_riscv64.dts).
The HTIF device itself is fully defined in dts/riscv/spike/virt-riscv.dtsi, which provides a generic definition for the RISC-V “virt” machine used in Spike. This file includes:
htif: uart {
compatible = "ucb,htif";
label = "HTIF_UART";
};
This defines the HTIF device as a UART-compatible peripheral, setting its compatible property to “ucb,htif”, which corresponds to the driver binding we will add later. The label property provides a human-readable name that can be referenced elsewhere in Zephyr’s configuration.
For the full file, see [virt-riscv.dtsi](https://github.com/ucb-bar/zephyr/blob/chipyard-port/dts/riscv/spike/virt-riscv.dtsi).
7.4.4.3. Define Device Tree Binding
Add a binding file to dts/bindings/serial/ucb,htif-uart.yaml:
# SPDX-License-Identifier: Apache-2.0
description: HTIF UART for Spike/FESVR
compatible: "ucb,htif"
include: base.yaml
properties:
label:
type: string
required: true
description: Human-readable string describing the device
This file defines the HTIF UART device as a serial device with a label property. The compatible property matches the device tree entry in virt-riscv.dtsi.
For the complete file, see [ucb,htif-uart.yaml](https://github.com/ucb-bar/zephyr/blob/chipyard-port/dts/bindings/serial/ucb,htif-uart.yaml).
7.4.4.4. Define HTIF Registers and Mutex in a Header
Create include/zephyr/drivers/htif.h to define HTIF constants and expose global variables:
#ifndef ZEPHYR_DRIVERS_HTIF_H
#define ZEPHYR_DRIVERS_HTIF_H
#include <stdint.h>
#include <zephyr/sys/mutex.h>
extern volatile uint64_t tohost;
extern volatile uint64_t fromhost;
extern struct k_mutex htif_lock;
#endif // ZEPHYR_DRIVERS_HTIF_H
For the complete header, see [htif.h](https://github.com/ucb-bar/zephyr/blob/chipyard-port/include/zephyr/drivers/htif.h).
7.4.4.5. Implement the HTIF UART Driver
Create drivers/serial/uart_htif.c, implementing poll_in and poll_out based on OpenSBI logic.
Key functions: - `uart_htif_poll_out()`: Transmits a character via HTIF. - `uart_htif_poll_in()`: Reads a character via HTIF.
static void uart_htif_poll_out(const struct device *dev, unsigned char out_char) {
k_mutex_lock(&htif_lock, K_FOREVER);
htif_wait_for_ready();
tohost = TOHOST_CMD(HTIF_DEV_CONSOLE, HTIF_CONSOLE_CMD_PUTC, out_char);
k_mutex_unlock(&htif_lock);
}
static int uart_htif_poll_in(const struct device *dev, unsigned char *p_char) {
k_mutex_lock(&htif_lock, K_FOREVER);
htif_wait_for_ready();
tohost = TOHOST_CMD(HTIF_DEV_CONSOLE, HTIF_CONSOLE_CMD_GETC, 0);
while (fromhost == 0);
*p_char = (char)(FROMHOST_DATA(fromhost) & 0xFF);
fromhost = 0;
k_mutex_unlock(&htif_lock);
return 0;
}
Additionally, define the UART driver API and bind it to the HTIF device:
static const struct uart_driver_api uart_htif_driver_api = {
.poll_in = uart_htif_poll_in,
.poll_out = uart_htif_poll_out,
};
DEVICE_DT_DEFINE(DT_NODELABEL(htif), uart_htif_init, NULL, NULL, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&uart_htif_driver_api);
For the full implementation, see [uart_htif.c](https://github.com/ucb-bar/zephyr/blob/chipyard-port/drivers/serial/uart_htif.c).
7.4.4.6. Update the Linker Script
Ensure that tohost and fromhost are placed in a dedicated .htif section by modifying include/zephyr/arch/riscv/common/linker.ld:
.htif ALIGN(0x100) : {
KEEP(*(.htif))
}
For the full linker script, see [linker.ld](https://github.com/ucb-bar/zephyr/blob/chipyard-port/include/zephyr/arch/riscv/common/linker.ld).
7.4.4.7. Modify the CMake Build System
Zephyr’s build system needs to recognize the new driver. Update drivers/serial/CMakeLists.txt to include uart_htif.c when the CONFIG_UART_HTIF option is enabled:
zephyr_library_sources_ifdef(CONFIG_UART_HTIF uart_htif.c)
For the full file, see [CMakeLists.txt](https://github.com/ucb-bar/zephyr/blob/chipyard-port/drivers/serial/CMakeLists.txt).
7.4.4.8. Add Kconfig Configuration for HTIF
Define a new Kconfig entry for enabling HTIF. Modify drivers/serial/Kconfig:
rsource "Kconfig.htif"
Then, create a new Kconfig.htif file to define HTIF-specific options:
menuconfig UART_HTIF
bool "Enable HTIF UART driver"
select SERIAL_HAS_DRIVER
depends on RISCV
help
Enable the HTIF (Host-Target Interface) UART driver for RISC-V Spike simulation.
For the complete configuration, see [Kconfig.htif](https://github.com/ucb-bar/zephyr/blob/chipyard-port/drivers/serial/Kconfig.htif).
You will now be able to enable the HTIF UART driver when building Zephyr applications.