Skip to content

[QBox/RISC-V] Does QBOX RISCV64 cpu instance support vector extension? #31

@donghyun-kang-dn

Description

@donghyun-kang-dn

Main Issue: illegal_instruction occurs when accessing vlenb CSR even after mstatus.VS is confirmed set

0. luafile for platform-vp


platform = {
    moduletype = "Container";
    quantum_ns = 1000000;

    -- Router for memory map
    -- log level
    -- 0: None
    -- 1: Error
    -- 2: Warning
    -- 3: Info
    -- 4: Debug
    -- 5: Verbose ?
    -- 6: Trace
    router = {
        moduletype = "router";
        log_level = 6; -- TRACE
    };

    -- QEMU Instance Manager
    qemu_inst_mgr = {
        moduletype = "QemuInstanceManager";
    };

    -- QEMU Instance (RISC-V64, single core)
    qemu_inst = {
        moduletype = "QemuInstance";
        args = {"&platform.qemu_inst_mgr", "RISCV64"};
        qemu_args = "-cpu rv64,v=on,vlen=512";
        tcg_mode = "MULTI";
        sync_policy = "multithread-unconstrained";
        log_level = 6;
    };

    -- Boot ROM at 0x0 - Bootrom that jumps to main firmware
    -- Reset signal
    reset = {
        moduletype = "sifive_test",
        args = {"&platform.qemu_inst"},
        target_socket = {address = 0x100000, size = 0x1000, bind = "&router.initiator_socket"};
        log_level = 6;
    };

    bootrom_0 = {
        moduletype = "gs_memory";
        target_socket = {address = 0x0, size = 0x2000, bind = "&router.initiator_socket"};
        shared_memory = false;
        reset = {bind = "&reset.reset"};
    };

    -- Main RAM at 0x80000000 - Main firmware
    ram_0 = {
        moduletype = "gs_memory";
        target_socket = {address = 0x80000000, size = 0x8000000, bind = "&router.initiator_socket"};
        shared_memory = true;
        reset = {bind = "&reset.reset"};
    };

    -- Loader module to load binaries
    loader = {
        moduletype = "loader";
        initiator_socket = {bind = "&router.target_socket"};
        -- {bin_file = bootrom_path, address = 0x0};
        {bin_file = bootrom_path, address = 0x1000};
        {bin_file = fw_path, address = 0x80000000};
        reset = {bind = "&reset.reset"};
    };

    gpex_0 = {
        moduletype = "qemu_gpex";
        args = {"&platform.qemu_inst"};
        bus_master = {bind = "&router.target_socket"};
        pio_iface = { address = 0x3000000, size = 0x10000, bind= "&router.initiator_socket"};
        mmio_iface = { address = 0x40000000, size = 0x40000000, bind= "&router.initiator_socket" };
        ecam_iface = { address = 0x30000000, size = 0x10000000, bind= "&router.initiator_socket" };
        mmio_iface_high = { address = 0x400000000, size = 0x400000000, bind= "&router.initiator_socket" },
        irq_out_0 = {bind = "&plic_0.irq_in_32"};
        irq_out_1 = {bind = "&plic_0.irq_in_33"};
        irq_out_2 = {bind = "&plic_0.irq_in_34"};
        irq_out_3 = {bind = "&plic_0.irq_in_35"};
    };

    global_peripheral_initiator_riscv_0 = {
        moduletype = "global_peripheral_initiator",
        args = {"&platform.qemu_inst", "&platform.cpu_0"},
        global_initiator = {bind = "&router.target_socket"},
    };

    -- PLIC (Platform Level Interrupt Controller)
    plic_0 = {
        moduletype = "plic_sifive",
        args = {"&platform.qemu_inst"},
        mem    = {address=0x0c000000, size=0x600000, bind = "&router.initiator_socket"},
        num_sources = 96,
        num_priorities = 7,
        priority_base = 0x0,
        pending_base = 0x1000,
        enable_base = 0x2000,
        enable_stride = 0x80,
        context_base =  0x200000,
        context_stride = 0x1000,
        aperture_size = 0x600000,
        hart_config = "MS";
    };

    -- CLINT (Core Local Interruptor) - Timer
    -- clint_0 = {
    --     moduletype = "riscv_aclint_mtimer";
    --     args = {"&platform.qemu_inst"};
    --     mem = {address = 0x2000000, size = 0x10000, bind = "&router.initiator_socket"};
    --     timecmp_base = 0x0;
    --     time_base = 0xbff8;
    --     provide_rdtime = true;
    --     aperture_size = 0x10000;
    --     num_harts = 1;
    -- };

    -- UART0 (16550 compatible)
    uart_0 =  {
        moduletype = "uart_16550",
        args = {"&platform.qemu_inst"},
        mem = {address= 0x10000000, size=0x100, bind = "&router.initiator_socket"},
        irq_out={bind = "&plic_0.irq_in_10"},
        regshift=2,
        baudbase=3686400;
    };


    -- RTC (Real Time Clock) - removed for simplicity
    -- rtc_0 = {
    --     moduletype = "goldfish_rtc";
    --     args = {"&platform.qemu_inst"};
    --     mem = {address = 0x101000, size = 0x1000, bind = "&router.initiator_socket"};
    --     irq = {bind = "&cpu_0.irq_in_1"};
    -- };

    -- Flash memory (4MB)
    flash_0 = {
        moduletype = "gs_memory";
        target_socket = {address = 0x20000000, size = 0x400000, bind = "&router.initiator_socket"};
        shared_memory = false;
    };

    -- RISC-V64 CPU
    cpu_0 = {
        moduletype = "cpu_riscv64";
        args = {"&platform.qemu_inst", 0};
        mem = {bind = "&router.target_socket"};
        reset = {bind = "&reset.reset"};
    };

}

