Instruction Set Architecture
An instruction set architecture (ISA) is the abstract contract between hardware and software: the set of instructions a processor can execute, their binary encodings, the registers they operate on, the memory model they obey, and the events (interrupts, exceptions, system calls) they expose to software (Wikipedia, Instruction set architecture). Everything compiled, assembled, or hand-written for a CPU is written against its ISA; the microarchitecture is whatever circuit chooses to implement that contract. Two cores that share an ISA run the same binary even when one is a tiny in-order microcontroller and the other is a four-wide out-of-order superscalar. This separation is the load-bearing abstraction in modern computing and the reason ARM, x86, and RISC-V binaries each constitute a coherent software ecosystem.
This note is the general concept. The RISC-V ISA used in the definitely-not-esp32 project gets its own note (RISC-V Instruction Set Architecture); concrete extension subsets are covered in RV32IMC and Zicsr Extension; the microarchitecture that executes the ISA appears in Classic Five-Stage Pipeline and friends.
Mental Model
flowchart TB subgraph SW["Software stack"] APP["Application source (C, Rust, Go)"] COMP["Compiler"] OBJ["Machine code (bytes)"] APP --> COMP --> OBJ end subgraph ISA["ISA contract (the interface)"] INSN["Instruction set:<br/>opcodes, encodings, semantics"] REGS["Programmer-visible state:<br/>registers, PC, flags, CSRs"] MEM["Memory model:<br/>addressing, alignment, ordering"] EVT["Events:<br/>interrupts, exceptions, syscalls"] end subgraph HW["Microarchitecture (the implementation)"] PIPE["Pipeline depth"] ISSUE["Issue width"] CACHE["Cache hierarchy"] PRED["Branch predictor"] OOO["Out-of-order rename / ROB"] end OBJ -->|"runs against"| ISA ISA -->|"implemented by"| HW
The ISA sits between software and silicon. What it shows: software produces bytes that target a fixed contract; the contract names a set of instructions, the visible state they manipulate, the memory model they share, and the events they raise. Below the contract, any microarchitecture is permitted: a single-cycle teaching core, an in-order embedded CPU, or a deeply pipelined out-of-order monster. The insight to take: the ISA is what binaries are written against; everything else is a black box hiding behind it.
What is on the Contract
The ISA defines a relatively small number of orthogonal things, and ducks every implementation choice it can. The standard list, drawn from any architecture textbook and made explicit in the RISC-V manual’s introduction (RISC-V Unprivileged ISA, 20241126 release):
- The instruction set itself. Each instruction has a binary encoding (the opcode and operand fields), a textual mnemonic the assembler accepts, and a written semantic in terms of register and memory state transformations. The opcode space is a finite resource; ISAs differ in how they spend it.
- Programmer-visible state. A register file (general-purpose integer registers, often a separate floating-point file, sometimes vector registers), a program counter, condition flags on some architectures (carry/zero/negative/overflow on x86 and ARM A32; not on RISC-V), and control registers used to configure or interrogate the machine. Software-visible state is what the ISA promises the program can read and write; cache lines, branch-target buffers, and store buffers are deliberately not on the contract.
- The memory model. How addresses are formed, what alignment rules apply, what happens on misaligned access (trap? slow? silently work?), and which orderings between loads and stores are guaranteed when multiple cores observe shared memory. The memory model is the most subtle part of any modern ISA and the part most easily got wrong by both implementers and programmers.
- The event model. How the CPU reports synchronous exceptions (illegal instruction, divide by zero, memory fault), how it accepts asynchronous interrupts, what privilege transitions happen on each, and what state the handler sees on entry. On RISC-V this is the trap machinery covered in RISC-V Trap Handling and RISC-V Privilege Modes.
- The ABI hook points. The ISA itself does not specify the calling convention (which registers carry arguments, who saves what, how the stack is laid out). The ABI is a separate document layered on top, and the same ISA can support multiple ABIs (think of ILP32 vs LP64 on the same x86-64 chip). The ISA reserves the mechanism (registers, stack pointer, return-address encoding); the ABI picks the policy.
What the ISA pointedly does not define: pipeline depth, cache size, branch predictor quality, out-of-order or in-order issue, the bus protocol, the power model, or the clock speed. Those are microarchitectural and per-implementation. The cleanness of this split is the entire reason a 1995 Pentium and a 2025 Xeon can run the same cmp instruction even though one decodes it directly and the other cracks it into micro-ops, renames the result, and speculates around it.
ISA versus Microarchitecture: Why the Split Exists
The textbook definition is sharp: “An instruction set architecture is distinguished from a microarchitecture, which is the set of processor design techniques used, in a particular processor, to implement the instruction set” (Wikipedia, Instruction set architecture). The Intel Pentium and the AMD Athlon implemented nearly identical x86 ISAs through radically different internal designs; binaries did not know or care.
The economic argument for the split is simple. A binary compiled against an ISA can run on any conformant implementation, present or future. Hardware can be redesigned freely (and often must be, for power or process-node reasons) without breaking the installed software base, as long as the ISA contract is preserved. The cost is paid up front, in the ISA’s design: choices made when the architecture is young become near-permanent constraints. The x86 ISA still carries 1978 design decisions because backward compatibility is more valuable than any individual cleanup.
The technical argument is equally simple. The microarchitecture is where competition happens. Two vendors implementing the same ISA compete on performance, power, and area; software portability is preserved by the contract. This is exactly why opening the ISA (rather than open-sourcing any one core) was the lever RISC-V chose to pull: liberating the contract lets a hundred competing microarchitectures bloom underneath it (Wikipedia, RISC-V).
RISC versus CISC: A Design-Philosophy Schism
The classification dominating ISA discussions for the last forty years is reduced-instruction-set computer (RISC) versus complex-instruction-set computer (CISC). The labels are imperfect (modern x86 chips internally execute RISC-like micro-ops, and modern ARM chips have grown plenty of complex instructions) but they capture a real philosophical divide.
The CISC tradition, exemplified by VAX in the 1970s and x86 ever after, packs many specialized instructions and rich addressing modes into the ISA, on the theory that doing more per instruction reduces program size, memory bandwidth, and the number of instructions the compiler needs to emit. A single CISC instruction may “perform a sequence of low-level functionality and may take several processor clock cycles to execute” (GeeksforGeeks, RISC vs CISC). VAX’s notorious POLY instruction evaluated a polynomial; x86’s REP MOVSB is a one-instruction memcpy. Variable-length encodings (1 to 15 bytes on x86) maximize density.
The RISC counter-thesis, developed independently at Berkeley by David Patterson and at Stanford by John Hennessy in the late 1970s and early 1980s, argued that the silicon spent decoding rich instructions and microcode-engine state was better spent on registers, pipelines, and caches. A reduced instruction set “simplifies the processor by efficiently implementing only the instructions that are frequently used in programs, while the less common operations are implemented as subroutines” (Wikipedia, Instruction set architecture). Patterson’s Berkeley work produced SPARC; Hennessy’s Stanford work produced MIPS; ARM emerged on the same RISC wave. The case held up under measurement: cycles-per-instruction dropped, clock rates rose, and the silicon freed up by simpler decoders went into pipelining and on-chip caches.
The defining technical traits of a classical RISC are:
- Load/store architecture. Memory is touched only by explicit load and store instructions; arithmetic operates strictly on registers. CISC permits operands in memory directly (
add [rax], 1on x86 is a single instruction that reads memory, adds, and writes back). - Fixed-length instructions. Almost always one word wide. Easy to fetch and decode in a pipeline because the next instruction is always at a known offset.
- Large register file. Often 32 general-purpose registers (RISC-I had 138, banked; MIPS, SPARC, and RISC-V settled on 32). More registers mean fewer spills to memory.
- Three-operand instructions.
add rd, rs1, rs2rather than CISC’s two-operandadd rd, rs1that overwritesrd. Three-operand keeps both inputs alive and saves register copies. - Simple addressing modes. Typically base + offset only; no scaled-index or memory-indirect modes. The complex addressing of CISC is reproduced, when needed, by a short sequence of simple RISC instructions.
- No microcode. Each instruction maps to a small, fixed sequence of pipeline stages, decoded by combinational logic rather than a microcode ROM.
The trade-off is brutally honest: RISC programs are larger in bytes than CISC programs that compute the same thing, because each operation is smaller. That cost was acceptable in 1985 when caches and memories grew faster than instruction count; it became painful again in deeply embedded systems where flash size dominated cost, which is exactly why compressed-instruction extensions like ARM Thumb and RISC-V’s C extension exist. Compressed ISAs add a 16-bit alternative encoding for the most common instructions, recovering CISC-like code density without giving up the rest of the RISC discipline.
Fixed-Length versus Variable-Length Encoding
One of the most consequential ISA decisions, and the cleanest place to see the RISC/CISC schism, is instruction length. “In some architectures, notably most reduced instruction set computers (RISC), instructions are a fixed length, typically corresponding with that architecture’s word size. In other architectures, instructions have variable length, typically integral multiples of a byte or a halfword.” (Wikipedia, Instruction set architecture).
A fixed-length ISA (classical MIPS, SPARC, original ARM A32, base RV32I) uses a single instruction width (usually 32 bits). The fetch logic always grabs PC and PC+4; the decode logic always looks at the same bit fields; pipeline alignment is trivial. The cost is wasted bits: a register-to-register add and a 32-bit lui both consume the same encoding budget, and short common operations like mv or nop waste most of their 32 bits.
A variable-length ISA (x86, x86-64, VAX) uses instructions ranging from 1 to 15 bytes (or 1 to 17 on classic VAX). Code is denser, especially for short common forms. The cost is decode complexity: every fetched byte may or may not start an instruction, and to find the length you must partially decode the bytes you have, which makes parallel decode and accurate branch-target alignment hard. x86 implementations spend significant silicon on length-decode logic and on a “marked I-cache” that records, per byte, whether it begins an instruction.
A modern middle ground, used by ARM Thumb, ARM Thumb-2, MIPS16, and RISC-V’s C extension, is dual-length encoding: a base ISA at one fixed width plus an optional compressed encoding at half the width, with a one-bit-or-two-bit marker telling the decoder which it is. RISC-V picks this up cleanly: the low two bits of any instruction word encode the length class. If bits[1:0] == 11, the instruction is 32 bits wide; otherwise it is 16 bits (RISC-V Unprivileged ISA, 20241126 release). Two more length codes are reserved for 48-bit and 64-bit-and-up instructions that the spec leaves room for but does not yet define. The decoder still pays for two-width handling, but it never has to scan bytes to find a boundary; the marker is at a fixed position.
Memory Model: The Most Subtle Clause
Every modern ISA has a written memory model that says which load-store reorderings are visible to other cores. The strongest is sequential consistency (every core sees the same global interleaving); weakest are the “weakly ordered” models like ARM and RISC-V where each core may locally reorder freely subject only to explicit fences. The middle ground includes x86’s total store order (TSO), where loads may pass earlier stores to different addresses but stores never reorder with each other.
The choice matters because the memory model is what defines correct lock-free code. On a sequentially consistent ISA, a naive Dekker’s algorithm works; on x86-TSO, you need an mfence; on weakly ordered ARM or RISC-V, you need release/acquire fences in specific places. The ISA pins down what is legal; the hardware then has design freedom to reorder anywhere the model permits, which lets store buffers, write-combining buffers, and out-of-order execution earn their keep without breaking software. RISC-V’s RVWMO (RISC-V Weak Memory Order) is a relaxed model with fence instructions for synchronization and the optional A extension for load-reserved/store-conditional and atomic AMOs.
ISA Extensions and Modularity
Older ISAs grew monolithically: every successive x86 revision (8086, 286, 386, MMX, SSE, SSE2, AVX, AVX-512) added instructions to the same single ISA. The base implementation is mandatory; the extensions are typically optional and probed via the CPUID instruction at runtime. RISC-V took the modular logic further: from day one, only a base (RV32I, RV64I, RV32E, RV64I) is mandatory; everything else (multiply/divide, atomics, floating-point, compressed, vector, hypervisor, bit-manipulation, cryptography, control-flow integrity, etc.) is a named, separately-versioned extension you opt into, and the ISA string of a chip names them explicitly: rv32imc_zicsr_zifencei, rv64gc, and so on (Research Red Hat, RISC-V extensions). See RISC-V Instruction Set Architecture for the details and RV32IMC for the concrete subset this project uses.
The modular approach has a clear win: a microcontroller can omit FPUs, vector units, and hypervisor support without paying silicon for instructions it cannot execute, while a server core can opt them all in. It has an equally clear cost: the toolchain has to know exactly which extensions the target supports, and shared distributions face a fragmentation problem. The RISC-V community manages this with profiles (RVA20, RVA22, RVA23, RVB23) that bundle a fixed set of extensions to target for application-class chips, plus distinct profiles for microcontrollers.
Concrete Example: A Single Instruction, Two Worlds
Compare adding 1 to a memory location on a CISC and a RISC ISA.
On x86-64 (CISC), one instruction does the whole thing:
incq (%rdi) ; bytes: 48 ff 07 (3 bytes)The CPU’s microcode reads *rdi, adds 1, writes back, and updates flags, all from one 3-byte instruction. The variable-length encoding packs the operation densely.
On RV32I (RISC), the same job takes three explicit instructions because of the load/store discipline:
lw t0, 0(a0) ; bytes: 03 25 05 00 (4 bytes)
addi t0, t0, 1 ; bytes: 93 02 15 00 (4 bytes)
sw t0, 0(a0) ; bytes: 23 20 50 00 (4 bytes)Twelve bytes versus three for the same computation. The RISC code does not get to fuse memory access with arithmetic, but every instruction is the same length, fetches at the same rate, and can be pipelined with no decode-length uncertainty. With the compressed extension enabled, the addi shrinks to a 2-byte c.addi and the loads/stores shrink to c.lw/c.sw (also 2 bytes each when the registers fall in the x8-x15 range), bringing the RISC version down to six bytes. Code density catches up; decoder simplicity is preserved.
Neither sequence is “correct” in some absolute sense. They are different points on a design surface, and the choice of point shapes every downstream implementation decision.
Failure Modes and Common Misunderstandings
- Conflating ISA and microarchitecture. Saying “RISC-V is fast” or “x86 is slow” is meaningless: speed lives in the microarchitecture, not the ISA. A given ISA can be implemented by a 10 MHz hand-built FPGA core or a 5 GHz superscalar out-of-order machine; the ISA itself has no clock. The honest comparison is between specific implementations.
- Treating extensions as universal. “RISC-V has vector” is a capability statement, not a guarantee for any given chip. The bare ESP32-C3 implements RV32IMC (no F, D, V, or A). Code compiled with vector instructions will hit illegal-instruction traps on cores that omitted V. The ISA catalog is broad; any one chip implements only a subset.
- Assuming the ABI is part of the ISA. It is not. The same
riscv64-unknown-linux-gnucore can run programs built forlp64andlp64dABIs (with and without hard-float register passing); the chip does not change. ABI mismatches produce silent corruption, not illegal instructions. - Misreading variable-length encoding. On x86 the byte at
PCis not necessarily the start of an instruction unless you know how you got there. Disassemblers can “lose sync” on data interleaved with code. Fixed-length and RVC’s two-bit length marker make this impossible by construction. - Believing more instructions is better. CISC instructions look strictly more capable, but the rare ones often have microarchitectural penalties (microcoded entry, serializing semantics, side effects on flags). Compiler authors routinely avoid them in favor of equivalent simple sequences that the out-of-order engine can pipeline more aggressively.
Alternatives and Cross-Comparison
Major ISAs as of 2026, in rough order of installed silicon volume:
- ARM (A-profile and M-profile). Dominant in mobile, embedded, and increasingly desktop and server. Originally fixed-length 32-bit, now multi-length with Thumb-2 and AArch64. Proprietary, licensed per-instance.
- x86-64. Dominant in PC, server, and gaming consoles. Variable-length (1-15 bytes), backward compatible to the 1978 8086. Two-vendor ecosystem (Intel, AMD), both proprietary.
- RISC-V. Modular, open, royalty-free. Used in embedded (ESP32-C3, BL602), accelerators (Nvidia GPU control processors), some application processors (SiFive, Alibaba T-Head), and the entire research community. See RISC-V Instruction Set Architecture for details.
- POWER / Power ISA. IBM mainframes and a small open variant (OpenPOWER). Fixed-length, big-endian historically, now bi-endian.
- MIPS. Originated the modern RISC discipline; long the dominant embedded ISA; now mostly displaced by ARM and RISC-V, though residual silicon ships in routers and set-top boxes.
- WebAssembly. A “virtual ISA” targeted by compilers and executed by an in-browser or in-runtime VM. Not implemented in silicon (yet); structurally a stack machine, not register-based. Different evolutionary niche.
The choice between them for any given product is driven by ecosystem, licensing, available cores, and only finally by ISA design merits. RISC-V’s relevance is structural rather than technical: it is the only ISA in the list whose specification can be implemented without a license fee, which lets it occupy niches the proprietary alternatives cannot.
Production Notes
In commercial practice, the ISA is essentially never the bottleneck. The microarchitecture, the process node, the compiler, and the memory subsystem dominate performance. The ISA shows up as a constraint on how small a chip can be (a baroque ISA forces a baroque decoder), on who can build a compatible chip (proprietary ISAs lock out competitors), and on what you can run on a chip (no extension means no execution). For the definitely-not-esp32 project the relevant production consequence is that picking RV32IMC matches both a real shipping core (ESP32-C3) and the well-supported Rust target riscv32imc-unknown-none-elf, so the toolchain story is solved before any silicon is written.
The historically important production consequence of the RISC vs CISC argument is that it ended in a draw at the architectural level (modern x86 chips decode CISC bytes into RISC-like internal micro-ops and execute them on a fundamentally RISC backend) but a near-total RISC victory in shipped external ISAs: every new ISA designed since 1990 (ARM AArch64, RISC-V, OpenRISC, LoongArch, Power ISA’s modern forms) has been RISC, and the only volume CISC survivor is x86. The argument was about whether the decoder belonged in software (compilers, generating RISC bytes the hardware runs directly) or hardware (a microcode engine, running CISC bytes by decomposing them); compilers won.
See Also
- RISC-V Instruction Set Architecture: the specific open, modular ISA used in this project
- RV32IMC: the concrete extension subset used in definitely-not-esp32
- Zicsr Extension: the CSR-access instructions, a separately versioned extension
- RISC-V Privilege Modes: the M/S/U privilege model layered on top
- Classic Five-Stage Pipeline: one microarchitectural implementation strategy
- Cycles Per Instruction: the performance metric on which ISAs and implementations are compared
- ESP32-C3: a real commercial RV32IMC chip
- Computer Architecture MOC: parent map