---
title: The Y86-64 Instruction Set
module: Instruction Set Architecture
moduleNumber: 2
lessonNumber: 4
order: 204
summary: >
  Y86-64 is a teaching ISA — a stripped-down x86-64 simple enough to implement by
  hand yet real enough to compile to. We fix its programmer-visible state (fifteen
  registers, three condition codes, the PC, memory, and a status code), give the
  instruction set with exact byte encodings, spell out how the condition codes decide
  every jXX and cmovXX, and run the encoding both directions: assembly to bytes and
  raw bytes back to meaning.
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 — §5 CPU Implementation"
---

To build a processor from scratch — which the next module does — we need an ISA
small enough to hold in our heads and implement in logic, yet rich enough that real
programs compile to it. **Y86-64** is that ISA: a deliberate simplification of
x86-64 that keeps the same register names, the same little-endian byte order, and a
recognizable subset of the operations, while throwing out the hundreds of
instructions and irregular encodings that make x86 hard to implement by hand. This
lesson defines exactly what state a Y86-64 program can see and exactly how every
instruction is encoded in bytes — and getting those bytes right is essential,
because the processor we build later decodes them literally.

## The programmer-visible state

Y86-64's visible state is five things, and an instruction can touch nothing else.

$$
% caption: The Y86-64 programmer-visible state: fifteen 64-bit registers (no %r15),
% caption: three condition-code bits ZF/SF/OF, the program counter, byte-addressable
% caption: memory, and a status code Stat reporting whether execution is normal or
% caption: has halted or faulted.
\begin{tikzpicture}[font=\footnotesize,
  reg/.style={draw, minimum width=13mm, minimum height=6mm, inner sep=0pt},
  box/.style={draw, minimum width=20mm, minimum height=8mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  % registers grid 5 x 3
  \node[anchor=south west, font=\scriptsize] at (-0.3,1.55) {registers};
  \foreach \name/\c/\r in {
    {\%rax}/0/0, {\%rcx}/1/0, {\%rdx}/2/0, {\%rbx}/3/0, {\%rsp}/4/0,
    {\%rbp}/0/1, {\%rsi}/1/1, {\%rdi}/2/1, {\%r8}/3/1, {\%r9}/4/1,
    {\%r10}/0/2, {\%r11}/1/2, {\%r12}/2/2, {\%r13}/3/2, {\%r14}/4/2} {
    \node[reg] at (\c*1.45, -\r*0.75) {$\mathtt{\name}$};
  }
  % condition codes
  \node[anchor=south west, font=\scriptsize] at (8.0,1.55) {cond. codes};
  \node[reg, minimum width=7mm] at (8.4,0)   {ZF};
  \node[reg, minimum width=7mm] at (9.2,0)   {SF};
  \node[reg, minimum width=7mm] at (10.0,0)  {OF};
  % PC, Stat
  \node[box] at (8.9,-0.95) {PC};
  \node[box] at (8.9,-2.0)  {Stat};
  % memory
  \node[box, minimum width=66mm, minimum height=7mm] at (3.0,-3.1) {memory (byte-addressable)};
\end{tikzpicture}
$$

- The **fifteen integer registers** `%rax` through `%r14`, each 64 bits. These are
  x86-64's registers minus `%r15`, which Y86-64 omits so the register identifier
  needs exactly four bits with one code left over to mean "no register." `%rsp` is
  still the stack pointer.
- The **three condition codes** `ZF` (zero), `SF` (sign), and `OF` (overflow), each
  a single bit. An arithmetic instruction sets them from its result; the conditional
  moves and jumps read them. This is a leaner set than x86's flags, but enough for
  every signed comparison.
- The **program counter** `PC`, the address of the next instruction.
- **Memory**, the byte-addressable array, little-endian as always.
- The **status code** `Stat`, a small field reporting execution state: `AOK`
  (running normally), `HLT` (a `halt` instruction executed), `ADR` (an invalid
  memory address), or `INS` (an invalid instruction). The processor we build uses
  `Stat` to decide whether to keep running.

> **Definition (Y86-64).** A teaching instruction set architecture: a simplified
> x86-64 with fifteen 64-bit registers (`%rax`–`%r14`), condition codes `ZF`/`SF`/
> `OF`, a program counter, byte-addressable little-endian memory, and a status code.
> Its small, regular instruction set is designed to be implemented by hand.

## The instructions and their encodings

Every Y86-64 instruction begins with a single byte split into two nibbles: the high
nibble is the **instruction code** `icode`, naming the operation family, and the low
nibble is the **function code** `ifun`, distinguishing variants within a family (the
four arithmetic ops, the seven branch conditions, the seven conditional moves).
After that first byte come, when the instruction needs them, a **register-specifier
byte** holding two register IDs `rA:rB`, and an **8-byte constant** — an immediate
value, a displacement, or a destination address — always stored little-endian.

The register IDs are one nibble each: `%rax` 0, `%rcx` 1, `%rdx` 2, `%rbx` 3, `%rsp`
4, `%rbp` 5, `%rsi` 6, `%rdi` 7, `%r8` 8, `%r9` 9, `%r10` A, `%r11` B, `%r12` C,
`%r13` D, `%r14` E, and the special code **F meaning "no register."** When an
instruction has a register byte but uses only one register, the unused half is F.

$$
% caption: The Y86-64 instruction encodings. The first byte is icode:ifun; some
% caption: instructions add a register byte rA:rB, and some add an 8-byte little-
% caption: endian constant (value, displacement, or destination). Lengths run from
% caption: 1 byte (halt, nop, ret) to 10 bytes (irmovq, rmmovq, mrmovq).
\begin{tikzpicture}[font=\scriptsize,
  c/.style={draw, minimum width=8mm, minimum height=6mm, inner sep=0pt},
  v/.style={draw, minimum width=24mm, minimum height=6mm, inner sep=0pt, fill=acc!8}]
  \definecolor{acc}{HTML}{2348F2}
  \def\rh{0.78}
  \foreach \name/\y in {
    {halt}/0,
    {nop}/1,
    {rrmovq/cmovXX \texttt{rA},\texttt{rB}}/2,
    {irmovq \texttt{V},\texttt{rB}}/3,
    {rmmovq \texttt{rA},D(\texttt{rB})}/4,
    {mrmovq D(\texttt{rB}),\texttt{rA}}/5,
    {OPq \texttt{rA},\texttt{rB}}/6,
    {jXX \texttt{Dest}}/7,
    {call \texttt{Dest}}/8,
    {ret}/9,
    {pushq \texttt{rA}}/10,
    {popq \texttt{rA}}/11} {
    \node[anchor=west, font=\ttfamily\footnotesize] at (-3.8,-\y*\rh) {\name};
  }
  % draw byte cells for each row
  \foreach \y in {0,...,11} {
    \node[c] at (0,-\y*\rh) {};
  }
  % fill content per row
  \node at (0,0)        {$\mathtt{0\,0}$};
  \node at (0,-\rh)     {$\mathtt{1\,0}$};
  \node at (0,-2*\rh)   {$\mathtt{2\,fn}$};\node[c] at (0.9,-2*\rh){$\mathtt{rA\,rB}$};
  \node at (0,-3*\rh)   {$\mathtt{3\,0}$};\node[c] at (0.9,-3*\rh){$\mathtt{F\,rB}$};\node[v] at (2.5,-3*\rh){$V$ (8 B)};
  \node at (0,-4*\rh)   {$\mathtt{4\,0}$};\node[c] at (0.9,-4*\rh){$\mathtt{rA\,rB}$};\node[v] at (2.5,-4*\rh){$D$ (8 B)};
  \node at (0,-5*\rh)   {$\mathtt{5\,0}$};\node[c] at (0.9,-5*\rh){$\mathtt{rA\,rB}$};\node[v] at (2.5,-5*\rh){$D$ (8 B)};
  \node at (0,-6*\rh)   {$\mathtt{6\,fn}$};\node[c] at (0.9,-6*\rh){$\mathtt{rA\,rB}$};
  \node at (0,-7*\rh)   {$\mathtt{7\,fn}$};\node[v] at (1.65,-7*\rh){$Dest$ (8 B)};
  \node at (0,-8*\rh)   {$\mathtt{8\,0}$};\node[v] at (1.65,-8*\rh){$Dest$ (8 B)};
  \node at (0,-9*\rh)   {$\mathtt{9\,0}$};
  \node at (0,-10*\rh)  {$\mathtt{A\,0}$};\node[c] at (0.9,-10*\rh){$\mathtt{rA\,F}$};
  \node at (0,-11*\rh)  {$\mathtt{B\,0}$};\node[c] at (0.9,-11*\rh){$\mathtt{rA\,F}$};
\end{tikzpicture}
$$

The function codes fill in the variants. For the arithmetic family `OPq` the `ifun`
selects the operation; for `jXX` and `cmovXX` it selects the condition, and the
two families number their conditions identically.

| Family | `icode` | `ifun` values |
| --- | --- | --- |
| `OPq` | 6 | `addq` 0, `subq` 1, `andq` 2, `xorq` 3 |
| `jXX` | 7 | `jmp` 0, `jle` 1, `jl` 2, `je` 3, `jne` 4, `jge` 5, `jg` 6 |
| `cmovXX` | 2 | `rrmovq` 0, `cmovle` 1, `cmovl` 2, `cmove` 3, `cmovne` 4, `cmovge` 5, `cmovg` 6 |

The `rrmovq` row is worth a pause: an unconditional move is just the "always" case
of conditional move, so it shares `icode` 2 with `ifun` 0. The full one-byte
opcodes are then `halt` 0:0, `nop` 1:0, `irmovq` 3:0, `rmmovq` 4:0, `mrmovq` 5:0,
`call` 8:0, `ret` 9:0, `pushq` A:0, `popq` B:0.

The stack and procedure instructions carry their exact semantics in the encoding
table. `pushq rA` decrements `%rsp` by 8, then writes `R[rA]` at the new top:
$R[\texttt{rsp}] \leftarrow R[\texttt{rsp}] - 8$, then
$M[R[\texttt{rsp}]] \leftarrow R[\texttt{rA}]$. `popq rA` reads the top into `rA`
and increments `%rsp` by 8. `call Dest` pushes the return address (the address of
the instruction after the `call`) and sets the PC to `Dest`; `ret` pops that
address back into the PC. One convention question the encoding forces: `pushq
%rsp` pushes the _old_ value of `%rsp`, the value before the decrement — the ISA
must pick an order, and Y86-64 follows x86-64's choice.

## Condition codes: how jXX and cmovXX decide

Only the four `OPq` instructions touch the condition codes. Each one sets all
three from its result $t$: `ZF` if $t = 0$, `SF` if $t < 0$ (the sign bit of $t$),
and `OF` if the operation overflowed two's-complement range (for `andq` and
`xorq`, `OF` is always cleared). Everything conditional in Y86-64 — every jump,
every conditional move — is a Boolean function of those three bits, evaluated at
the moment the `jXX` or `cmovXX` executes.

Y86-64 has no compare instruction, so the comparison idiom is subtraction: to ask
"how does `%rbx` stand relative to `%rax`?", compute `subq %rax, %rbx`, which sets
the codes from $t = R[\texttt{rbx}] - R[\texttt{rax}]$ (and, unlike x86's `cmpq`,
overwrites `%rbx` with $t$ — the price of the smaller instruction set). The six
conditions then read:

| Condition | `ifun` | Formula | After `subq rA, rB`, true when |
| --- | --- | --- | --- |
| `le` | 1 | $(\text{SF} \oplus \text{OF}) \lor \text{ZF}$ | $R[\texttt{rB}] \le R[\texttt{rA}]$ |
| `l` | 2 | $\text{SF} \oplus \text{OF}$ | $R[\texttt{rB}] < R[\texttt{rA}]$ |
| `e` | 3 | $\text{ZF}$ | $R[\texttt{rB}] = R[\texttt{rA}]$ |
| `ne` | 4 | $\lnot\text{ZF}$ | $R[\texttt{rB}] \ne R[\texttt{rA}]$ |
| `ge` | 5 | $\lnot(\text{SF} \oplus \text{OF})$ | $R[\texttt{rB}] \ge R[\texttt{rA}]$ |
| `g` | 6 | $\lnot(\text{SF} \oplus \text{OF}) \land \lnot\text{ZF}$ | $R[\texttt{rB}] > R[\texttt{rA}]$ |

The exclusive-or is the interesting part. Naively, "less than" should just be
"result negative" ($\text{SF}$), and usually it is: with `%rbx` = 3 and `%rax` = 5,
the subtraction gives $t = -2$, so $\text{SF} = 1$, $\text{OF} = 0$, and
$\text{SF} \oplus \text{OF} = 1$ says 3 < 5, correctly. But subtraction can
overflow. With `%rbx` holding the most negative value $-2^{63}$ and `%rax` = 1,
the true difference $-2^{63} - 1$ is not representable; the computed $t$ wraps to
$+2^{63} - 1$, a _positive_ number, so $\text{SF} = 0$. The overflow bit corrects
the comparison: $\text{OF} = 1$, and $\text{SF} \oplus \text{OF} = 1$ still
reports "less than," which is the truth. The xor folds the overflow correction
into one gate, exactly the kind of trick that makes the
[ALU](/computer-architecture/digital-logic/multiplexers-decoders-and-the-alu)
cheap to build.

For example, trace one comparison through the whole chain.
Suppose `%rbx` holds 7 and `%rax` holds 12, and the code runs `subq %rax, %rbx`
followed by `jl target`. The subtraction computes $t = 7 - 12 = -5$. From $t$: it is
nonzero, so $\text{ZF} = 0$; it is negative, so $\text{SF} = 1$; and $-5$ is well
inside two's-complement range, so no overflow, $\text{OF} = 0$. The `jl` condition is
$\text{SF} \oplus \text{OF} = 1 \oplus 0 = 1$, so the branch is taken — correctly,
since $7 < 12$. Here `SF` carried the sign of the difference, and
`OF` (zero) confirmed no overflow corrupted it.

$$
% caption: A full condition-code trace for subq %rax,%rbx with %rbx = 7, %rax = 12,
% caption: then jl. The result -5 sets ZF = 0, SF = 1, OF = 0; the jl formula
% caption: SF xor OF evaluates to 1, so the branch is taken, matching 7 < 12.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  b/.style={draw, minimum width=26mm, minimum height=8mm, align=center},
  fl/.style={draw, minimum width=15mm, minimum height=7mm, align=center,
             font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \node[b] (sub) at (0,0) {\texttt{t = 7 - 12 = -5}};
  \node[anchor=south, font=\scriptsize] at (0,0.5) {\texttt{subq \%rax,\%rbx}};
  \node[fl] (zf) at (3.6,0.85)  {\texttt{ZF = 0}};
  \node[fl] (sf) at (3.6,0.0)   {\texttt{SF = 1}};
  \node[fl] (of) at (3.6,-0.85) {\texttt{OF = 0}};
  \node[b, fill=acc!8, draw=acc, thick, text=acc, minimum width=34mm] (jl) at (7.6,0)
        {\texttt{SF xor OF = 1}};
  \node[anchor=south, font=\scriptsize, text=acc] at (7.6,0.5) {\texttt{jl}: taken};
  \draw[->] (sub.east) -- (2.1,0.85) -- (zf.west);
  \draw[->] (sub.east) -- (sf.west);
  \draw[->] (sub.east) -- (2.1,-0.85) -- (of.west);
  \draw[->, acc] (sf.east) -- (5.5,0.0) -- (jl.west);
  \draw[->, acc] (of.east) -- (5.5,-0.4) -- (5.5,0.0);
\end{tikzpicture}
$$

`cmovXX rA, rB` applies the same conditions to data movement: if the condition
holds, `R[rB]` gets `R[rA]`; if not, the instruction is a no-op. Either way the
condition codes are untouched — conditional moves read them, never write them. The
point of `cmovXX` is to replace a short branch (test, jump around a move) with
straight-line code, an idiom whose real payoff appears when
[branch mispredictions](/computer-architecture/pipelining/control-hazards-and-branch-prediction)
start costing pipeline bubbles.

## Encode: from assembly to bytes

The encoding rules are easiest to trust once you have walked a real instruction
byte by byte. Take `irmovq $0x100, %rax` — move the immediate `0x100` into `%rax`.
Its `icode:ifun` is `3:0`. `irmovq` carries a register byte with no source register,
so `rA` is F and `rB` is the destination `%rax` = 0, giving `F0`. Then the 8-byte
value `0x100`, little-endian, is `00 01 00 00 00 00 00 00`.

$$
% caption: Encoding irmovq $0x100,%rax into its 10 bytes. Byte 0 is icode:ifun = 30;
% caption: the register byte is F:0 (no source, destination %rax); the 8-byte value
% caption: 0x100 is stored little-endian, low byte 00 first, then 01.
\begin{tikzpicture}[font=\footnotesize,
  by/.style={draw, minimum width=9mm, minimum height=8mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  \node[by] at (0,0) {$\mathtt{30}$};
  \node[by] at (1.0,0) {$\mathtt{f0}$};
  \foreach \i/\v in {2/00, 3/01, 4/00, 5/00, 6/00, 7/00, 8/00, 9/00} {
    \node[by] at (\i*1.0,0) {$\mathtt{\v}$};
  }
  \node[anchor=south, font=\scriptsize] at (0,0.55) {icode:ifun};
  \node[anchor=south, font=\scriptsize] at (1.0,1.1) {rA:rB $=$ F:0};
  \draw (1.0,0.45) -- (1.0,1.05);
  \draw (1.55,0.55) -- (1.55,0.7) -- (9.45,0.7) -- (9.45,0.55);
  \node[anchor=south, font=\scriptsize] at (5.5,0.72)
        {value $\mathtt{0x100}$, little-endian};
\end{tikzpicture}
$$

Now a memory store. The reference gives `rmmovq %rsi, 0x8(%rbx)` as
`40 63 08 00 00 00 00 00 00 00`: `icode:ifun` = `4:0`, register byte `rA:rB` =
`%rsi`:`%rbx` = `6:3`, then the displacement `0x8` little-endian. The same shape
holds for `rmmovq %rax, 0x8(%rsp)`: `40`, then `rA:rB` = `%rax`:`%rsp` = `0:4`
giving `04`, then `08 00 00 00 00 00 00 00`. The operand order is worth pinning
down: for `rmmovq rA, D(rB)` the source register is `rA` and the destination is
the memory at `D + R[rB]`; `mrmovq` reverses it, `mrmovq D(rB), rA`, loading from
memory into `rA`, but the byte layout — `icode:ifun`, then `rA:rB`, then `D` — is
identical.

$$
% caption: rmmovq %rsi,0x8(%rbx) in 10 bytes. Byte 0 = 40 (icode 4, ifun 0); the
% caption: register byte 63 names source %rsi (6) and base %rbx (3); the 8-byte
% caption: displacement 0x8 follows little-endian, 08 then seven zero bytes.
\begin{tikzpicture}[font=\footnotesize,
  by/.style={draw, minimum width=9mm, minimum height=8mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  \node[by] at (0,0) {$\mathtt{40}$};
  \node[by] at (1.0,0) {$\mathtt{63}$};
  \foreach \i/\v in {2/08, 3/00, 4/00, 5/00, 6/00, 7/00, 8/00, 9/00} {
    \node[by] at (\i*1.0,0) {$\mathtt{\v}$};
  }
  \node[anchor=south, font=\scriptsize] at (0,0.55) {icode:ifun};
  \node[anchor=south, font=\scriptsize] at (1.0,1.1) {rA:rB $=$ 6:3};
  \draw (1.0,0.45) -- (1.0,1.05);
  \draw (1.55,0.55) -- (1.55,0.7) -- (9.45,0.7) -- (9.45,0.55);
  \node[anchor=south, font=\scriptsize] at (5.5,0.72)
        {displacement $\mathtt{0x8}$, little-endian};
\end{tikzpicture}
$$

## Decode: from raw bytes back to meaning

The processor runs the same walk in reverse, and so should you. Suppose the fetch
unit reads these bytes at the current PC:

```
50 15 f8 ff ff ff ff ff ff ff
```

Byte 0 splits into `icode` = 5, `ifun` = 0: an `mrmovq`, so the instruction is 10
bytes long and has both a register byte and a displacement. Byte 1 is `15`:
`rA` = 1 = `%rcx` (the destination of a load) and `rB` = 5 = `%rbp` (the base).
Bytes 2–9, read little-endian, assemble to `0xfffffffffffffff8`, which as a signed
64-bit value is $-8$. The instruction is `mrmovq -8(%rbp), %rcx`: load the 8 bytes
at `R[%rbp]` $-$ 8 into `%rcx`. A negative displacement is nothing special — it is
just a constant whose high bytes are `ff`, and reading `f8 ff ff ...` backwards as
`0xff...f8` is the little-endian discipline from the
[foundations lesson](/computer-architecture/foundations/bits-bytes-and-words)
in action.

$$
% caption: Decoding raw bytes. 50 15 f8 ff ... ff splits into icode 5, ifun 0
% caption: (mrmovq), rA = 1 (%rcx), rB = 5 (%rbp), and the little-endian constant
% caption: 0xfffffffffffffff8 = -8, giving mrmovq -8(%rbp),%rcx.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  by/.style={draw, minimum width=9mm, minimum height=8mm, inner sep=0pt},
  fld/.style={draw, minimum width=17mm, minimum height=8mm, align=center,
              font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \node[by] at (0,0) {$\mathtt{50}$};
  \node[by] at (1.0,0) {$\mathtt{15}$};
  \foreach \i/\v in {2/f8, 3/ff, 4/ff, 5/ff, 6/ff, 7/ff, 8/ff, 9/ff} {
    \node[by] at (\i*1.0,0) {$\mathtt{\v}$};
  }
  % field boxes below
  \node[fld] (f1) at (0,-1.8)   {icode 5, ifun 0\\= mrmovq};
  \node[fld] (f2) at (2.6,-1.8) {rA 1 = \texttt{\%rcx}\\rB 5 = \texttt{\%rbp}};
  \node[fld, fill=acc!8, text=acc, minimum width=30mm] (f3) at (6.2,-1.8)
        {valC $=$ \texttt{0xff...f8}\\$=$ \texttt{-8}};
  \draw[->, black] (0,-0.45) -- (f1.north);
  \draw[->, black] (1.0,-0.45) -- (f2.north);
  \draw[->, black] (5.5,-0.45) -- (f3.north);
  \node[anchor=west, text=acc] at (8.6,-1.8)
        {\texttt{mrmovq -8(\%rbp),\%rcx}};
\end{tikzpicture}
$$

Notice what made the walk possible: the first byte alone told us the instruction's
length. That is by design, and it is the property the fetch stage leans on. From
the `icode`, two yes/no questions — does this instruction have a register byte?
does it have an 8-byte constant? — fix the length as
$1 + r + 8c$ with $r, c \in \{0, 1\}$: lengths 1, 2, 9, or 10, and nothing else.

$$
% caption: Instruction length from the first byte. The icode determines two bits -
% caption: register byte present (r) and 8-byte constant present (c) - and the
% caption: length is 1 + r + 8c: either 1, 2, 9, or 10 bytes. Fetch needs nothing
% caption: past byte 0 to know where the next instruction starts.
\begin{tikzpicture}[font=\footnotesize, >=stealth,
  q/.style={draw, minimum width=26mm, minimum height=9mm, align=center},
  len/.style={draw, minimum width=10mm, minimum height=8mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[q, fill=acc!8, text=acc, thick] (ic) at (0,0) {icode};
  \node[q] (rb) at (4.2,0.9) {register byte?\\$r = 0$ or $1$};
  \node[q] (vc) at (4.2,-0.9) {8-byte constant?\\$c = 0$ or $1$};
  \node[q, minimum width=30mm] (sum) at (8.6,0) {length $= 1 + r + 8c$};
  \draw[->] (ic.east) -- (rb.west);
  \draw[->] (ic.east) -- (vc.west);
  \draw[->] (rb.east) -- (sum.west);
  \draw[->] (vc.east) -- (sum.west);
  \node[len, minimum width=24mm] (lens) at (12.6,0) {1, 2, 9, 10};
  \node[anchor=north, font=\scriptsize] at (12.6,-0.55) {the only lengths};
  \draw[->] (sum.east) -- (lens.west);
\end{tikzpicture}
$$

Contrast that with x86-64, where the length can depend on everything up to and
including the addressing-mode byte, and the appeal of Y86-64 as a machine to
implement by hand is plain. These layouts are what the fetch stage of the
[sequential processor](/computer-architecture/processor-design/the-fetch-decode-execute-cycle)
reads to pull out its fields. With the encodings fixed, the
[next lesson](/computer-architecture/instruction-set-architecture/y86-64-programming)
writes complete Y86-64 programs and watches the assembler turn assembly text into
exactly these bytes.

## How real ISAs handle conditions

Y86-64's condition-code design is a faithful miniature of x86's, and comparing it to
what other real ISAs do shows the design space the simplification sits inside.

**x86's extra flag, and the missing `cmpq`.** x86-64 has a fourth arithmetic flag
Y86-64 drops: the **carry flag** `CF`, needed for _unsigned_ comparisons (unsigned
"below" is `CF`, where signed "less" is $\text{SF} \oplus \text{OF}$). Y86-64 keeps
only the three signed-comparison flags, so it can compare signed integers but has no
clean unsigned test — an honest simplification CS:APP notes. x86 also keeps a
non-destructive `cmpq`/`testq` pair that set flags _without_ writing a result, which
is why real code rarely pays Y86-64's "the subtraction clobbers a register" tax; the
`andq x, x` and `subq` idioms this module leans on are Y86-64 working around a
compare instruction it deliberately omits.

**ARM's condition flags and full predication.** ARM carries the same four flags
(`N`, `Z`, `C`, `V` — sign, zero, carry, overflow) but historically went much
further than `cmovXX`: in 32-bit ARM, _almost every_ instruction could be
**predicated**, carrying a 4-bit condition field so that `ADDNE`, `MOVGT`, and the
like execute only when the flags match.[^armpred] A whole `if`-body could be
straight-line predicated code with no branch at all — Y86-64's conditional-move idea
generalized to the entire instruction set. AArch64 (64-bit ARM) pulled most of this
back, keeping predication only for a handful of select and conditional-compare
instructions, because full predication wastes an encoding bit on every instruction
and modern branch predictors made the branch-avoidance payoff smaller. The arc —
add predication for the pipeline, then retreat when prediction improved — is the same
cost-driven revisiting seen throughout these lessons.

**The XOR trick is universal.** The one piece of Y86-64's condition logic that is not
a simplification at all is $\text{SF} \oplus \text{OF}$ for signed "less than." That
exact Boolean is how x86, ARM, RISC-V comparison sequences, and essentially every
two's-complement machine define the signed-less-than condition, for the exact
overflow reason the lesson traced. It is one of the small pieces of the teaching ISA
that is bit-for-bit identical to the production ones.

> **Takeaway.** Y86-64's visible state is fifteen registers (`%rax`–`%r14`), the
> condition codes `ZF`/`SF`/`OF`, the `PC`, memory, and `Stat`. Every instruction
> starts with an `icode:ifun` byte, optionally followed by a register byte `rA:rB`
> (unused half = F) and an 8-byte little-endian constant, so lengths are 1, 2, 9,
> or 10 and the first byte fixes the length. `OPq` sets the condition codes; every
> `jXX` and `cmovXX` is a Boolean formula over them, with $\text{SF} \oplus
> \text{OF}$ giving signed "less than" even under overflow — the same formula every
> two's-complement machine uses. The encodings run both ways: `irmovq $0x100,%rax`
> is `30 f0 00 01 ...`, and `50 15 f8 ff ...` decodes back to `mrmovq -8(%rbp),%rcx`.

[^armpred]: 32-bit ARM (ARMv7 and earlier) encodes a 4-bit condition field in nearly
    every instruction, allowing full **predication** — an instruction executes only if
    the condition-flag test passes; see the _ARM Architecture Reference Manual_,
    condition-code section. AArch64 removed general predication, retaining conditional
    select (`CSEL`) and conditional compare. The idea is the generalization of the
    conditional move CS:APP §4.1 introduces as `cmovXX`.