1. Summary

We are encountering an illegal_instruction (cause 2) exception when trying to initialize the RISC-V 'V' (Vector) extension in our firmware running on the QEMU/QBox platform.

The core of the problem is a contradiction:

  1. Our firmware successfully sets the mstatus.VS bits to 'Initial' (0b01).
  2. Our firmware successfully reads back mstatus and confirms the VS bits are set.
  3. Despite this, the very next attempt to read a vector CSR (vlenb) immediately fails with an illegal_instruction exception.

2. Firmware Initialization Logic

Our firmware follows the standard procedure to enable the vector unit:

  1. It first checks misa (via rvv_is_available()) to confirm the 'V' extension is present. This check passes.
  2. It then reads mstatus, sets the FS bits (13-14) to 'Initial' (0b01), and writes it back. This succeeds.
  3. It then reads mstatus again, sets the VS bits (9-10) to 'Initial' (0b01), and writes it back. This also succeeds.
  4. We added a verification step in our C code to double-check the VS bits immediately after setting them:
    // Verify VS field is set
    mstatus = read_csr(CSR_MSTATUS);
    if ((mstatus & MSTATUS_VS_MASK) == MSTATUS_VS_INITIAL) {
        // OK
    } else {
        return RVV_INIT_FAILED; // Failsafe
    }
    This check passes, confirming that the CPU reports mstatus.VS as 'Initial'.

3. The Crash

Immediately after this verification passes, the firmware attempts to read vlenb:

// This C code is executed:
uint32_t vlenb = read_csr(CSR_VLENB);

This compiles to the following assembly instruction:

0x80000774:  c22027f3   csrrs a5,vlenb,zero

When this instruction executes, we get an illegal_instruction trap.

4. Confusing Log Entry

Oddly, the trap log we captured shows an epc that does not match the vlenb instruction itself, which is confusing. This may be a separate issue, but it's the log we see when the vlenb read is attempted.

riscv_cpu_do_interrupt: hart:0, async:0, cause:0000000000000002, epc:0x0000000080001168, tval:0x00000000cc07f057, desc=illegal_instruction

5. Questions and Hypothesis

Main Question: Why would an access to vlenb (or any vector CSR) cause an illegal_instruction after the mstatus.VS bits have been successfully set and verified as 'Initial' (0b01)?

Hypothesis: We suspect this might be a libQEMU configuration issue rather than a firmware logic error. Is it possible that the QEMU instance is being launched without the v=true flag (e.g., -cpu rv64,v=true)?

  • I found that the same firmware binary works on the standard QEMU (10.1), virt with --kernel loader. So I think it's weird..
  • Do I have to build libqemu with additional options for this purpose?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions