← Back to Blog

FRISC OS: First Boot

January 20, 2026 friscosdevdevlog

The moment FRISC OS first booted on real RISC-V hardware — a journey from QEMU to physical silicon. After months of emulator testing, we finally lit up real LEDs.

◉ Target Board

HiFive Unmatched with SiFive U74 cores. Four 64-bit RISC-V cores at 1.2 GHz, 16GB DDR4, PCIe slot, NVMe support. Finally, Lateralus running on native RISC-V silicon.

The U74 is a "Linux-capable" core, meaning it has:

◉ Boot Sequence

Getting from power-on to shell takes several stages:

ZSBL → FSBL → OpenSBI → FRISC Bootloader → Kernel → Init → Shell
 ↓       ↓        ↓              ↓              ↓        ↓
ROM   SPI Flash  FW_JUMP     stage1.ltl    kernel.ltl  /init

ZSBL (Zero Stage Boot Loader): Hardwired in ROM. Loads FSBL from SPI flash.

FSBL (First Stage Boot Loader): Initializes DDR, loads OpenSBI from SD/NVMe.

OpenSBI: RISC-V Supervisor Binary Interface. Provides M-mode services to S-mode OS. We use FW_JUMP mode — it jumps directly to our bootloader.

FRISC Bootloader: Written in Lateralus, compiled to C. Loads kernel, sets up initial page tables, jumps to kernel_main.

◉ The Magic Moment

January 20, 2026, 2:47 AM. Serial console output:

OpenSBI v1.2
  ____                    _____ ____ ___
 / __ \                  / ____|  _ \_ _|
| |  | |_ __   ___ _ __ | (___ | |_) || |
| |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
| |__| | |_) |  __/ | | |____) | |_) || |
 \____/| .__/ \___|_| |_|_____/|____/___|
       | |
       |_|

Platform Name : SiFive HiFive Unmatched A00
...
[FRISC] Bootloader v0.1.0 starting
[FRISC] Found 16 GB RAM @ 0x80000000
[FRISC] Loading kernel from /boot/kernel.bin
[FRISC] Kernel loaded at 0x80200000 (487 KB)
[FRISC] Jumping to kernel_main...

 _____ ____  ___ ____   ____   ___  ____
|  ___|  _ \|_ _/ ___| / ___| / _ \/ ___|
| |_  | |_) || |\___ \| |    | | | \___ \
|  _| |  _ < | | ___) | |___ | |_| |___) |
|_|   |_| \_\___|____/ \____| \___/|____/

FRISC OS v0.1.0 on HiFive Unmatched
4 cores detected (U74 @ 1200 MHz)
Memory: 16384 MB
Initializing kernel heap... OK
Initializing interrupts... OK
Starting scheduler... OK
Mounting root filesystem... OK

Welcome to FRISC OS
frisc:/ $ 

We stared at that blinking cursor for a solid minute before anyone typed anything.

◉ What Broke First

Of course, it wasn't smooth. The journey to that boot included:

Timer interrupt frequency: QEMU's timer runs at 10 MHz. The U74's runs at 1 MHz. Our scheduler ran 10x too slow until we fixed the frequency detection.

Cache coherency: The U74 has non-coherent DMA. Forgot fence.i after self-modifying code? Enjoy stale instruction cache.

UART initialization: The SiFive UART needs explicit clock configuration that QEMU doesn't care about. Spent two days getting serial output working.

◉ Pipeline-Driven Boot

The kernel boot sequence is a Lateralus pipeline:

fn kernel_main() {
    serial_puts("[FRISC] Starting kernel subsystems")

    let boot_sequence = [
        ("memory", memory_init),
        ("interrupts", interrupts_init),
        ("scheduler", scheduler_init),
        ("filesystem", fs_init),
        ("network", network_init),
        ("devices", device_init),
    ]

    boot_sequence
        |> each(fn((name, init_fn)) {
            serial_puts("  Starting " + name + "...")
            let result = init_fn()
            match result {
                Ok(_) => serial_puts(" OK"),
                Err(e) => panic("Boot failed: " + name + ": " + e),
            }
        })

    serial_puts("Boot complete. Starting init.")
    spawn_process("/init")
}

Error handling flows naturally through the pipeline. If any subsystem fails, boot halts with a clear message.

◉ Performance Numbers

First measurements on real hardware:

Metric QEMU HiFive Unmatched
Cold boot to shell4.2s1.8s
Kernel image size487 KB487 KB
Process spawn12ms3ms
Context switch~1ms~50μs

Real hardware is fast. QEMU's full-system emulation adds substantial overhead.

◉ What's Next

With first boot working, we're focused on:

See the OS page for build instructions and latest progress.