What an ISA Is
The instruction set architecture is the contract that lets a compiler and a chip be written by people who never meet: the stable interface software targets and hardware implements. We separate architecture from microarchitecture, read RISC and CISC as opposite answers to where complexity should live, price out what each choice costs in decode hardware, code density, and pipeline friendliness, and see how x86-64 endures by translating its instructions into RISC-like operations on the fly.
╌╌╌╌
A compiler team and a chip team can ship a working product without ever speaking, and the reason is a single document neither of them is free to change on a whim: the instruction set architecture. The ISA is the agreed-upon vocabulary: the exact set of instructions, their encodings, the registers and memory they touch, and the effect each one has. The compiler emits only words from that vocabulary; the hardware promises to understand every one of them. Pin the vocabulary down and the two halves of the system can evolve independently for decades. This module builds an ISA from the ground up, and this first lesson says what kind of object an ISA is and what its designers are really arguing about.
The contract between hardware and software
The computing stack is a tower: applications on top, then a language runtime, an operating system, the compiler's output, and at the bottom the silicon. Most of those layers can be swapped freely. The one fixed seam where hardware and software meet is the ISA. It is the only interface in the system that both a piece of software and a piece of hardware are written directly against.
The payoff of this arrangement is binary compatibility. A program compiled to x86-64 in 2005 still runs on an x86-64 chip built today, even though the two processors share almost no internal structure: different pipelines, different cache sizes, different transistor counts by orders of magnitude. They agree only where they must, at the ISA. Conversely, a chip vendor can redesign the entire internals of a processor and the entire planet's worth of existing software keeps running, untouched, because the contract held.
Compatibility is asymmetric. Let be the instruction set a chip generation decodes. Backward compatibility — new hardware running old binaries — requires , and every vendor must keep it, because the installed software predates the chip and cannot be recompiled to suit it. Forward compatibility — old hardware running binaries built for a newer chip — is not promised: a 2005 processor faults on an instruction in (say an AVX vector op added in 2013) because its decoder was never taught the encoding. Hence the contract can grow but never shrink: encodings may be appended, old software simply never uses them, but no encoding that shipping software depends on may be withdrawn — which is how forty-year-old corners of x86 survive into every new part. The contract can only grow.
What lives in the contract is what software is allowed to depend on: the instructions and their meanings, the registers and their widths, the addressing modes, the byte ordering, how exceptions and interrupts behave. What lives outside it is everything about how the work gets done. The contract also cuts both ways in time: once software ships that depends on some corner of the ISA, even an awkward one, every future implementation must honor it. x86-64 still executes encodings whose design decisions date to the 8086 of 1978, not because anyone likes them, but because dropping them would break binaries nobody can recompile.
Architecture versus microarchitecture
It is tempting to use architecture
loosely, but the field draws a hard line, and
keeping it straight clears up most confusion about what a processor is.
The architecture is the contract; the microarchitecture is one way of honoring it. Two processors implement the same architecture if a program cannot tell them apart by its results, only by how long it takes. Intel's Skylake and a budget Atom core are the same architecture (x86-64) and wildly different microarchitectures. This separation is what lets the later modules in this course build a complete processor twice over: first as a simple sequential machine, then as a pipelined one, both implementing the same Y86-64 ISA we are about to define.
For example, take the register file. x86-64 the architecture names sixteen
general-purpose registers, because its encodings carry 4-bit register fields. A
high-end implementation actually contains a few hundred physical registers and
renames the sixteen architectural names onto them on the fly, so that
independent uses of %rax in nearby instructions can execute in parallel. No
program can detect the extra registers; it can only run faster. The sixteen names
are architecture; the hundreds of slots and the renaming table are
microarchitecture. The same split recurs everywhere: the ISA says a load returns
the last value stored, and says nothing about the three levels of cache that make
most loads fast; the ISA says instructions execute in program order as far as
results are concerned, and says nothing about the out-of-order engine reordering
them internally.
The line matters for a working programmer too. The architecture tells you what your code computes; the microarchitecture tells you how fast. Cache misses, branch mispredictions, and pipeline stalls are microarchitectural facts: invisible to correctness, decisive for performance.
To see the two axes vary independently, run one program on three chips that share the architecture. The program is a tight loop summing an array of instructions. All three are x86-64, so they fetch the same bytes and produce the same sum — the architecture holding. Only the cycle count differs. Suppose the array overflows the first-level cache, so each iteration risks a memory stall.
| Chip | Microarchitecture | Cycles |
|---|---|---|
| A | in-order, two-level cache | |
| B | out-of-order, wide issue | |
| C | out-of-order, larger LLC |
Chip A stalls often; chip B keeps dozens of iterations in flight and hides most stalls behind independent work; chip C keeps more of the array warm. A better than threefold spread in time, one architecture, three microarchitectures. No line of the program changed, and no line could have changed to reveal which chip it ran on — only a stopwatch tells them apart.
Two philosophies: RISC and CISC
Designing an ISA forces one central question: when should the hardware do something complicated, and when should it make the compiler do it instead? Two schools answered oppositely, and the names stuck — CISC (Complex Instruction Set Computer) and RISC (Reduced Instruction Set Computer).
CISC, the older style, grew up when memory was scarce and compilers were weak. Its approach is to make each instruction do a lot: a single instruction might load two operands from memory, multiply them, and store the result, all at once. Instructions vary in length so that common operations stay compact. x86 is the canonical CISC: it has hundreds of instructions and encodings from 1 to 15 bytes long.
RISC took the opposite bet, made once compilers improved and memory grew cheap.
Keep the instruction set small and uniform: every instruction is the same
length (typically 4 bytes), most run in a single cycle, and — the defining rule —
only explicit load and store instructions touch memory, while all arithmetic
happens register-to-register. The hardware stays simple and fast; the compiler
takes on the job of stitching simple instructions into complex behavior.
What each choice costs
The table reads like a matter of taste until you price the rows out in hardware and in bytes. Three consequences carry most of the weight.
Decode complexity. With a fixed instruction length , instruction of a fetched block starts at byte — the boundaries are known before a single opcode is read, so a block of instructions feeds decoders in parallel. Variable length destroys this: the start of instruction is and on x86 depends on optional prefixes, the opcode, and the addressing-mode byte, so it is not known until instruction is itself decoded. Boundary-finding is inherently serial. To decode four instructions per cycle from a 16-byte block anyway, a wide x86 front end speculatively starts a decoder at every byte offset — up to 15 candidate starts — and discards those that land mid-instruction. That parallel-guess-and-discard is silicon and power spent recovering length information a fixed-length encoding hands over for free.
Code density. Variable length's advantage is bytes. The x86-64 encoding of
pushq %rbp is one byte (55); a register-to-register add is three; only
instructions hauling large constants stretch toward the maximum. Compiled x86-64
averages bytes per instruction against a
flat , so the same program occupies fewer bytes and more
of it fits in the instruction cache — a performance asset, not just a storage
saving. The RISC camp
conceded the point in its own way: ARM's Thumb and RISC-V's compressed extension
add 2-byte forms of the most common instructions, trading back a little decode
simplicity for density.
Pipeline friendliness. The load/store rule is the most consequential entry in
the RISC column. If arithmetic never touches memory, then every instruction needs the same
short list of resources in the same order — fetch, decode, read registers,
execute, maybe one memory access, write back — and the pipeline can be one clean
five-stage structure. A CISC instruction like addq (%rbx), %rax needs a memory
read and an ALU operation, and a memory-to-memory instruction would need even
more, so either the pipeline grows extra stages that most instructions waste, or
the control logic becomes a thicket of special cases. When this course builds a
pipelined processor,
the regularity of its RISC-flavored ISA is precisely what makes the design fit in
a lesson.
Why x86-64 survives
By the pricing above, x86-64 should have lost: it drags four decades'-worth of accumulated encodings behind it and pays the full decode tax on every fetch. It survives because modern implementations stopped executing x86 directly. The decoder at the front of every high-end x86 chip translates each incoming instruction into one or more simple, fixed-format internal operations called micro-ops, and everything past the decoder is a RISC-style machine executing those.
The translation is easy to picture on a real instruction. addq 8(%rbx), %rax
is one x86-64 instruction that reads memory and adds; the decoder splits it into a
load micro-op that fetches the memory operand into an internal temporary, and an
add micro-op that combines the temporary with %rax. Simple instructions map one
to one; the baroque ones fall back to a lookup ROM that emits longer micro-op
sequences. Decoded micro-ops are even cached, so a hot loop skips the x86 decode
tax entirely on repeat passes.
Count the work to see the split pay off. A four-instruction x86-64 loop body expands to fixed-format micro-ops:
| x86-64 instruction | Micro-ops |
|---|---|
movq (%rsi,%rcx,8), %rax | address-compute + load |
addq %rax, %rdx | add |
decq %rcx | dec |
jne | branch |
Four architectural instructions become six micro-ops, each doing exactly one register-to-register operation, one memory access, or one branch — the shape a five-stage RISC pipeline was built for. The CISC encoding lives only long enough to be translated; from the micro-op queue onward the machine does RISC work, and on the loop's second pass even translation is skipped, the micro-ops having been cached the first pass.
So the contract is CISC and the machine underneath is essentially RISC, which is the neatest possible demonstration of the architecture/microarchitecture split: the entire translation layer is microarchitecture, invisible to every program ever compiled. The reason vendors pay for that layer rather than switching contracts is economic, not technical. The installed base of x86-64 binaries — operating systems, decades of commercial software nobody can recompile — is worth more than the silicon the decoder costs. Pure RISC designs, meanwhile, dominate phones and embedded systems and have moved into laptops and servers, where fixed-length decoding saves power and no comparable binary legacy holds the door shut.
The RISC resurgence and an open ISA
CS:APP presents the RISC/CISC debate as settled history — the x86-64 it targets won the desktop, and the argument moved inside the decoder. The decade since has reopened the question at both ends of the market, and in a way the pricing above predicts exactly.
Apple Silicon. In 2020 Apple replaced Intel x86-64 in its laptops with the M1, a chip built on the ARM architecture — fixed-length, load/store, the RISC column of the table.1 The pricing predicts why this was possible now and not before. Fixed 4-byte instructions let the M1's front end decode eight instructions per cycle without the speculative boundary-guessing an x86 front end pays for, a width that is brutally expensive on variable-length code and nearly free on fixed-length. The one thing that had held ARM off the laptop for years was the binary legacy — decades of x86 software — and Apple bridged it with Rosetta 2, which translates x86-64 binaries to ARM ahead of time. That is the same move x86 makes internally (translate one ISA into another), lifted up into software and run once at install time rather than continuously in hardware.
RISC-V. The other development is that the RISC idea became an open standard
anyone may implement without a license. RISC-V, whose base integer ISA was ratified
in 2019, is a clean modern take on the RISC column: fixed 4-byte instructions, a
load/store discipline, a small base set, and — conceding exactly the density point
priced above — an optional compressed extension (C
) adding 2-byte forms of the
commonest instructions.2 The compressed extension is the design space diagram
made real: RISC-V starts at classic-RISC decode simplicity and buys back some density
by adding a second instruction length, landing where compressed RISC
sits on the
plot. That an ISA can be a public specification with dozens of independent
implementations, rather than one company's product, is the architecture/
microarchitecture split taken to its limit — the contract belongs to no one, and the
implementations compete freely beneath it.
With the idea of a contract in hand, the next lesson opens it up: how an instruction actually encodes an operation and its operands, and how many operands a machine even names, in instruction formats and operands.
Footnotes
- The Apple M1 (2020) implements the ARMv8-A architecture, a fixed-length load/store (RISC) ISA; its wide instruction decoder and the Rosetta 2 x86-64 translation layer are documented in Apple's platform materials and analyzed in detail by Johlin and others in the trade press. The general point — that fixed-length encoding enables very wide decode cheaply — is the code-density/ decode-complexity trade of CS:APP §4.1 applied to a modern part. ↩
- A. Waterman and K. Asanović, eds., The RISC-V Instruction Set Manual,
Volume I: Unprivileged ISA (RISC-V International). The base integer ISA (RV32I/
RV64I) was ratified in 2019; the
C
compressed extension adds 16-bit encodings of common instructions, trading a second instruction length for code density exactly as ARM's Thumb does. ↩
╌╌ END ╌╌