---
title: What an ISA Is
module: Instruction Set Architecture
moduleNumber: 2
lessonNumber: 1
order: 201
summary: >
  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.
topics: [Instruction Set Architecture]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §4.1 The Y86-64 Instruction Set Architecture"
  - book: Bistriceanu
    ref: "Computer Architecture Notes — §3 Instruction Set Design"
---

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.

$$
% caption: The ISA as a horizontal contract. Everything above it — applications,
% caption: compilers, operating systems — is written to target the ISA; every
% caption: microarchitecture below it is one hardware implementation of the same
% caption: contract, free to differ in speed, power, and internal cleverness.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  sw/.style={draw, minimum width=52mm, minimum height=7mm, align=center},
  hw/.style={draw, minimum width=24mm, minimum height=9mm, align=center,
             fill=acc!8}]
  \definecolor{acc}{HTML}{2348F2}
  % software stack
  \node[sw] (app) at (0,2.7) {applications};
  \node[sw] (cmp) at (0,1.8) {compilers, runtimes, OS};
  % the contract bar
  \node[draw=acc, thick, text=acc, fill=acc!8,
        minimum width=58mm, minimum height=8mm, align=center] (isa) at (0,0.6)
        {\textbf{instruction set architecture}};
  % microarchitectures
  \node[hw] (m1) at (-2.0,-1.0) {in-order\\core};
  \node[hw] (m2) at ( 0.0,-1.0) {out-of-order\\core};
  \node[hw] (m3) at ( 2.0,-1.0) {low-power\\core};
  % connect software down to the bar, bar down to hardware
  \draw[->] (cmp) -- (isa) node[midway, right, text=acc, font=\scriptsize] {targets};
  \draw[->] (isa.south) -- (m1.north);
  \draw[->] (isa.south) -- (m2.north);
  \draw[->] (isa.south) -- (m3.north);
  \node[text=acc, font=\scriptsize] at (3.6,-1.0) {implement};
\end{tikzpicture}
$$

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 $I_t$ be the instruction set a chip generation
$t$ decodes. **Backward** compatibility — new hardware running old binaries —
requires $I_{t-1} \subseteq I_t$, 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 $I_{t} \setminus
I_{t-1}$ (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.

> **Definition (Instruction set architecture).** The programmer-visible model of a
> processor: the set of instructions it executes, their binary encodings, and the
> registers, condition codes, and memory they read and write. The ISA is the
> abstraction a compiler targets and a hardware designer is obligated to implement.

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."

> **Definition (Microarchitecture).** A specific hardware implementation of an ISA:
> the pipeline depth, the number and kind of functional units, the cache geometry,
> the branch predictor, the register-renaming machinery — every internal choice
> that affects speed or power but not the visible result of running a program.

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](/computer-architecture/pipelining/pipelining-principles) 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 $W$ instructions.
All three are x86-64, so they fetch the _same_ bytes and produce the _same_ sum —
the architecture holding. Only the cycle count $C = \text{CPI} \cdot W$ 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 | $3.1\,W$ |
| B | out-of-order, wide issue | $1.2\,W$ |
| C | out-of-order, larger LLC | $0.9\,W$ |

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.

$$
% caption: One architecture, three microarchitectures. The same x86-64 program (W
% caption: instructions) produces the same result on all three chips, so the
% caption: architecture is fixed. Only the running time differs — in-order with a
% caption: small cache is slowest, out-of-order faster, out-of-order with a big
% caption: cache faster still — and the program cannot tell which one it ran on.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  chip/.style={draw, minimum width=40mm, minimum height=8mm, align=center},
  bar/.style={fill=acc!8, draw=acc, thick}]
  \definecolor{acc}{HTML}{2348F2}
  \node[chip] (a) at (0,0)    {chip A: in-order,\\small cache};
  \node[chip] (b) at (0,-1.2) {chip B: out-of-order};
  \node[chip] (c) at (0,-2.4) {chip C: out-of-order,\\big cache};
  % bars, length proportional to cycles
  \draw[bar] (2.5,-0.25) rectangle (8.7,0.25);
  \draw[bar] (2.5,-1.45) rectangle (5.4,-0.95);
  \draw[bar] (2.5,-2.65) rectangle (4.5,-2.15);
  \node[anchor=west, text=acc, font=\scriptsize] at (8.85,0)    {slowest};
  \node[anchor=west, text=acc, font=\scriptsize] at (5.55,-1.2) {faster};
  \node[anchor=west, text=acc, font=\scriptsize] at (4.65,-2.4) {fastest};
  \node[anchor=north, font=\scriptsize] at (5.6,-3.1)
        {same $W$ instructions, same result, di\/f\/ferent time};
\end{tikzpicture}
$$

## 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.

