Skip to content

[PROPOSAL] M-Mode ACT4 Firmware Runner & Hardware Validation - VisionFive 2 + Milk-V Jupiter #1482

@Jjateen

Description

@Jjateen

M-Mode ACT4 Firmware Runner & Hardware Validation - VisionFive 2 + Milk-V Jupiter

Developing a minimal, reusable M-mode firmware runner to execute ACT4 ELF binaries on real RISC-V silicon with full privilege access, UART-based result reporting, and an automated multi-board compliance pipeline.


Overview

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart TD
    subgraph Boards["Target Hardware"]
        VF2["StarFive VisionFive 2\nJH7110 · RV64GC"]
        MV["Milk-V Jupiter\nSpacemiT K1 · RV64GCB+V"]
    end

    subgraph Dev["CI / Development"]
        Q["QEMU virt-rv64\nM-mode proxy"]
    end

    subgraph Runner["M-Mode Firmware Runner"]
        direction LR
        ELF["ELF64 Loader"] --> TRAP["Trap Handler\nmtvec · PMP · FPU"] --> TH["tohost Monitor"]
    end

    subgraph Host["Host Tooling"]
        direction LR
        CAP["uart_capture.py"] --> SUITE["run_act_suite.sh"] --> RPT["generate_report.py"]
    end

    Boards --> Runner
    Dev --> Runner
    Runner --> Host
    Host --> OUT["riscof-compatible\nCompliance Report"]
Loading

The Problem

The ACT4 framework has excellent simulation coverage (Spike, QEMU, Sail, RTL). What is missing is a path to run M-mode compliance tests on real Linux-capable RISC-V boards. The root cause is structural - not a config gap but a boot chain conflict.

Boot Chain Conflict

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart LR
    ROM["ROM / ZSBL"]
    SPL["SPL\nM-mode"]
    OSBI["OpenSBI\nM-mode claimed here"]
    UB["U-Boot\nS-mode only"]
    LIN["Linux"]
    ACT["ACT ELF via 'go'\nRuns in S-mode\nM-mode CSRs inaccessible"]

    ROM --> SPL --> OSBI --> UB --> LIN
    UB -->|"go 0xaddr"| ACT
Loading

By the time U-Boot runs, OpenSBI has already claimed M-mode. Any binary launched from U-Boot runs in S-mode. ACT tests that rely on rvtest_mtrap_routine, PMP configuration, mstatus, mie, mtvec, or FPU state will either trap incorrectly or return wrong CSR values. There is no workaround without entering the boot chain above OpenSBI.

Problem Inventory

# Problem Impact
1 OpenSBI claims M-mode before any user binary runs All M-mode ACT tests untestable on hardware
2 No M-mode ELF loader exists for these SoCs ACT ELFs cannot be placed and executed at correct privilege
3 tohost has no host-side monitor on real hardware PASS/FAIL unobservable - simulation-only contract
4 No config/cores/ entry enables M-mode on any Linux-capable SBC No community reference for hardware M-mode testing
5 Milk-V Jupiter B/V extensions have zero hardware ACT coverage Zba, Zbb, Zbs, RVV 1.0 tests never validated on silicon
6 No multi-test automation or report generation for hardware runs RVI20 suite is impractical to execute and report without tooling

Proposed Solution

Boot Chain Redesign for ACT Runs

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart TD
    subgraph Normal["Production Boot (unchanged)"]
        direction LR
        N1["ROM"] --> N2["SPL"] --> N3["OpenSBI"] --> N4["U-Boot"] --> N5["Linux"]
    end

    subgraph ACTBoot["ACT Test Boot - OpenSBI Domain (primary)"]
        direction LR
        A1["ROM"] --> A2["SPL"] --> A3["OpenSBI +\nDomain Config"]
        A3 -->|"S-mode domain"| A4["U-Boot\npre-loads ACT ELF\nvia tftpboot / fatload"]
        A3 -->|"M-mode domain\nnext-mode = 0x3"| A5["act_mmode_runner\n0x47000000"]
        A5 --> A6["ACT ELF\n0x47200000\nFull M-mode"]
        A6 --> A7["UART\nRVCP-SUMMARY"]
    end

    subgraph Fallback["fw_payload Fallback - QEMU / clean env"]
        direction LR
        F1["ROM"] --> F2["SPL"] --> F3["fw_payload.bin\nrunner = firmware"] --> F4["ACT ELF\nM-mode"] --> F5["HTIF tohost\n+ UART"]
    end
