Anatomy of an instruction set
What an ISA specifies
An instruction set architecture is a specification, not a chip. It defines everything a program, compiler or operating system can observe about the processor, and leaves everything else (pipeline depth, cache sizes, predictor design) to the implementation. Amdahl, Blaauw and Brooks drew this line when designing the IBM System/360, and it is why the same binary can run on a phone core and a server core that share an ISA.
Calling conventions (which register holds the return address or the first argument) belong to the ABI, a software agreement layered on top of the ISA.
Registers and data
Registers are the fastest storage in the machine, and the ISA fixes how many a program can name. More registers mean fewer spills to memory, but each one costs encoding bits in every instruction that names it, plus register-file area, read ports and save/restore time on a context switch.
| ISA | Integer registers | FP / vector registers | Notes |
|---|---|---|---|
| x86-64 | 16 × 64-bit | 16 or 32 (XMM/YMM/ZMM) | Intel APX extends integer registers to 32 |
| AArch64 | 31 × 64-bit | 32 × 128-bit | Register 31 reads as zero or the stack pointer, depending on the instruction |
| RISC-V RV64 | 32 × 64-bit | 32 FP, 32 vector | x0 is hardwired to zero; RV32E cuts to 16 for tiny cores |
| MIPS32 | 32 × 32-bit | 32 FP | $zero hardwired, the RISC convention RISC-V kept |
| MOS 6502 | A, X, Y (8-bit) | none | Accumulator machine; zero page acts as extra registers |
Byte order is part of the data definition. Storing the 32-bit word 0x12345678 at address 0x100 lays out bytes differently:
| Address | 0x100 | 0x101 | 0x102 | 0x103 | Used by |
|---|---|---|---|---|---|
| Little-endian | 78 | 56 | 34 | 12 | x86, RISC-V, Arm (in practice) |
| Big-endian | 12 | 34 | 56 | 78 | Network byte order, IBM z, SPARC, classic 68k |
Instruction formats and encoding
Encoding decides how hard instructions are to fetch and decode. Fixed-length encodings (AArch64, RISC-V, MIPS: 4 bytes) let a wide core find instruction boundaries trivially and decode many instructions in parallel. Variable-length encodings (x86: 1 to 15 bytes) pack common operations into fewer bytes but force the decoder to find each instruction's length before it can start the next. A middle path adds a 16-bit compressed subset: Arm Thumb-2 and the RISC-V C extension.
RISC-V uses six 32-bit formats. Register specifiers always sit in the same bit positions, so the register file can be read before the decoder has finished working out what the instruction is. Immediates are split across fields so that the sign bit is always bit 31.
| Format | 31–25 | 24–20 | 19–15 | 14–12 | 11–7 | 6–0 | Used for |
|---|---|---|---|---|---|---|---|
| R | funct7 | rs2 | rs1 | funct3 | rd | opcode | Register–register ALU |
| I | imm[11:0] | rs1 | funct3 | rd | opcode | Immediate ALU, loads, jalr | |
| S | imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode | Stores |
| B | imm[12|10:5] | rs2 | rs1 | funct3 | imm[4:1|11] | opcode | Conditional branches |
| U | imm[31:12] | rd | opcode | lui, auipc | |||
| J | imm[20|10:1|11|19:12] | rd | opcode | jal | |||
Try the decoder
Enter a 32-bit RV32IM instruction in hex, or pick an example. The decoder splits it into fields and disassembles it.
Addressing modes
An addressing mode is the recipe for the effective address. Rich modes let one instruction do the work of several; simple modes keep the pipeline short and regular. Studies of VAX programs in the early 1980s found that a few modes (register, immediate, displacement) covered the large majority of uses, a key argument for RISC.
| Mode | Effective address | Example | Typical use |
|---|---|---|---|
| Immediate | Operand is in the instruction | addi a0, a0, 8 | Constants |
| Register | Operand is in a register | add a0, a1, a2 | Arithmetic |
| Base + displacement | Reg + constant | ld a3, 16(sp) | Locals, struct fields |
| Scaled index | Base + index × scale + disp | mov rax, [rbx+rcx*8+16] | Array elements (x86) |
| Register offset | Base + shifted register | ldr x0, [x1, x2, lsl #3] | Array elements (Arm) |
| Post-index | Use base, then base += n | ldr x3, [x0], #8 | Walking an array (Arm) |
| PC-relative | PC + constant | auipc a0, 0x2 | Position-independent code, branches |
| Memory indirect | Address read from memory | MOVL @(R1), R0 | Pointers (VAX); dropped by RISC |
RISC-V has only base + 12-bit displacement for loads and stores. Everything else is built from extra instructions, which cores can fuse back together internally.
Operations and control flow
Instruction sets group operations into a handful of categories: data transfer (load, store, move), integer arithmetic and logic, control flow (branches, jumps, calls, returns), floating point, SIMD or vector, atomic memory operations, and system instructions (traps, fences, cache and TLB control).
Conditional branches come in two styles. Condition codes (x86 RFLAGS, Arm NZCV) are set as a side effect by arithmetic, and a later branch tests them. That saves an instruction but creates an implicit dependency on a single shared register that out-of-order hardware must rename. Compare-and-branch (RISC-V, MIPS) puts the comparison in the branch itself, so there is no hidden state, at the cost of encoding two register fields in every branch.
Atomics also split two ways. Load-reserved/store-conditional (RISC-V LR/SC, Arm LDXR/STXR) builds any read-modify-write from two simple instructions. Single atomic instructions (x86 lock cmpxchg, Arm LSE, RISC-V AMOs) perform the operation in one step and scale better under contention.
Privilege, exceptions and the memory model
| ISA | Privilege levels | Memory ordering |
|---|---|---|
| x86-64 | Rings 0–3 (OS in 0, apps in 3), plus VMX root/non-root for virtualization | TSO: stores may be delayed past later loads; little else reorders |
| AArch64 | EL0 apps, EL1 kernel, EL2 hypervisor, EL3 secure monitor | Weakly ordered; explicit barriers and acquire/release instructions |
| RISC-V | U user, S supervisor, M machine; H extension for hypervisors | RVWMO (weak), with an optional Ztso extension for TSO |
An exception is caused by the instruction itself (page fault, illegal instruction, system call); an interrupt comes from outside (timer, device). Modern ISAs require precise exceptions: when the handler runs, every earlier instruction has completed and no later one has changed visible state. Out-of-order cores pay for this with the reorder buffer.
The memory consistency model is the most subtle part of a multicore ISA. A weaker model gives hardware more freedom to reorder and buffer memory operations; a stronger model is easier to program. Binary translators such as Apple's Rosetta 2 must preserve x86 TSO behavior on weakly ordered Arm hardware, and Apple Silicon includes a hardware TSO mode for that reason.
Comparing instruction sets
Classes by where operands live
The oldest way to classify ISAs is by where an ALU instruction finds its operands. Here is C = A + B in each style:
| Stack | Accumulator | Register–memory | Load–store |
|---|---|---|---|
push A push B add pop C | load A add B store C | load R1, A add R3, R1, B store R3, C | load R1, A load R2, B add R3, R1, R2 store R3, C |
| JVM, WebAssembly, x87 FPU | 6502, early machines, many 8-bit MCUs | x86, IBM System/360 and z | RISC-V, Arm, MIPS, Power |
Stack and accumulator code is compact because operands are implicit, which is why virtual ISAs like the JVM and WebAssembly use a stack: small downloads, easy verification. Load–store machines need more instructions, but every instruction is simple, registers are explicit, and the compiler can schedule freely. Since about 1980 nearly every new general-purpose ISA has been load–store.
CISC, RISC and beyond
CISC designs of the 1970s (VAX, x86, 68k) aimed to close the "semantic gap" with rich instructions and addressing modes, implemented with microcode. The RISC work at IBM (the 801), Berkeley and Stanford showed that compilers used a small subset of those instructions, and that a simple, pipelined load–store design with many registers ran faster. Since the Pentium Pro, x86 cores translate instructions into RISC-like micro-operations internally, so the old performance gap has largely closed.
VLIW (very long instruction word) moves scheduling from hardware to the compiler: each wide instruction bundles several independent operations that issue together. It succeeded in DSPs (TI C6000, Qualcomm Hexagon) where code is regular and power is tight, and struggled in general-purpose computing (Intel Itanium), where compilers cannot predict cache misses and branches well.
Packed SIMD (SSE, AVX, NEON) operates on fixed-width registers, so every widening (128 to 256 to 512 bits) adds a new set of instructions. Vector-length-agnostic ISAs (Arm SVE, RISC-V V) let one binary run on hardware with any vector width, reviving the approach of the Cray vector machines.
| x86-64 | AArch64 | RISC-V RV64GC | |
|---|---|---|---|
| Style | CISC, register–memory | RISC, load–store | RISC, load–store |
| Instruction length | 1–15 bytes | 4 bytes | 4 bytes, or 2 (C ext.) |
| ALU operands | 2, destructive (3 with VEX) | 3 | 3 |
| Memory operands in ALU ops | Yes | No | No |
| Branches | Flags (RFLAGS) | Flags (NZCV), plus cbz/cbnz | Compare-and-branch |
| Load/store addressing | Base + index×scale + disp, RIP-relative | Offset, register offset, pre/post-index, literal | Base + 12-bit offset |
| SIMD / vector | SSE, AVX2, AVX-512, AMX | NEON, SVE/SVE2, SME | V extension |
| Extensibility | Prefix bytes added over decades | Versioned features (v8.x, v9.x) | Modular base + lettered extensions; custom opcode space |
| Licensing | Proprietary (Intel, AMD) | Licensed by Arm | Open, royalty-free |
One function, three ISAs
The same array sum, written by hand for each ISA (arguments arrive in registers per each platform's ABI):
long sum(long *a, long n) {
long s = 0;
for (long i = 0; i < n; i++) s += a[i];
return s;
}
sum: xor eax, eax test rsi, rsi jle .done lea rdx, [rdi+rsi*8] .loop: add rax, [rdi] add rdi, 8 cmp rdi, rdx jne .loop .done: ret
sum: mov x2, #0 cmp x1, #1 b.lt .done .loop: ldr x3, [x0], #8 add x2, x2, x3 subs x1, x1, #1 b.ne .loop .done: mov x0, x2 ret
sum: li a2, 0 blez a1, .done slli a1, a1, 3 add a1, a0, a1 .loop: ld a3, 0(a0) add a2, a2, a3 addi a0, a0, 8 bne a0, a1, .loop .done: mv a0, a2 ret
| ISA | Instructions | Loop body | Code size | Highlighted feature |
|---|---|---|---|---|
| x86-64 | 9 | 4 | 24 bytes | Memory operand: load and add in one instruction |
| AArch64 | 9 | 4 | 36 bytes | Post-index load bumps the pointer for free |
| RV64G | 10 | 4 | 40 bytes | Compare-and-branch, no flags |
| RV64GC | 10 | 4 | 24 bytes | 8 of 10 instructions compress to 2 bytes |
Sizes measured by assembling these exact listings with LLVM. Each ISA reaches a four-instruction loop by a different route. Paste the C into Compiler Explorer to compare with compiler output at different optimization levels.
Performance implications
The ISA touches all three terms. Richer instructions lower the instruction count. Simple, regular instructions lower CPI because they pipeline well and decode in parallel. Decode complexity can lengthen the critical path and raise the cycle time, or force extra pipeline stages that make branch mispredictions more expensive.
Modern implementations blur these effects. x86 cores cache decoded micro-ops to skip the expensive decoder on hot loops, and both x86 and RISC cores fuse common instruction pairs (compare + branch, lui + addi) into one internal operation. Blem, Menon and Sankaralingam measured ARM and x86 cores and found that the ISA itself had no significant effect on performance or energy; microarchitecture and design targets explained the differences.
Where the ISA still clearly matters:
- Code density affects instruction-cache misses, fetch bandwidth, and flash cost in embedded parts.
- Decode width. Fixed-length instructions make 8-wide decode practical; variable-length decode costs area and power.
- Vector and matrix extensions give order-of-magnitude gains on the kernels they target.
- Memory model. Weaker ordering allows more aggressive store buffering; stronger ordering simplifies concurrent software.
- Ecosystem. Compilers, libraries and binary compatibility often outweigh any technical advantage.
Designing for an application
Design decisions and what they cost
| Decision | Gain | Cost |
|---|---|---|
| Fixed-length instructions | Simple, parallel decode; easy boundaries | Lower code density; wasted bits on simple ops |
| Variable-length or compressed | Smaller code, fewer I-cache misses | Complex fetch and decode; instructions can cross cache lines |
| More registers | Fewer spills, more room for compiler scheduling | More bits per operand; bigger register file; slower context switch |
| 3-operand vs 2-operand | Non-destructive; fewer copy instructions | One more register field per instruction |
| Rich addressing modes | Fewer instructions for array and struct access | More address-generation hardware; harder pipelining |
| Condition codes | Compare is free after arithmetic | Implicit dependency that must be renamed |
| Larger immediates | Fewer instructions to build constants and offsets | Fewer bits left for opcodes or registers |
| Specialized instructions | Large speedups on target kernels | Opcode space, verification effort, unused silicon elsewhere |
| Backward compatibility | Existing software keeps running | Legacy features must be supported forever |
| Security features | Hardware-enforced memory safety or control-flow integrity | Extra state, encoding space and software changes |
The encoding budget
Every instruction has a fixed number of bits to share among the opcode, register specifiers and immediate. Adjust the design to see what fits.
Reading the result. The default, 32 registers with 3 operands and a 7-bit opcode, is RISC-V's R-type: 22 bits used, 10 left for funct3 and funct7. An I-type instruction drops one register field, which frees 5 bits and gives a 12-bit immediate.
Try a 16-bit instruction with 32 registers and 3 operands: it does not fit. Compressed encodings such as RISC-V C get around this by using 2 operands and limiting some fields to 8 popular registers (3 bits each).
Matching the ISA to the application
| Application | What matters | ISA choices | Real examples |
|---|---|---|---|
| Microcontrollers and IoT | Code size, energy, gate count, interrupt latency | 16/32-bit mixed encoding, fewer registers, no MMU, simple pipeline | Arm Cortex-M (Thumb-2 only), RISC-V RV32EC/IMC |
| Digital signal processing | Multiply-accumulate throughput, deterministic timing | MAC, saturating and fixed-point math, zero-overhead loops, circular addressing, VLIW | TI C6000, Qualcomm Hexagon, Cadence Tensilica, PULP RI5CY |
| Mobile application processors | Performance per watt, large software ecosystem | Clean 64-bit RISC, SIMD, heterogeneous core support | AArch64 with big and little cores |
| Desktop and server | Single-thread speed, binary compatibility, virtualization | Wide SIMD, large memory addressing, hypervisor modes | x86-64, Arm Neoverse |
| Machine learning | Dense matrix math at low precision | Matrix and tensor instructions; FP16, BF16, FP8, INT8 types | Intel AMX, Arm SME, Google TPU (a few coarse CISC-style instructions) |
| Cryptography | Throughput and constant-time execution | AES rounds, SHA steps, carry-less multiply | AES-NI, Arm crypto extensions, RISC-V Zk |
| Memory-safe systems | Hardware-enforced bounds and permissions | Capabilities, pointer authentication, memory tagging | CHERI (Arm Morello), Arm PAC and MTE |
| Portable code distribution | Compactness, verifiability, any-host execution | Stack-based virtual ISA, translated by a JIT or ahead of time | WebAssembly, JVM bytecode |
Worked example: should we add a multiply-accumulate?
A team building an audio processor finds that a dot-product kernel takes 60% of run time. On RV32IM the inner loop is 7 instructions:
loop: lw t0, 0(a0) lw t1, 0(a1) mul t2, t0, t1 add a2, a2, t2 addi a0, a0, 4 addi a1, a1, 4 bne a0, a3, loop
loop: lw t0, 0(a0) lw t1, 0(a1) mac a2, t0, t1 addi a0, a0, 4 addi a1, a1, 4 bne a0, a3, loop
Benefit. The kernel drops from 7 to 6 instructions. If CPI is unchanged, Amdahl's law gives 1 / (0.4 + 0.6 × 6/7) = 1.094, about a 9% speedup.
Cost. mac reads three registers (two sources plus the accumulator), so the register file needs a third read port, and a multiplier feeding an adder lengthens the critical path. If that raises the cycle time by 3%, the net speedup is 1.094 / 1.03 = 1.062. Every other program pays the 3% too, with no benefit.
Alternatives. Post-increment loads would remove both addi instructions, and a hardware loop would remove the branch, bringing the loop to 3 instructions. The PULP project's RI5CY core added exactly this set (MAC, post-increment loads, hardware loops) to RISC-V for near-threshold IoT signal processing.
Evaluating a proposed instruction
- Profile real workloads. Find where time goes and which instruction sequences repeat. A new instruction only helps code that uses it.
- Estimate the ceiling. Apply Amdahl's law and the iron law with optimistic assumptions. If the best case is small, stop here.
- Check the encoding. Count the opcode space and register fields it needs and whether it fits existing formats. Prefer reserved or custom opcode space over consuming base-ISA encodings.
- Prototype in software. Add it to an instruction-set simulator (Spike, QEMU) and the compiler or intrinsics, then rerun the workloads to measure instruction count.
- Model timing. Use a cycle-level simulator such as gem5 to see whether CPI holds up once caches and dependencies are included.
- Cost the hardware. Synthesize the RTL change for area, timing and power. A 1% cycle-time hit applies to every program.
- Count the long-term cost. Verification, compiler and OS support, documentation, and supporting the instruction for as long as the ISA exists.
Resources
Interactive tools and references
Peer-reviewed reading
- Amdahl, G. M., Blaauw, G. A., & Brooks, F. P. (1964). Architecture of the IBM System/360. IBM Journal of Research and Development, 8(2), 87–101.Defines architecture as distinct from implementation; the origin of the ISA concept.doi.org/10.1147/rd.82.0087
- Patterson, D. A., & Ditzel, D. R. (1980). The case for the reduced instruction set computer. ACM SIGARCH Computer Architecture News, 8(6), 25–33.The argument against ever-growing instruction sets.doi.org/10.1145/641914.641917
- Radin, G. (1982). The 801 minicomputer. Proc. ASPLOS I, 39–47.IBM's early RISC design, built around what compilers actually needed.doi.org/10.1145/800050.801824
- Patterson, D. A., & Séquin, C. H. (1982). A VLSI RISC. IEEE Computer, 15(9), 8–21.The Berkeley RISC I design and its register windows.doi.org/10.1109/MC.1982.1654133
- Hennessy, J., et al. (1982). MIPS: A microprocessor architecture. Proc. MICRO-15, ACM SIGMICRO Newsletter, 13(4), 17–22.Stanford's ISA designed for pipelining, with software handling interlocks.doi.org/10.1145/1014194.800930
- Fisher, J. A. (1983). Very long instruction word architectures and the ELI-512. Proc. ISCA, 140–150.The founding VLIW paper.doi.org/10.1145/800046.801649
- Bhandarkar, D., & Clark, D. W. (1991). Performance from architecture: Comparing a RISC and a CISC with similar hardware organization. Proc. ASPLOS IV, 310–319.A controlled iron-law comparison of MIPS and VAX.doi.org/10.1145/106972.107003
- Blem, E., Menon, J., & Sankaralingam, K. (2013). Power struggles: Revisiting the RISC vs. CISC debate on contemporary ARM and x86 architectures. Proc. IEEE HPCA, 1–12.Finds the ISA has little effect on modern performance and energy.doi.org/10.1109/HPCA.2013.6522302
- Weaver, V. M., & McKee, S. A. (2009). Code density concerns for new architectures. Proc. IEEE ICCD, 459–464.Measures code size across dozens of ISAs.doi.org/10.1109/ICCD.2009.5413117
- Peleg, A., & Weiser, U. (1996). MMX technology extension to the Intel architecture. IEEE Micro, 16(4), 42–50.How packed SIMD was retrofitted onto x86.doi.org/10.1109/40.526924
- Stephens, N., et al. (2017). The ARM Scalable Vector Extension. IEEE Micro, 37(2), 26–39.Vector-length-agnostic design and why it matters.doi.org/10.1109/MM.2017.35
- Codrescu, L., et al. (2014). Hexagon DSP: An architecture optimized for mobile multimedia and communications. IEEE Micro, 34(2), 34–43.A commercial VLIW DSP ISA shaped by its application.doi.org/10.1109/MM.2014.12
- Gautschi, M., et al. (2017). Near-threshold RISC-V core with DSP extensions for scalable IoT endpoint devices. IEEE Transactions on VLSI Systems, 25(10), 2700–2713.The RI5CY extensions from the worked example, with measured gains.doi.org/10.1109/TVLSI.2017.2654506
- Jouppi, N. P., et al. (2017). In-datacenter performance analysis of a tensor processing unit. Proc. ISCA, 1–12.A domain-specific accelerator with a small CISC-style instruction set.doi.org/10.1145/3079856.3080246
- Gueron, S. (2009). Intel's new AES instructions for enhanced performance and security. Fast Software Encryption, LNCS 5665, 51–66.Application-specific instructions for speed and side-channel resistance.doi.org/10.1007/978-3-642-03317-9_4
- Woodruff, J., et al. (2014). The CHERI capability model: Revisiting RISC in an age of risk. Proc. ISCA, 457–468.Adding memory safety at the ISA level.doi.org/10.1109/ISCA.2014.6853201
- Sewell, P., et al. (2010). x86-TSO: A rigorous and usable programmer's model for x86 multiprocessors. Communications of the ACM, 53(7), 89–97.Formalizes the x86 memory model.doi.org/10.1145/1785414.1785443
- Smith, J. E., & Nair, R. (2005). The architecture of virtual machines. IEEE Computer, 38(5), 32–38.ISA-level virtualization and binary translation.doi.org/10.1109/MC.2005.173
- Haas, A., et al. (2017). Bringing the web up to speed with WebAssembly. Proc. ACM PLDI, 185–200.Design of a portable, verifiable virtual ISA.doi.org/10.1145/3062341.3062363
- Hennessy, J. L., & Patterson, D. A. (2019). A new golden age for computer architecture. Communications of the ACM, 62(2), 48–60.Open ISAs and domain-specific architectures as the way forward.doi.org/10.1145/3282307