$$
% caption: RISC versus CISC as opposite answers to one question — where complexity
% caption: lives. RISC pushes it up into the compiler and keeps instructions short,
% caption: fixed-length, and register-only; CISC pushes it down into rich,
% caption: variable-length instructions that can compute on memory directly.
\begin{tikzpicture}[font=\footnotesize,
  hd/.style={draw, minimum width=40mm, minimum height=7mm, align=center,
             fill=acc!8, text=acc, thick},
  row/.style={align=left, text width=38mm}]
  \definecolor{acc}{HTML}{2348F2}
  \node[hd] (r) at (0,0)   {RISC};
  \node[hd] (c) at (5.2,0) {CISC};
  \foreach \y/\rt/\ct in {
    -0.95/{f\/ixed length (4 B)}/{variable length (1-15 B)},
    -1.70/{few, simple ops}/{many, complex ops},
    -2.45/{load/store only}/{ops compute on memory},
    -3.20/{many registers}/{fewer registers},
    -3.95/{complexity in compiler}/{complexity in hardware}} {
    \node[row] at (0,\y) {\rt};
    \node[row] at (5.2,\y) {\ct};
  }
  \draw (-2.1,-0.5) -- (7.3,-0.5);
  \draw (2.55,0.45) -- (2.55,-4.35);
\end{tikzpicture}
$$

## 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 $L$, instruction $k$ of a
fetched block starts at byte $L k$ — the boundaries are known before a single
opcode is read, so a block of $n$ instructions feeds $n$ decoders in parallel.
Variable length destroys this: the start of instruction $k{+}1$ is
$$\text{start}(k{+}1) = \text{start}(k) + \text{len}(k),$$
and on x86 $\text{len}(k)$ depends on optional prefixes, the opcode, and the
addressing-mode byte, so it is not known until instruction $k$ 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 $\bar{b}_{\text{x86}} \approx 3\text{–}4$ bytes per instruction against a
flat $\bar{b}_{\text{RISC}} = 4$, 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](/computer-architecture/pipelining/pipelining-principles),
the regularity of its RISC-flavored ISA is precisely what makes the design fit in
a lesson.

$$
% caption: The design space, qualitatively. Fixed-length RISC buys cheap decode at
% caption: a cost in bytes; variable-length CISC packs code tighter and pays in
% caption: decode hardware. Compressed RISC variants (Thumb, RVC) split the
% caption: difference. Y86-64's variable-length bytes cost it slightly more decode
% caption: than classic RISC, and it spends bytes freely.
\begin{tikzpicture}[font=\footnotesize, >=stealth]
  \definecolor{acc}{HTML}{2348F2}
  % axes
  \draw[->] (0,0) -- (8.6,0);
  \draw[->] (0,0) -- (0,5.2);
  \node[anchor=north] at (4.3,-0.15) {decode complexit\/y};
  \node[anchor=south, rotate=90] at (-0.25,2.6) {code density};
  % points
  \fill[acc] (2.1,1.1) circle (2pt);
  \node[anchor=west] at (2.35,1.1) {Y86-64};
  \fill[acc] (1.9,2.6) circle (2pt);
  \node[anchor=west] at (2.15,2.6) {classic RISC (4 B f\/ixed)};
  \fill[acc] (3.4,3.7) circle (2pt);
  \node[anchor=west] at (3.65,3.7) {compressed RISC (\texttt{Thumb,} RVC)};
  \fill[acc] (7.0,4.5) circle (2pt);
  \node[anchor=east] at (6.75,4.5) {x86-64};
\end{tikzpicture}
$$

## 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.

$$
% caption: How x86-64 survives. The visible ISA stays CISC, but the decoder
% caption: translates each instruction into fixed-format micro-ops, and everything
% caption: past the front end is a RISC-style engine. addq 8(%rbx),%rax becomes a
% caption: load micro-op feeding an add micro-op.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  box/.style={draw, minimum width=30mm, minimum height=10mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (ins) at (0,0) {\texttt{addq 8(\%rbx),\%rax}\\CISC instruction};
  \node[box, fill=acc!8, text=acc, thick, minimum width=20mm] (dec) at (4.4,0)
       {decoder};
  \node[box] (u1) at (8.6,0.9) {load: $T = M[8 + \mathtt{rbx}]$};
  \node[box] (u2) at (8.6,-0.9) {add: $\mathtt{rax} = \mathtt{rax} + T$};
  \node[box, fill=acc!8, minimum width=22mm, minimum height=14mm] (core)
       at (12.9,0) {RISC-style\\execution core};
  \draw[->] (ins) -- (dec);
  \draw[->] (dec.east) -- (u1.west);
  \draw[->] (dec.east) -- (u2.west);
  \draw[->] (u1.east) -- (core.west);
  \draw[->] (u2.east) -- (core.west);
  \node[text=acc, font=\scriptsize, anchor=south] at (8.6,1.75) {micro-ops};
\end{tikzpicture}
$$

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.[^m1] 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.[^riscv] 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.

> **Takeaway.** An ISA is the stable contract between software and hardware: the
> instructions, encodings, and state a program may depend on. The _architecture_ is
> that contract; a _microarchitecture_ is one implementation of it, free to differ
> in everything but visible results. RISC and CISC are opposite answers to where
> complexity should live, and the price list is concrete: variable length buys
> code density and pays in decode hardware; fixed length and load/store buy cheap
> decode and a clean pipeline. x86-64 survives by translating its CISC contract
> into RISC micro-ops internally — while Apple Silicon and the open RISC-V
> standard show the fixed-length side of the same ledger paying off in new markets.

[^m1]: 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.
[^riscv]: **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.

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](/computer-architecture/instruction-set-architecture/instruction-formats-and-operands).