Loading

The OpenSBI domain approach (primary) keeps the production boot chain intact with no reflashing required. An opensbi_domain.dts fragment carves a reserved DRAM region and grants it M-mode privilege. U-Boot pre-loads the ACT ELF into that region via tftpboot; the runner picks it up at a known address.

The fw_payload approach (fallback) replaces OpenSBI entirely for test-only boot scenarios. Used for QEMU and fully controlled environments where board reflashing is acceptable.


Architecture - M-Mode Runner

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart TD
    subgraph Entry["Entry - start.S"]
        direction TB
        S1["Set mtvec (vectored mode)"]
        S2["Set mstatus.MIE + mstatus.FS = Initial"]
        S3["Configure PMP\nRWX for test region + UART MMIO"]
        S4["Set up stack pointer"]
        S1 --> S2 --> S3 --> S4
    end

    subgraph Load["ELF Loading - elf_loader.c"]
        direction TB
        L1["Read Elf64_Ehdr\nValidate magic + e_machine = 0xF3"]
        L2["Iterate PT_LOAD segments"]
        L3["memcpy p_filesz bytes to p_paddr"]
        L4["Zero BSS region\np_memsz - p_filesz"]
        L5["Return e_entry\nrvtest_entry_point"]
        L1 --> L2 --> L3 --> L4 --> L5
    end

    subgraph Exec["Execution and Monitoring"]
        direction TB
        E1["jalr to entry_point"]
        E2["trap.c fires on any exception\nLog mcause, mepc, mtval over UART"]
        E3["tohost.c polls tohost\naddress in M-mode"]
        E4{"tohost value?"}
        E5["RVCP-SUMMARY: PASS"]
        E6["RVCP-SUMMARY: FAIL\n+ mcause dump"]
        E7["mcycle timeout\nconfigurable limit"]
        E1 --> E3 --> E4
        E1 -.->|"on trap"| E2
        E4 -->|"0x1"| E5
        E4 -->|"0x3"| E6
        E4 -->|"timeout"| E7
    end

    Entry --> Load --> Exec
Loading

Runner Components

The runner is a small, self-contained firmware written in C and RISC-V assembly with no standard library dependency. It is composed of six focused modules:

Module Responsibility
Assembly entry point Sets up the M-mode environment before any C code runs: trap vector, interrupt enable, FPU state, and stack
ELF64 loader Parses the test binary, copies each loadable segment to its correct physical address in DRAM, and returns the entry point
Trap handler Catches any exception the test generates, logs the cause and faulting address over UART, and recovers to continue the run
Result reporter Writes to the tohost address and simultaneously prints a RVCP-SUMMARY: line over UART so results are visible from both channels
PMP configurator Grants read/write/execute access to the test memory region and the UART MMIO range before handing off to the test
Board UART drivers Separate drivers for the NS16550 (VisionFive 2) and 8250-compatible (Milk-V Jupiter) controllers; selected at compile time

Board-specific linker scripts place the runner itself at a fixed DRAM address and reserve the region above it for the incoming test binary, with no overlap with OpenSBI or U-Boot memory regions.


Deliverables

Board 1 - StarFive VisionFive 2 (JH7110, RV64GC)

Parameter Value Source
DRAM base 0x40000000 JH7110 TRM
UART NS16550 @ 0x10000000 JH7110 TRM
PMP grain 4 bytes JH7110 TRM
Runner base 0x47000000 Above OpenSBI + U-Boot
ELF base 0x47200000 Above runner
Boot chain SPL + OpenSBI + U-Boot Standard JH7110

