RISC-V: A Modern Open ISA
September 8, 202636 min readintermediate
RISC-V originated at the University of California, Berkeley in 2010. A team led by Krste Asanović and David Patterson designed it as a clean-slate instruction set for teaching and research . Unlike ARM, which…
RISC-V originated at the University of California, Berkeley in 2010. A team led by Krste Asanović and David Patterson designed it as a clean-slate instruction set for teaching and research [1]. Unlike ARM, which is licensed from a single company, and unlike x86-64, which is controlled by Intel and AMD, RISC-V is a free and open standard governed by a nonprofit foundation, RISC-V International. Anyone can build a RISC-V processor without paying license fees, and anyone can extend the ISA with custom instructions without seeking permission. The openness is not just a legal arrangement. It is a design philosophy that pervades the ISA’s structure, from its modular extension scheme to its clean encoding that invites experimentation.
This chapter walks through the RISC-V ISA in detail. It begins with the base integer instruction set (RV32I and RV64I), examines each of the six major standard extensions, diagrams the instruction encoding formats, and closes with the privileged specification that governs exception handling and operating-system support. The presentation assumes the vocabulary of Chapter 13.
01.The Rationale for a Clean-Slate ISA
By 2010, every major ISA carried decades of accumulated legacy. x86-64 traced its roots to the 8086 of 1978 and still supported 16-bit real mode. ARM A64 was a clean 64-bit redesign, but ARM Holdings controlled it through a licensing model that charged per- chip royalties or substantial upfront fees. MIPS, SPARC, and PA-RISC had either faded commercially or been acquired.
The Berkeley team wanted an ISA that met four requirements. First, it had to be free of licensing restrictions so that students and researchers could build, modify, and publish implementations without legal barriers. Second, it had to be simple and regular so that a graduate student could build a working processor in a semester. Third, it had to be modular so that a minimal microcontroller and a high-performance server core could share the same base specification and differ only in which extensions they implemented. Fourth, it had to leave room for custom extensions so that domain-specific accelerators could add instructions without polluting the base encoding space.
The resulting design, documented in the unprivileged specification [2] and the privileged specification [3], has since moved from academia into industry. SiFive, Andes, Ventana, Tenstorrent, and dozens of other companies now ship commercial RISC-V silicon. The open-source ecosystem includes the Rocket Chip generator, the Berkeley Out-of-Order Machine (BOOM), the CVA6 core, and many others.
02.The Base Integer ISA: RV32I and RV64I
Every RISC-V processor implements either RV32I (32-bit) or RV64I (64-bit) as its mandatory base. This section walks through the base using RV64I as the primary example, noting differences where RV32I diverges.
Registers
RV64I provides 32 integer registers, x0 through x31, each 64 bits wide (32 bits in RV32I). Register x0 is hardwired to zero. Reading x0 always returns zero, and writing to x0 is silently discarded. This design choice is not unique to RISC-V. MIPS also hardwired register $0 to zero. The hardwired zero register simplifies the encoding of many common operations, as the worked examples in this chapter demonstrate. The program counter is a separate register, not accessible as a GPR. The base ISA does not define floating-point registers. Those are added by the F and D extensions.
The names the ABI gives them
The hardware calls the registers x0 through x31 and assigns no meaning beyond x0’s hardwired zero. Every other role is a software convention, written down in the ABI rather than in the hardware, and it is the ABI names that appear in compiler output and in almost all hand-written assembly.
The convention matters more than it first appears, because it partitions the register file by who is responsible for preserving a value across a call. A caller-saved register may be destroyed by any function you call, so if you need its value afterward you must save it yourself before the call. A callee-saved register must be preserved by any function that uses it, so you may leave a value there across a call and find it intact.
Figure 1 lays out all thirty-two.
That last point in the caption is the piece worth remembering, because it is a case of one extension reaching back into the base ABI. The compressed instructions of the C extension have only 3 bits for a register specifier, so they can name eight registers rather than thirty-two, and the eight they name are x8–x15. The ABI therefore places the values a compiler uses most often, the first six arguments and the two most frequently used saved registers, precisely in that window. Had the argument registers been assigned to x1–x8 in the obvious way, a large fraction of ordinary code would have fallen outside the compressible range and the C extension would have recovered far less.
Arithmetic and logical instructions
The base provides signed and unsigned addition, subtraction, and the logical operations AND, OR, and XOR. It also provides shift instructions (logical left, logical right, arithmetic right) and comparison instructions that write 1 or 0 to the destination register (SLT for signed less-than, SLTU for unsigned less-than). Multiplication and division are not in the base. They live in the M extension.
Each arithmetic instruction has two forms. The register form (ADD rd, rs1, rs2) takes two source registers. The immediate form (ADDI rd, rs1, imm) takes one source register and a 12-bit signed immediate. This pair of forms covers nearly all integer computation without requiring any memory access.
RISC-V also provides a SUB instruction for register- register subtraction but deliberately omits a SUBI immediate form. Subtracting an immediate is the same as adding its negation, so ADDI rd, rs1, -5 serves the purpose of SUBI rd, rs1, 5. This is a characteristic RISC-V design choice: do not add an instruction when an existing instruction already covers the operation at no extra cost.
A related simplification is the family of pseudo-instructions that the assembler recognizes and translates into base instructions. MV rd, rs is a pseudo-instruction for ADDI rd, rs, 0. NOP is ADDI x0, x0, 0. LI rd, imm is a pseudo-instruction that the assembler expands into one or two instructions depending on the immediate’s size. The pseudo-instruction layer lets assembly code read naturally without inflating the hardware’s instruction set.
Load and store instructions
RISC-V is a strict load-store architecture. The base provides byte, halfword (16-bit), word (32-bit), and doubleword (64-bit, RV64I only) loads and stores. Each load instruction specifies whether the loaded value is sign-extended or zero-extended to fill the full register width. The addressing mode is uniformly base plus a 12-bit signed offset. There are no indexed, scaled, or auto-increment modes in the base ISA. The simplicity of having a single addressing mode is a deliberate design choice: it keeps the address-generation unit minimal and predictable.
The load instructions are LB (load byte, sign-extend), LBU (load byte, zero-extend), LH (load halfword, sign-extend), LHU (load halfword, zero-extend), LW (load word, sign-extend in RV64I), LWU (load word, zero-extend, RV64I only), and LD (load doubleword, RV64I only). The store instructions are SB, SH, SW, and SD. Stores do not need sign-extension variants because they write the appropriate number of low-order bits from the source register to memory.
RISC-V specifies little-endian byte ordering as the default, with an optional big-endian mode that implementations may support. In practice, nearly all RISC-V hardware and software uses little-endian, matching the convention of ARM A64 and x86-64.
Control flow instructions
Conditional branches compare two registers and jump to a PC-relative target if the condition holds. The six branch conditions are equal (BEQ), not equal (BNE), less than signed (BLT), greater than or equal signed (BGE), less than unsigned (BLTU), and greater than or equal unsigned (BGEU). There is no condition-code register. The comparison is performed directly in the branch instruction, as discussed in Chapter 13.
Note the absence of “greater than” and “less than or equal” branch conditions. These are not needed because the opposite operand order provides the same test: BLT x5, x6, label tests whether x5 < x6, so testing x6 < x5 is equivalent to testing x5 > x6. The assembler provides pseudo-instructions BGT, BLE, BGTU, and BLEU that swap the operands automatically. This halves the number of branch encodings the hardware must recognize.
The unconditional jump JAL (jump and link) writes the return address (the address of the instruction following the JAL) to a destination register and jumps to a PC-relative target encoded as a 20-bit signed offset. By convention, a function call uses JAL x1, target, storing the return address in the link register x1 (also known as ra). A plain unconditional jump that does not need to save a return address uses JAL x0, target, discarding the return address into the hardwired zero register. The indirect jump JALR computes the target by adding a 12-bit signed offset to a base register. A function return is JALR x0, 0(x1): jump to the address in the link register, discard the “return address of the return” into x0. Together, JAL and JALR support function calls, returns, indirect jumps, and switch-table dispatch.
Upper-immediate instructions
Two instructions construct large constants. LUI (load upper immediate) places a 20-bit immediate into bits [31:12] of the destination register and zeros bits [11:0]. AUIPC (add upper immediate to PC) adds the same 20-bit immediate, shifted left by 12, to the current PC and writes the result to the destination register. A LUI + ADDI pair loads any 32-bit constant. An AUIPC + JALR pair reaches any address within a GiB range of the current PC, which is sufficient for position-independent code.
System instructions
The base defines ECALL (environment call, used for system calls), EBREAK (environment breakpoint, used by debuggers), and the FENCE instruction for memory ordering. It also defines the CSR access instructions (CSRRW, CSRRS, CSRRC, and their immediate variants), which read and write control and status registers.
03.Instruction Encoding Formats
Every 32-bit RISC-V instruction belongs to one of six formats: R, I, S, B, U, and J. The formats differ in how the 32 bits are allocated among the opcode, register specifiers, function codes, and immediate fields. A critical design principle is that the register specifiers always occupy the same bit positions regardless of format: rd is always at bits [11:7], rs1 at bits [19:15], and rs2 at bits [24:20]. This consistency lets the decoder start reading registers before it has fully determined the instruction type.
R-type: register-register operations
The R-type format carries no immediate. It encodes the opcode (7 bits), two source registers rs1 and rs2 (5 bits each), a destination register rd (5 bits), a 3-bit function code funct3, and a 7-bit function code funct7. All base arithmetic and logical instructions with two register operands use this format.
I-type: immediate operations and loads
The I-type format replaces rs2 and funct7 with a 12-bit signed immediate. It is used for arithmetic immediates (ADDI, ANDI, ORI), loads (LW, LD), and the JALR indirect jump.
S-type: stores
Store instructions need two source registers (rs1 for the base address and rs2 for the data to store) but no destination register. The S-type format splits the 12-bit immediate across two fields (bits [31:25] and bits [11:7]) to keep rs1 and rs2 in their standard positions.
B-type: conditional branches
The B-type format is a variant of S-type used for branches. The 12-bit immediate encodes a signed offset in multiples of 2 bytes (bit 0 of the target is always zero for aligned instructions), giving a branch range of KiB from the current PC. The immediate bits are shuffled so that the sign bit is always in bit 31 of the instruction word, which simplifies sign extension in hardware.
U-type: upper immediate
The U-type format carries a 20-bit immediate and a destination register. It is used by LUI and AUIPC.
J-type: unconditional jumps
The J-type format carries a 20-bit immediate (encoding a signed offset in multiples of 2 bytes, giving a range of MiB) and a destination register for the return address. It is used by JAL. The immediate bits are shuffled, like the B-type, to keep the sign bit at position 31.
04.Standard Extensions
The base integer ISA is deliberately minimal. A RISC-V implementation that supports only RV32I can execute any general-purpose program, but it will perform multiplication through a software routine and will have no hardware floating-point support. The standard extensions add capabilities in well-defined, independently testable groups.
M: integer multiply and divide
The M extension adds four multiply instructions (MUL, MULH, MULHSU, MULHU) and two divide-and-remainder pairs (DIV/REM for signed, DIVU/REMU for unsigned). MUL returns the lower half of the product. MULH and its variants return the upper half, which is needed for multi-word multiplication and for detecting overflow.
A: atomics
The A extension provides two mechanisms for atomic memory operations. The first is the load-reserved/store-conditional (LR/SC) pair. LR loads a value and places a reservation on the address. A subsequent SC to the same address succeeds (writes the value and returns zero in the destination register) only if no other core has written to that address since the reservation was placed. If another core wrote to the address, SC fails (writes nothing and returns a nonzero value). This pair is the foundation for lock-free algorithms and for implementing higher-level synchronization primitives such as compare-and-swap.
The second mechanism is a set of atomic memory operations (AMOADD, AMOSWAP, AMOAND, AMOOR, AMOXOR, AMOMAX, AMOMIN, and their unsigned variants) that atomically read a memory location, perform an operation, and write the result back. Each AMO instruction also takes acquire and release ordering annotations to control memory ordering.
F and D: floating point
The F extension adds 32 floating-point registers (f0 through f31), each 32 bits wide, and a full set of IEEE 754 single-precision arithmetic instructions: add, subtract, multiply, divide, square root, fused multiply-add, comparisons, conversions between integer and floating-point formats, and sign- injection operations. The D extension widens the floating-point registers to 64 bits and adds double-precision versions of all F-extension instructions. A processor that implements D necessarily implements F.
The floating-point control and status register fcsr holds the rounding mode and the accrued exception flags (invalid, divide-by-zero, overflow, underflow, inexact), following the IEEE 754 model described in Chapter 8.
RISC-V floating-point instructions use a 3-bit rm field in each instruction to select the rounding mode for that operation: round to nearest even, round toward zero, round down, round up, or round to nearest (ties away from zero). A special encoding (rm = 111) tells the hardware to use the dynamic rounding mode stored in fcsr. This per- instruction rounding-mode selection is unusual. ARM A64 and x86-64 both use a single global rounding mode stored in a control register, and changing the mode requires a write to that register. The RISC-V approach avoids the control-register write for operations that need a non-default rounding mode, eliminating a potential serialization point in the pipeline.
C: compressed instructions
The C extension defines 16-bit encodings for the most frequently used instructions. A compressed C.ADDI occupies 2 bytes instead of 4. A compressed C.LW occupies 2 bytes instead of 4. The compressed forms use a restricted set of registers (typically x8–x15, the eight callee-saved and argument registers most used in leaf functions) and narrower immediates. The hardware detects the instruction length by checking the lowest 2 bits of each fetch: if both bits are not 11, the instruction is 16 bits.
The C extension reduces code size by 25 to 30 percent on typical workloads, bringing RISC-V code density close to that of ARM Thumb-2 and x86-64. Since instruction-cache misses are a meaningful source of performance loss, smaller code translates directly into fewer cache misses and lower energy consumption.
V: vector operations
The V extension adds variable-length vector processing to RISC-V. It defines 32 vector registers (v0 through v31) whose width is implementation-defined. A processor that provides 128-bit vector registers executes a vector add on two elements at a time. A processor that provides 512-bit vector registers executes the same instruction on eight elements at a time. The same binary runs on both, because the vector length is not encoded in the instruction. Instead, a special VSETVLI instruction configures the number of elements per vector operation at run time.
This length-agnostic approach is the same philosophy that ARM’s SVE extension adopted. It avoids the binary-compatibility trap that plagued x86 SIMD extensions, where each new vector width (SSE at 128 bits, AVX at 256 bits, AVX-512 at 512 bits) required a new set of instructions and a recompile.
How a length-agnostic loop actually works
The claim that the same binary runs on hardware with different vector widths sounds like it should require magic. It does not. It requires that the loop ask the hardware how much work it is willing to do, and then do exactly that much, repeatedly, until nothing is left. The technique is called strip mining, and VSETVLI is the instruction that makes it possible.
Consider adding two arrays of elements. The loop body is four instructions:
| loop: | |
| vsetvli t0, a0, e32, m1 # t0 <- min(a0, VLMAX); set 32-bit elements | |
| vle32.v v0, (a1) # load t0 elements from x[] | |
| vle32.v v1, (a2) # load t0 elements from y[] | |
| vadd.vv v2, v0, v1 # add them | |
| vse32.v v2, (a3) # store t0 results | |
| sub a0, a0, t0 # n -= t0 | |
| slli t1, t0, 2 # t0 elements * 4 bytes | |
| add a1, a1, t1 # advance all three pointers | |
| add a2, a2, t1 | |
| add a3, a3, t1 | |
| bnez a0, loop |
The whole design rests on one line. VSETVLI takes the number of elements still remaining, in a0, and writes back into t0 the number the hardware will actually process this iteration, which is the smaller of what was requested and what the machine can do. Every subsequent vector instruction in the iteration operates on exactly t0 elements. The program never contains a vector width, and never needs to.
Figure 3 shows the same loop, the same binary, running on two machines whose vector registers differ by a factor of four.
The absence of a cleanup loop is the part worth dwelling on, because it is where the design pays off a second time. Fixed-width SIMD code has to handle a remainder: if the array holds ten elements and the registers hold four, the compiler emits a vector loop for the first eight and then scalar code for the last two, which is a second copy of the loop body with different instructions. That duplication is a standard source of bugs and a large fraction of the code size of hand-vectorized routines. Under strip mining, the short iteration executes exactly the same instructions as a full one.
The extension naming convention
A RISC-V implementation declares its capabilities through a string such as RV64IMAFDC. The letters are concatenated in a canonical order: base width first, then single-letter extensions in the order fixed by the specification, which runs I, M, A, F, D, Q, L, C, B and onward rather than alphabetically. The shorthand G (general-purpose) abbreviates the combination IMAFD. A processor labeled RV64GC implements the 64-bit base, integer multiply/ divide, atomics, single-precision float, double-precision float, and compressed instructions, which is the standard general-purpose configuration for application processors. This is the configuration that Linux distributions target by default, and it is the minimum for running a full operating system with standard C library support.
Beyond the single-letter extensions, RISC-V defines multi-letter extensions for specialized features. Zicsr provides the CSR access instructions (technically part of the base but given its own extension name for modularity). Zifencei provides the FENCE.I instruction for instruction-cache coherence. Zba, Zbb, and Zbs (the bit-manipulation extensions) add address-generation helpers, basic bit-manipulation operations (count leading/trailing zeros, population count, byte reverse), and single-bit instructions. These extensions fill gaps in the base ISA that compilers encounter frequently in real code.
Table 1. Standard RISC-V extensions and their functions.
| Letter | Name | Function |
|---|---|---|
| I | Base integer | Arithmetic, logic, loads, stores, branches |
| M | Multiply/divide | MUL, DIV, REM |
| A | Atomics | LR/SC, AMO operations |
| F | Single-precision FP | IEEE 754 single-precision arithmetic |
| D | Double-precision FP | IEEE 754 double-precision arithmetic |
| C | Compressed | 16-bit encodings for common instructions |
| V | Vector | Variable-length vector operations |
05.Custom Extensions and the Encoding Space
The RISC-V encoding reserves four opcode ranges (custom-0 through custom-3) for vendor-defined instructions. A company building a domain-specific accelerator (a neural-network inference engine, a cryptographic coprocessor, a signal-processing unit) can add instructions in these ranges without conflicting with any standard extension. The custom instructions share the same 32-bit encoding framework and the same register-specifier positions as the base, so existing decoder infrastructure can route them to the custom execution unit with minimal modification.
This extensibility is one of RISC-V’s most distinctive features relative to ARM and x86-64. ARM extensions must be proposed to and ratified by ARM Holdings. x86 extensions must be proposed by Intel or AMD and adopted by the other. RISC-V custom extensions require no external approval, which has made the ISA popular for research prototypes and for companies building specialized silicon.
The custom-extension mechanism has a practical constraint: the software toolchain must know about the custom instructions. GCC and LLVM both support RISC-V custom instructions through inline assembly or through intrinsics defined in a header file. For more systematic integration, RISC-V supports a .insn assembler directive that lets the programmer encode arbitrary instruction words by specifying the format, opcode, and operand fields directly. This directive bridges the gap between hardware experimentation and software usability, allowing researchers to test new instructions without modifying the compiler’s code-generation backend.
A second practical consideration is that custom extensions must be tested for correctness independently of the base ISA. The RISC-V compliance test suite, maintained by RISC-V International, covers the standard extensions. Custom extensions need their own test suites. The open-source Spike simulator and the QEMU RISC-V port both support pluggable custom-instruction modules, making it possible to simulate and debug custom extensions before committing to silicon.
The cost of an open encoding space
Extensibility has a bill attached, and a chapter that presents RISC-V’s modularity as an unqualified improvement over ARM and x86-64 would be leaving it out. The bill is fragmentation, and it falls on software rather than on hardware.
An x86-64 binary runs on any x86-64 processor. That is a strong guarantee, and it is strong precisely because no vendor may add an instruction without going through Intel or AMD, and because the baseline has moved only a handful of times in forty years. ARM occupies a middle position: the architecture is versioned, extensions are optional but centrally defined, and the set a given core implements is discoverable through system registers.
RISC-V deliberately gave up that guarantee. A binary compiled for RV64GC does not run on an RV64IMAC core, because it may contain floating-point instructions the core does not implement. The extension string is not decoration. It is the actual compatibility contract, and there are many valid strings.
Three consequences follow, and all three are visible in the ecosystem today.
Distributions must choose a baseline. Debian, Ubuntu and Fedora all settled on RV64GC as the minimum for their RISC-V ports, which excludes cores that omit the F, D or C extensions. That choice is what makes a general-purpose RISC-V distribution possible at all, and it means the low-end cores the modularity was designed to enable cannot run the distribution. The flexibility is real and the general-purpose software stack declines to use most of it.
Custom instructions do not survive a compiler. An accelerator reached through .insn directives or inline assembly is invisible to the optimizer, which cannot reorder around it, cannot allocate registers through it, and cannot prove anything about it. Getting a custom instruction properly integrated means patching the compiler backend, which is a much larger undertaking than adding the instruction to the hardware, and it must be redone against every toolchain release.
Two vendors can occupy the same encoding. The custom-0 through custom-3 ranges are reserved against the standard, not partitioned among vendors. Two companies may independently assign different meanings to the same word, and both are conforming. Nothing detects the collision except a binary that runs on the wrong chip.
06.The Privileged Specification
The unprivileged specification defines the instructions that application code uses. The privileged specification [3] defines the system-level architecture: privilege modes, exception handling, virtual memory, and hardware configuration.
Machine, supervisor, and user modes
RISC-V defines three privilege modes. Machine mode (M) is the most privileged. It has full access to all hardware resources and is the mode that firmware and bootloaders run in. Supervisor mode (S) is less privileged. It runs the operating system kernel and has access to virtual memory configuration and most system registers, but certain operations (like configuring the physical memory protection unit) are restricted to M-mode. User mode (U) is the least privileged and runs application code. A simple embedded controller may implement only M-mode. A system running Linux needs all three.
An optional hypervisor extension (H) adds virtualized privilege modes for virtual-machine monitors. The H-extension allows a hypervisor running in HS-mode (hypervisor-extended supervisor mode) to manage guest operating systems running in VS-mode (virtual supervisor mode) and guest applications running in VU-mode (virtual user mode).
Control and status registers (CSRs)
Each privilege mode has its own set of CSRs. The most important are:
-
mstatus/sstatus: global interrupt enable, privilege-mode stack, and other status bits. -
mtvec/stvec: the trap-vector base address, which is the entry point for exception handlers. -
mepc/sepc: the exception program counter, holding the address of the instruction that caused the trap or that was interrupted. -
mcause/scause: the cause code that identifies the exception type. -
mtval/stval: additional exception information (for example, the faulting virtual address on a page fault). -
mie/sieandmip/sip: interrupt-enable and interrupt-pending registers.
What a trap actually does
The CSR list above names the pieces. What connects them is a short, fixed sequence that the hardware performs atomically when a trap fires, and knowing that sequence is what makes the registers stop looking arbitrary. Every one of them exists because the sequence needs somewhere to put something.
When a trap is taken into machine mode, the hardware does six things, in this order and with no instruction boundary in the middle. It writes the address of the faulting or interrupted instruction into mepc. It writes a code identifying what happened into mcause. It writes any auxiliary detail, such as the faulting address, into mtval. It copies the current interrupt-enable bit MIE into MPIE and then clears MIE, so the handler starts with interrupts off. It records the privilege mode that was interrupted in MPP. And it sets the program counter to the address in mtvec.
Figure 4 draws the round trip.
Returning is the same sequence run backwards, and MRET is the single instruction that does it: the program counter is restored from mepc, MIE is restored from MPIE, and the privilege level is restored from MPP.
Two properties of this design are worth naming, because both are deliberate choices that other architectures made differently.
The hardware saves no general-purpose registers. On entry the handler is running with the interrupted program’s registers still live, and it must save whatever it intends to use. Some architectures provide a shadow bank that is swapped in automatically. RISC-V does not, which makes trap entry cheaper for the machine and puts the cost on the handler. The mscratch CSR exists exactly to bootstrap out of this: it holds a pointer the handler can swap with a register to obtain a stack to save onto without having destroyed anything first.
Interrupts are disabled on entry and the enable bit is stacked, not saved to memory. MPIE is a single-entry stack. That is sufficient because nesting is prevented by the disable, and a handler that wants to permit nested interrupts must save mepc and mstatus itself before re-enabling. Getting that wrong, by re-enabling interrupts before saving mepc, destroys the return address of the outer trap and is one of the classic bugs in a hand-written RISC-V trap handler.
Physical memory protection (PMP)
Machine mode can configure a set of PMP (physical memory protection) entries that restrict the physical addresses that supervisor and user mode can access. Each PMP entry specifies a range of physical addresses and a set of permissions (read, write, execute). The PMP mechanism is the foundation for memory isolation on systems that do not implement virtual memory (for example, embedded microcontrollers running a real-time operating system).
Virtual memory
When the S-mode is present, RISC-V supports paged virtual memory through the Sv32 (32-bit, two-level page table), Sv39 (39-bit, three-level), Sv48 (48-bit, four-level), and Sv57 (57-bit, five-level) translation schemes. The page size is 4 KiB, with support for superpages at each intermediate level. The page table entry format includes present, read, write, execute, user, global, accessed, and dirty bits. The active translation mode is selected by writing to the satp CSR.
The Sv39 scheme is the most commonly implemented. It provides a 39-bit virtual address space (512 GiB), which is sufficient for most application workloads. The three-level page table uses 9 bits of virtual address per level, with a 12-bit page offset. Each page table is exactly one 4 KiB page (512 entries of 8 bytes each). The hardware page-table walker reads the satp register to find the root page table’s physical address, then walks three levels to produce the final physical address. A translation lookaside buffer (TLB) caches recent translations to avoid the full walk on most accesses. The SFENCE.VMA instruction flushes TLB entries when the operating system modifies the page tables.
Timer and performance counters
The privileged specification defines a real-time counter (mtime and mtimecmp) for timer interrupts and three read-only counters accessible from user mode: cycle (clock cycles elapsed), time (wall-clock time), and instret (instructions retired). These counters give user-mode code a lightweight mechanism for benchmarking and time-stamping without requiring a system call. Machine mode can disable user-mode access to these counters through the mcounteren CSR if the security policy requires it.
07.RISC-V Compared with ARM and x86-64
The table below places RISC-V alongside ARM A64 and x86-64 on the key dimensions introduced in Chapter 13.
Table 2. RISC-V compared with ARM A64 and x86-64 on major ISA dimensions.
| Dimension | RISC-V (RV64GC) | ARM A64 | x86-64 |
|---|---|---|---|
| Encoding width | 32-bit + 16-bit C | 32-bit | 1–15 bytes |
| Operand model | 3-register GPR | 3-register GPR | 2-operand reg-mem |
| Memory model | Load-store | Load-store | Register-memory |
| Integer registers | 32 | 31 + XZR/SP | 16 (32 with APX) |
| FP registers | 32 (F/D ext.) | 32 | 16 XMM/YMM/ZMM |
| Condition codes | None | NZCV flags | EFLAGS |
| Memory ordering | Relaxed (RVWMO) | Relaxed | TSO |
| Privilege modes | M/S/U (+ H) | EL0–EL3 | Ring 0–3 |
| Licensing | Free, open | Licensed | Proprietary |
The comparison reveals RISC-V’s minimalism. Fewer addressing modes, no condition codes, no register-memory arithmetic. The cost is occasionally higher instruction count. The benefit is decoder simplicity, a smaller verification surface, and unencumbered extensibility. Whether that trade-off favors RISC-V over ARM or x86-64 depends on the application domain. For embedded and education, the simplicity is a strong advantage. For high- performance server workloads, the mature toolchains and microarchitectural investment behind ARM and x86-64 remain formidable.
One dimension that the table does not capture is verification complexity. An ISA with fewer instructions, fewer addressing modes, and fewer implicit side effects (no flag register, no register-memory arithmetic) has a smaller state space for formal verification and testing. The RISC-V designers explicitly cite verification simplicity as a design goal [2]. A clean-slate ISA with 47 base instructions and well-separated extensions is easier to verify exhaustively than an ISA with over a thousand encodings and decades of accumulated corner cases. This matters for safety- critical applications (automotive, medical, aerospace) where functional correctness of the processor must be demonstrated to a certification authority.
Another dimension is the ecosystem of open-source implementations. Because RISC-V is free to implement, a rich landscape of open-source cores exists. The Rocket Chip generator (a configurable in-order core), BOOM (a configurable out-of-order core), CVA6 (formerly Ariane, a 6-stage in-order application processor core), PicoRV32 (a size-optimized 32-bit core for FPGAs), and SweRV (a commercial-quality core originally from Western Digital) are all available as open-source RTL. No comparable ecosystem of open-source cores exists for ARM A64 or x86-64. This openness is what makes RISC-V the preferred ISA for academic computer architecture research and education.
08.Worked Examples
09.Exercises
References
- [1]Patterson, David A. and Hennessy, John L. (2020). “Computer Organization and Design RISC-V Edition: The Hardware Software Interface.” Morgan Kaufmann.
- [2]Waterman, Andrew and Asanovi\'c (2024). “The RISC-V.”
- [3]Waterman, Andrew and Asanovi\'c (2024). “The RISC-V.”