The ISA as a Contract

You have already met the idea that the Instruction Set Architecture is the seam between software and hardware. This lesson zooms all the way in on that seam and reads the fine print. An ISA is a contract, and like any good contract it spells out — precisely, exhaustively, and forever — exactly what each side may assume about the other.

On the software side, a compiler writer promises: "I will only ever emit these instructions, name these registers, and touch memory in these ways." On the hardware side, a chip designer promises: "Feed me any legal program and I will produce exactly the result the manual says — no matter how cleverly, or how differently, I build the silicon underneath." Get the contract right and a program written in 1995 still runs in 2026 on a machine a thousand times faster. That is the whole magic trick of the computing industry, and it lives entirely in the ISA.

What the contract actually specifies

An ISA is not one thing but a bundle of tightly-linked decisions. To design one — or to read one honestly — you must pin down every clause below. Each is a knob, and the whole history of processor design is the story of engineers turning these knobs differently.

ClauseWhat it fixesExample choice
Operationsthe set of instructions: arithmetic, logic, load/store, branch, systemadd, lw, beq, ecall
Operand modelwhere operands live: stack, accumulator, or registersregister–register (RISC-V)
Data typesthe widths and kinds the hardware understands8/16/32/64-bit ints, IEEE-754 floats
Registershow many, how wide, what is hardwired32 × 64-bit, x0 = 0
Addressing modeshow an instruction names a memory locationbase + displacement, PC-relative
Memory modelbyte-addressed? alignment? endianness? ordering?byte-addressed, little-endian
Encodinghow instructions become bitsfixed 32-bit words

Change any one clause and you have a different ISA — a different contract, incompatible with the old one. The rest of this lesson works through the two clauses that shape a machine's personality most: the operand model and the memory model.

The operand model: where do the operands live?

Every arithmetic instruction needs its inputs from somewhere. The single biggest fork in ISA history is the answer to "where?". There are four classic answers, and they line up on a spectrum from "almost no explicit operands" to "everything named explicitly in registers".

The load–store principle

The register–register model deserves its own name because it embodies a deep design rule, the load–store principle: arithmetic and memory access are strictly separated. An add only adds; it never reads RAM. To operate on a value in memory you must first load it into a register, compute, then store the result back. Compare the same computation C = A + B under the two dominant models:

; Register–memory (x86-style): one instruction can reach memory mov eax, [A] ; eax = A add eax, [B] ; eax = eax + B <-- ALU op ALSO does a memory load mov [C], eax ; C = eax ; Load–store (RISC-V): arithmetic touches registers only lw t0, A ; load A -> t0 lw t1, B ; load B -> t1 add t2, t0, t1 ; pure register add, no memory at all sw t2, C ; store t2 -> C

The load–store version runs more instructions (higher IC in the iron law), which sounds like a loss. But every instruction is now simple, fixed-shape, and takes a predictable time — which lets the hardware pipeline them relentlessly and drive CPI toward 1. That trade — a few more simple instructions in exchange for a clockwork-regular pipeline — is exactly why RISC won.

The memory model, and endianness

The contract must also say how memory is seen. Nearly every modern ISA presents memory as a giant array of bytes, each with its own address — byte-addressable. A 64-bit machine can name 2^{64} byte addresses. When you store a multi-byte value, though, a question appears: which byte goes first? That is endianness.

Store the 32-bit value \mathtt{0x0A0B0C0D} at address 100. In little-endian order the least-significant byte goes to the lowest address; in big-endian the most-significant byte does:

Address100101102103
Little-endian (x86, RISC-V, ARM default)0D0C0B0A
Big-endian (older SPARC, network byte order)0A0B0C0D

Neither is "correct" — but if two machines disagree and exchange raw bytes over a network or a file, the number \mathtt{0x0A0B0C0D} silently becomes \mathtt{0x0D0C0B0A}. The code below lays out both orderings byte by byte so you can watch the swap happen.

// Lay out a 32-bit word as bytes in memory, little- and big-endian. const value = 0x0A0B0C0D; const base = 100; // Extract the four bytes, most-significant first. const bytes: number[] = []; for (let shift = 24; shift >= 0; shift -= 8) { bytes.push((value >>> shift) & 0xff); } const hex = (b: number) => b.toString(16).toUpperCase().padStart(2, "0"); console.log(`Storing 0x${value.toString(16).toUpperCase()} at address ${base}:`); console.log("addr : " + [0, 1, 2, 3].map((i) => String(base + i).padStart(2)).join(" ")); // Big-endian: MSB first -> bytes in natural order. console.log("big : " + bytes.map((b) => " " + hex(b)).join(" ")); // Little-endian: LSB first -> reversed. console.log("little: " + [...bytes].reverse().map((b) => " " + hex(b)).join(" "));

They are a literary joke. In Jonathan Swift's Gulliver's Travels (1726), the empires of Lilliput and Blefuscu wage war over which end of a boiled egg to crack — the big end or the little end. In 1980 the computer scientist Danny Cohen borrowed the feud for a famous paper, "On Holy Wars and a Plea for Peace", arguing that the byte-ordering debate was every bit as pointless as the egg war: pick one, agree on it, and move on. The names stuck. Today x86 and ARM both default to little-endian, so the little-endians effectively won — but network protocols still speak big-endian ("network byte order"), so the war never quite ends.

The word "stack" gets overloaded. A stack-machine ISA (like the JVM's bytecode) means arithmetic operands live on an implicit operand stack — add pops two values and pushes one. That is an operand model. It is unrelated to the call stack that every ISA — including register–register RISC-V — uses to hold local variables and return addresses during procedure calls. RISC-V is a register machine with no operand stack at all, yet it still has a call stack in memory. Don't let the shared word fool you into thinking they are the same mechanism.

Why register–register won

By the mid-1980s the evidence had piled up. Registers are the fastest storage on the chip, and naming operands explicitly (add r3, r1, r2) makes each instruction independent, fixed-width, and predictable — a perfect diet for a pipeline. Compilers, it turned out, are excellent at keeping hot values in registers, so the "extra" load/store instructions cost far less than the rigid regularity gained. Memory operands, variable-length encodings, and clever addressing modes all made instructions slower to decode and harder to overlap, which is death for CPI. Every clean-sheet ISA since — MIPS, SPARC, Alpha, ARM's 64-bit AArch64, and RISC-V — is a load–store, register–register design. The contract's shape, chosen once, echoes through every chip that honours it. Next we put RISC and CISC side by side and settle the old argument.