Files under config/cores/starfive/visionfive2/:

  • rvmodel_macros.h - RVMODEL_BOOT initialises NS16550 UART and M-mode runner handshake; RVMODEL_HALT_PASS / RVMODEL_HALT_FAIL write tohost and print RVCP-SUMMARY: over UART simultaneously
  • link.ld - ELF base 0x47200000, above runner and OpenSBI/U-Boot regions
  • test_config.yaml - include_priv_tests: true, profile rvi20u64
  • rvtest_config.h - RV64GC + Zba/Zbb/Zbs + Sv39/48 flags
  • visionfive2-rv64gc.yaml - Full UDB description with JH7110 memory map, PMP grain = 4
  • sail.json - Sail reference config (DRAM 0x40000000, UART 0x10000000)
  • opensbi_domain.dts - Domain config fragment granting M-mode to the runner region
  • uboot_load.sh - tftpboot + domain trigger + UART capture scripted flow
  • README.md - Full bring-up walkthrough: toolchain, build, U-Boot flow, UART, result parsing

Board 2 - Milk-V Jupiter (SpacemiT K1, RV64GCB+V)

The K1 is the only widely available SBC with B and V extensions, making it the first hardware target for Zba/Zbb/Zbs and RVV 1.0 compliance tests that currently have no silicon coverage in ACT4.

Parameter Value Source
DRAM base 0x00000000 SpacemiT K1 Datasheet
UART 8250 @ 0x0D090000 SpacemiT K1 Datasheet
Extensions RV64GC + Zba/Zbb/Zbs + RVV 1.0 K1 ISA spec
Runner base 0x07000000 Above OpenSBI region
ELF base 0x07200000 Above runner

Files under config/cores/milkv/jupiter/:

  • rvmodel_macros.h - 8250 UART init; mstatus.VS = Initial set before V-extension tests
  • link.ld - Runner 0x07000000, ELF 0x07200000
  • test_config.yaml - include_priv_tests: true; profile rvi20u64 + Zba/Zbb/Zbs; V-extension tests staged separately
  • rvtest_config.h - RVMODEL_ZBA, RVMODEL_ZBB, RVMODEL_ZBS, RVMODEL_V flags
  • milkv-jupiter-rv64gcbv.yaml - UDB description with K1 UART, CLINT, PLIC, memory map
  • README.md - K1 bring-up notes; vtype/vl CSR handling in trap handler

QEMU CI Proxy - config/cores/qemu/virt-rv64/

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart LR
    GH["GitHub Actions\nCI trigger"]

    subgraph Run["QEMU virt M-mode Run"]
        direction LR
        QC["QEMU virt machine\nfw_payload boot + ACT ELF"]
        HTIF["HTIF tohost\nnative QEMU support"]
        QC --> HTIF
    end

    RPT2["Compliance report\nas CI artifact"]

    GH --> Run --> RPT2
Loading

Enables full M-mode ACT runs in CI without physical hardware. Mirrors the VisionFive 2 memory layout for parity. A GitHub Actions workflow YAML is included.


Host-Side Tooling (tools/)

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart TD
    BOARD["Board UART\n/dev/ttyUSB0"]

    subgraph Loop["run_act_suite.sh"]
        direction TB
        FOR["for each ELF in rvi20u64 profile"]
        LOAD["U-Boot: tftpboot ELF to DRAM"]
        TRIG["Trigger domain / go addr"]
        FOR --> LOAD --> TRIG --> FOR
    end

    subgraph Capture["uart_capture.py"]
        direction TB
        RECV["Read UART until RVCP-SUMMARY line"]
        PARSE["Parse result + mcause + cycle count"]
        JSON["Emit structured JSON per test"]
        RECV --> PARSE --> JSON
    end

    subgraph Report["generate_report.py"]
        direction TB
        AGG["Aggregate all JSON results"]
        MD["Markdown summary table"]
        RISCOF["riscof-compatible report"]
        EXIT["exit 0 all pass / exit 1 any fail"]
        AGG --> MD --> RISCOF --> EXIT
    end

    BOARD --> Capture
    Loop --> Capture
    Capture --> Report
