← Home / Reset Vector

QEMU virt Reset Vector

Everything that runs before your first instruction — from the 5-instruction MROM stub to register state on entry to your code.

CPU State at Power-On

When reset is released the RISC-V CPU is in a precisely defined state mandated by the privileged specification. None of this is QEMU-specific — every RISC-V implementation follows these rules.

Register / CSRReset ValueReason
PC0x1000MROM base — implementation-defined, QEMU virt uses 0x1000
PrivilegeM-modeHighest level, must configure lower modes before entering them
mstatus0x0000000A00000000SXL=64-bit, UXL=64-bit; MIE=0 (interrupts off)
misa0x8000000000141101MXL=10 → 64-bit; extensions I,M,A,C,S,U
mie0All M-mode interrupts disabled
satp0Virtual memory off — bare physical addressing
x0Always 0Hardwired zero by definition
x1–x31Undefined (QEMU zeros them)Spec does not require initialisation

QEMU virt Memory Map

AddressSizeDevice
0x000010000xF000MROM — reset vector lives here ←
0x020000000x10000CLINT (mtime @ 0x200BFF8, mtimecmp hart0 @ 0x2004000)
0x0C0000000x4000000PLIC
0x100000000x100UART0 (NS16550A) — write bytes here for terminal output
0x80000000up to 128 MBDRAM — your code runs here

Reset Vector Disassembly

QEMU writes this exact sequence into MROM before simulation starts. The instruction bytes are hardcoded in hw/riscv/virt.c; only the two data words are patched at runtime.

0x1000:  97 02 00 00  auipc  t0, 0          # t0 = 0x1000 (current PC)
0x1004:  13 86 82 02  addi   a1, t0, 40     # a1 = 0x1028 (DTB address)
0x1008:  73 25 40 f1  csrr   a0, mhartid    # a0 = hart ID (usually 0)
0x100c:  83 2b 82 01  ld     t0, 24(t0)     # t0 = *(0x1018) = kernel entry
0x1010:  67 80 02 00  jr     t0             # PC = 0x80000000
0x1014:  00 00 00 00  <padding>
0x1018:  .dword kernel_entry                  # written by QEMU from ELF e_entry
0x1020:  .dword fdt_addr                       # written by QEMU (top of RAM)
0x1028:  d0 0d fe ed ...   <DTB blob>         # Flattened Device Tree

After these 5 instructions execute, the register state on entry to your firmware is:

RegisterValueSet by
a00 (hart ID)csrr a0, mhartid — instruction 3
a1DTB address (~0x87E00000)addi a1, t0, 40 — instruction 2
t00x80000000ld t0, 24(t0) — instruction 4 (now trashed)
PC0x80000000jr t0 — instruction 5
PrivilegeM-modeCPU never changed privilege

32-bit Instructions ≠ 32-bit Mode

Every instruction in the reset vector is 4 bytes wide. It is natural to conclude the CPU is in 32-bit mode — but this is wrong. These are two completely independent properties.

PropertyWidthControlled by
Instruction encoding widthAlways 32-bit (base ISA)Fixed by RISC-V ISA spec — does not change with XLEN
Register width (XLEN)32 / 64 / 128-bitmisa.MXL — set to 64-bit at reset on QEMU virt
This is identical to x86-64: a modern 64-bit Intel CPU fetches instructions that may be 1–15 bytes wide, but its registers are 64-bit. Nobody says "x86-64 is in 1-byte mode" when they see a 1-byte NOP. Instruction width and register width are orthogonal.

Proof that the reset vector runs in 64-bit mode

The ld instruction at 0x100c loads 8 bytes (64 bits) from memory. This instruction does not exist in RV32 — executing it on a 32-bit core causes an Illegal Instruction fault. Its presence and successful execution is definitive proof of 64-bit mode.

InstructionLoadsRV32?RV64?
lw32 bits✅ Yes✅ Yes
ld64 bits❌ Illegal✅ Yes — proof of RV64

Decoding ld t0, 24(t0) → 0x0182b283

# I-type instruction format (all loads are I-type):
#  bits 31:20   19:15  14:12  11:7   6:0
#  imm[11:0]    rs1    funct3  rd    opcode

0x0182b283 in binary:
  000000011000  00101   011   00101  0000011
  ├──────────┤  ├───┤   ├─┤   ├───┤  ├─────┤
  imm = 24(0x18) rs1=t0  011   rd=t0  LOAD

funct3 = 011 = LD (doubleword)  # Only valid on RV64 — illegal on RV32
# Effect: t0 = Memory[t0 + 24] = Memory[0x1018] = 0x80000000

MISA CSR — Decoding 0x8000000000141101

# Read in GDB: -exec info registers misa
misa = 0x8000000000141101

  bits 63:62 = 10  → MXL = 2 → XLEN = 64-bit (RV64)
  bit  20    =  1  → U-mode supported
  bit  18    =  1  → S-mode supported
  bit  12    =  1  → M extension (multiply/divide)
  bit   8    =  1  → I base integer ISA
  bit   2    =  1  → C extension (compressed 16-bit instructions)
  bit   0    =  1  → A extension (atomics)

ISA string: RV64IMAC + User mode + Supervisor mode
# Matches: -march=rv64imac in the Makefile

mstatus Reset Value — 0x0000000A00000000

The lower 32 bits are all zero, which is why tools that show only 32-bit registers display 0x00000000. The meaningful bits are in the upper 32:

0x0000000A00000000

  bits 37:36 = 10  → SXL = 2 → S-mode XLEN = 64-bit
  bits 35:34 = 10  → UXL = 2 → U-mode XLEN = 64-bit
  bit  12:11 =  0  → MPP = 0 = U-mode (previous privilege)
  bit   3    =  0  → MIE = 0 → M-mode interrupts DISABLED at reset

# zsbl.S must explicitly set MIE before any interrupt can fire:
csrr  t0, mstatus
ori   t0, t0, (1 << 3)    # set MIE
csrw  mstatus, t0

-bios none vs Default QEMU

QEMU FlagsWhat runs between reset vector and your codePrivilege on entry
-bios none -kernel x.elfNothing — reset vector jumps directly to your ELF entryM-mode (Project 1)
-bios zsbl.elf -device loader,file=super.elfYour zsbl.S in M-mode, then mret to S-modeM→S-mode (Project 2)
(default) -kernel vmlinuxOpenSBI (built-in) in M-mode, then mret to S-mode for LinuxS-mode (Linux)

Inspect the Reset Vector in GDB

# Start QEMU paused, connect GDB, then:

-exec x/7i 0x1000              # disassemble 7 instructions from 0x1000
-exec x/2gx 0x1018             # show kernel_entry and fdt_addr data words
-exec x/4xb 0x1028             # verify DTB magic: 0xd0 0x0d 0xfe 0xed

# Step through manually:
-exec stepi                    # auipc  → t0 = 0x1000
-exec info registers t0 a0 a1
-exec stepi                    # addi   → a1 = 0x1028
-exec stepi                    # csrr   → a0 = 0
-exec stepi                    # ld     → t0 = 0x80000000
-exec stepi                    # jr     → PC = 0x80000000

# Now load symbols and debug your code:
-exec file main.elf
-exec break _start
-exec continue

Common Misconceptions

MisconceptionVerdictReality
Instructions are 32-bit wide → CPU is in 32-bit modeFALSEInstruction encoding is always 32-bit in base ISA. XLEN=64 is set by misa.MXL=10.
CPU starts at address 0x0FALSECPU starts at 0x1000 (MROM base). Address 0x0 is DEBUG region — not executable.
-bios none means no firmware runsMISLEADINGThe 5-instruction MROM stub still runs. -bios none skips OpenSBI only.
mstatus=0 at reset — CPU unconfiguredMISLEADINGLower 32 bits are 0 but SXL=UXL=10 in bits 37:34 confirm 64-bit for all modes.
mtime runs at 10 MHz (per DTB)FALSEDTB reports 10 MHz but QEMU mtime actually ticks at 1 MHz. Measure empirically.
a0 and a1 are set by your _start codeFALSESet by the QEMU reset vector (csrr + addi) before your code runs. They arrive pre-populated.
MROM reset vector can be modifiedFALSEMROM is read-only. Writing to 0x1000 causes a store access fault.
satp=0 means the MMU is missingTRUE BUTsatp=0 means virtual memory is disabled, using physical addressing. MMU is present but off. Correct for M-mode firmware.