Loading

For each test, uart_capture.py records the board name, test name, pass or fail result, any trap cause code observed during execution, and the cycle count. This structured output is what generate_report.py consumes to produce the final compliance report.


tohost Strategy - Resolved

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
flowchart LR
    TEST["ACT ELF\nwrites tohost"]

    CH1["Channel A\ntohost DRAM address\nHost reads via /dev/mem"]
    CH2["Channel B\nUART RVCP-SUMMARY\nuart_capture.py"]

    CHECK["Cross-check\nBoth channels must agree"]

    TEST --> CH1 --> CHECK
    TEST --> CH2 --> CHECK
Loading

UART is the primary channel (works on all boards, no Linux required). The /dev/mem channel is a secondary cross-check available when Linux is running on the same board. Both are implemented in uart_capture.py.


Implementation Timeline

%%{init: {"theme": "base", "themeVariables": {"darkMode": true, "primaryColor": "#1e3558", "primaryTextColor": "#e2eeff", "primaryBorderColor": "#3d6aad", "secondaryColor": "#0d1f3c", "tertiaryColor": "#162b4d", "lineColor": "#5b8dd9", "edgeLabelBackground": "#0d1f3c", "clusterBkg": "#0d1f3c", "titleColor": "#93b4e8"}}}%%
gantt
    title 12-Week Implementation Plan
    dateFormat  YYYY-MM-DD
    axisFormat  W%W

    section Phase 1 - Foundation
    ACT4 framework study + riscof flow      :2026-06-01, 7d
    QEMU bare-metal M-mode baseline         :2026-06-08, 7d

    section Phase 2 - M-Mode Runner
    start.S + elf_loader + trap + UART      :2026-06-15, 14d
    tohost integration + QEMU virt config   :2026-06-29, 7d

    section Phase 3 - Hardware Ports
    VisionFive 2 port + OpenSBI domain      :2026-07-06, 14d
    VisionFive 2 full rvi20u64 validation   :2026-07-20, 7d
    Milk-V Jupiter port + B/V extensions    :2026-07-27, 14d

    section Phase 4 - Tooling and Docs
    uart_capture + run_suite + report gen   :2026-08-10, 7d
    Boot flow refactor integration          :2026-08-17, 7d
    Documentation + upstream PR             :2026-08-24, 7d
Loading

Open Questions for Maintainers

  1. OpenSBI domain DTS integration - should opensbi_domain.dts ship as a patch against the vendor OpenSBI tree or as a standalone overlay users apply manually? Is there a preferred pattern across existing config/ entries?

  2. rvi20u64 profile declaration in test_config.yaml - following @karabambus's suggestion, should the profile be declared explicitly in the YAML, or is it intended to be passed only at the riscof invocation layer?

  3. V-extension test readiness on Milk-V Jupiter - the K1's RVV 1.0 support makes it a strong first hardware target for vector compliance. Is there an existing or planned V-extension test suite in ACT4, or should a minimal set be scoped into this work?

  4. Boot flow refactor coordination - @jordancarlin mentioned the refactor completing soon. Is there a tracking issue or branch to follow so the runner can be designed with the new flow in mind from the start?

  5. PMP grain confirmation for JH7110 - from the TRM, pmpgranularity = 4. Can a maintainer with hardware access confirm this, or should the runner detect it dynamically via a pmpcfg probe at boot?


Hardware Reference

Board SoC ISA Boot Chain UART DRAM Base
StarFive VisionFive 2 JH7110 RV64GC SPL + OpenSBI + U-Boot NS16550 @ 0x10000000 0x40000000
Milk-V Jupiter SpacemiT K1 RV64GCB_Zba_Zbb_Zbs_V SPL + OpenSBI + U-Boot 8250 @ 0x0D090000 0x00000000

Boards will be arranged by mentors upon selection. All development prior to hardware access uses QEMU virt with the fw_payload boot path.


Related Issues


I'll track the boot flow refactor branch and design the runner to adapt cleanly once it lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions