◈ computers mapVol 1 · Ch 16/19
How Computers Work from the transistor up · chapter 16

16Programs

Last chapter the machine finally started running itself. A ROM read the situation and raised the control lines, fetching and executing with no hand on the panel. And I left you standing on a small cliff. The control unit decodes bytes it pulls from RAM. But the numbers those instructions add and store are also just bytes in RAM. Same wires, same cells, same 0s and 1s. So which ones are the program, and which ones are the data? How does the machine tell them apart? Stop there and answer it before you read the next line. You wired every path in this thing yourself, so name the wire that checks. Take the guess seriously. It costs you nothing, and it is about to be worth a lot. Ready? Here is the answer, and it's the vertiginous idea this whole chapter turns on: it can't, because there is no difference. A byte has no type stamped on it. "Instruction" and "number" are not properties the bits carry. They're only ever how something chose to read them. The machine treats a cell as an instruction for exactly one reason: the program counter happened to point at it during a fetch. Aim a load at that same cell and the identical byte is now a number to add. If that reads like a word game to you, hold on to the suspicion. Section 01 is where you try to catch me out, with a real byte in your hands, and fail. This is the stored-program idea — von Neumann's — and it's the reason the gibberish you couldn't read twelve chapters ago can run Fibonacci today. The bits never changed. You built the interpreter. In this chapter we turn an idea into a real program. We hand-assemble it down to bytes, deposit it into memory with our own fingers, and watch it run. And by the end the machine you've been building will finally get its proper name: SAP-1.

01The bytes never changed

Let's stare straight at the thing that should feel impossible. Here is a single RAM cell — eight bits, one byte, nothing more. It holds 00010101. Before you read on, commit to an answer: is that a number or an instruction? Pick one. Now watch both answers be right. Read it one way and it's a plain number: add up the place-values the way you have since Chapter 4, and you get 21. Read it the other way and it's an instruction. Split it into a 4-bit opcode and a 4-bit operand — 0001 and 0101 — and it becomes a command like "load from address 5." Same bits. Both readings are complete, and neither one is the real one. So the question I just asked has no answer. The machine cannot ask it either. Nothing in this hardware ever inspects a byte to find out what kind it is. Flip the reading lens below and watch one byte wear both faces at once.

the same 8 bits — flip the lens, they never move read as a NUMBER place-value · 0 to 255
reads 21 — a place-value number
tip: click any bit to flip it — watch both readings track the one byte.
Same bits, two meanings. RAM stores no type — you choose the lens. That's the von Neumann idea, made visible: in memory, code and data are the very same thing.
Fig. 1. One byte, two lenses. RAM hands the CPU eight bare bits — 00010101 — with no label saying what they mean. Read them as place values and they sum to the number 21; split them 4 + 4 and the top nibble is an opcode (0001 = LDA, “load”) and the bottom nibble its operand (0101 = 5), the command LDA 5. Flip the lens and click the bits: nothing in the byte changes, only how you choose to read it. That is von Neumann's idea made visible — in one memory, code and data are the very same thing.

Sit with that. The byte did not change when you flipped the lens. You did. There is nothing inside those eight bits that says "I am code" or "I am data." The type isn't in the cell. It's in the act of reading. So the honest question isn't "which cells are instructions?" It's "who's reading, and how?"

And the reader is the machine, with exactly one thing that decides a byte's fate: the program counter. When the PC points at a cell and the fetch step pulls it into the instruction register, that byte becomes code. It gets split into opcode and operand and executed. But when a load instruction names that same cell as its operand, the byte rides the bus into the accumulator and lands there as a plain number. Notice where it does not go: the ALU sits downstream of A and B, adding what's already in them, and a load never reaches it. Two roads out of one cell, and the byte has no say in which one it takes. Point the PC at the cell below, then point a load at it, and watch the identical byte take two completely different roads.

PC · program counter = 0x04 LOAD · an instruction [0x04] 7F 02 3C 03 B2 04 A1 05 09 06 the same byte, every time IR · instruction register opcode 1011 operand 0010 → an instruction ACC · accumulator value in 0xB2 = 178 → just a number
PC → IR: this byte reads as CODE
'code' isn't a property of the byte — it's a property of whichever register's arrow was pointing at it. Same bits, different arrow, different fate.
Fig. 2. The byte at address 0x04 never changes — it's 0xB2, full stop. Toggle who's aiming at it. Point the PC (program counter, the register naming the next byte to fetch) at 0x04 and the fetch wiring reads it into the IR (instruction register), splitting it into an opcode nibble and an operand nibble — it's an instruction. Point a LOAD's operand at that same 0x04 and the identical bits route into the accumulator instead, landing as the plain number 178 — it's data. Same wire, same voltage pattern, same byte. The only thing that ever decided code versus data was which register's arrow happened to be pointing there when it got read.

So the program counter is the whole distinction. "Code" is just "the bytes the PC happened to walk through." Nothing more mystical than that. This is also why a wild jump into the middle of your data will happily start executing your numbers as instructions. And it's why a bug that runs off the end of the program is so dangerous. The machine never hesitates, because it can't tell it's left the code behind.

There's one more layer to peel, and it kills the last trace of "bytes have meaning." Even as an instruction, a byte only means something relative to this particular machine's opcode table. Feed the exact same eight bits to a CPU with a different architecture, and they decode to a completely different command. That opcode table has a name. The agreement about which nibble means which command is the whole of what engineers call a machine's instruction set architecture (ISA). No deep principle, no committee: different machines simply shipped different tables, and each one's table is its ISA. So meaning isn't in the bits. It's in the decoder you read them against. Send one byte through two different opcode maps and watch it say two different things.

THE BYTE — fixed, never changes 1 0 1 1 0 1 0 1 opcode operand 0xB 0x5 ISA α — opcode map 0xALD 0xB SUB 0xCJMP ISA β — opcode map 0xACMP 0xB OUT 0xCHLT OUTPUT SUB R5 acc = acc - reg5 same wire, opcode 0xB every time ACTIVE: ISA α
opcode 0xB reads as SUB here
aha — the byte never moved. Swap the table and “SUB” becomes “OUT”: meaning lives in the decoder, not the bits.
Fig. 3. Two decoders, one byte. 1011 0101 (0xB5) never changes — opcode nibble 0xB, operand nibble 0x5. Plug that opcode into ISA α's table and 0xB reads SUB: subtract register 5 from the accumulator. Swap in ISA β's table — same wire, same voltage, same 0xB — and it reads OUT: write the accumulator to port 5. Nothing about the bits carried an instruction; the table you decoded them against supplied all of it. That's why a compiled binary for one machine is gibberish on another — machine code isn't universal, because meaning was never in the bits. It's in the opcode table you read them against.

That's the full weight of the stored-program principle. It's worth naming loudly, because most people never hear it said plainly: code and data live in the same memory, made of the same bytes, and meaning comes only from how they're read. John von Neumann's name is on it. Everything else in this chapter is us using that freedom. We write bytes that are numbers and bytes that are commands into one shared RAM, and let the program counter sort them out.

02From an idea to bytes

Before we can write a program, we need something worth writing: an algorithm. That's a finite recipe that turns an input into an output in a definite number of steps. But here's the part textbooks state and then walk past. A recipe that's only a straight list — do this, then this, then this — can add two numbers. But it can never loop and never choose. What lifts a list into a real algorithm is one extra ingredient hiding in the formal definition: a conditional diversion, a point where the path forks on a test. And you have already built that fork. It's the JZ / JC branch from Chapter 14. Watch the two kinds of step side by side.

next STEP 1 LOAD A STEP 2 SUB 1 STEP 3 ADD STEP 4 STORE one conditional, two vocabularies 🔒 math: IF Z=1 THEN jump to step 2 CPU: JZ / JC → loads step 2 into PC the "conditional diversion" IS the branch
step 3 is either —
a straight list: runs once, no repeats
flip step 3 and watch the runner take a path a plain list never could.
Fig. 4. Toggle step 3 between a plain sequential op and a conditional fork, and watch the little runner. As a plain op, step 3 just hands off to step 4 — the runner glides through all four steps once, start to end — no detours, no repeats: that's all a straight list can ever do. Flip it to a fork and the same slot becomes a question — is Z (the zero-flag) set? — and the runner detours back to step 2 before it's allowed to reach step 4, something no plain list has any way to do. The lock below names why: the mathematician's "if condition then jump" from the formal definition of an algorithm and the CPU's JZ / JC instructions from Chapter 14 (jump-if-zero, jump-if-carry — they load a new address into the PC, the program counter, instead of just advancing it by one) are the same conditional diversion wearing two vocabularies. Sequence alone can only recite; that one instruction is what lets it decide, and repeat.

That's the cross-domain lock worth carrying out of here. The "if the condition holds, jump elsewhere" in a mathematician's definition of an algorithm, and the conditional jump in your CPU, are the same idea in two languages. And you have both in silicon. But be exact about what that buys. A straight list can only recite. One conditional jump lets it decide and repeat. That is the whole shape of computation. Add unbounded memory to sequence plus branch and you have a universal machine. Yours has sixteen cells. So you have the shape, and not the reach.

Now, we don't want to think in raw opcodes while we design. So we write the plan in pseudocode first — hardware-independent, human-readable — then translate down. Each mnemonic like LDA, ADD, STA, JMP maps to exactly one 4-bit opcode, and its argument becomes the 4-bit operand. Exactly one, and the name is nailed down just as hard: LDA is opcode 1 in this machine's table, always, and it answers to no other name. That translation is mechanical enough that a program can do it — a program called the assembler. Step through one line at a time and watch pseudocode collapse into mnemonics, then into the actual bytes.

PSEUDOCODE MNEMONIC BYTE (opcode·operand) x = 5 ··· ···· x = x + 3 ··· ···· print x ··· ···· the assembler's lookup table — mnemonic → opcode LOAD → 1 ADD → 2 OUT → 5 ✎ the aha one line in, one byte out — a table with a pen,
step 0 / 6
press Step to begin
each beat lowers one line: pseudocode → mnemonic, then mnemonic → byte, straight off the table.
Fig. 5. Step through a three-line program and watch each pseudocode line collapse into a mnemonic, then into the actual byte the machine stores — opcode nibble and operand nibble, read straight off the lookup table on the left. There's no cleverness at the byte step: LOAD is always 1, ADD is always 2, because a pen (a program) is just checking a row and copying a number. That's the whole trick behind writing in mnemonics instead of raw bytes — the assembler is a lookup table with a pen, one line in, one byte out.

Notice what the assembler is — and, more importantly, what it isn't. It's not intelligent. It's a dumb, deterministic hand-map: look up the mnemonic, glue its opcode to its operand, emit one byte. One line in, one byte out. There's no optimization and no understanding. It's just a lookup table with a pen. One honest asterisk on that, because you'll hit it the moment you write a jump: a name can point forward. Write JMP top and the pen hasn't reached top yet, so it can't know the address. So it reads the listing through once, noting where every name landed, and only then goes back and writes the bytes. Two passes, still no thinking. (That's the seed of every compiler you'll ever meet: layers of exactly this, stacked.)

One word we've been leaning on without defining, and I owe you a clean definition. A variable is not a mysterious container. It is simply a named address in RAM that we've agreed will hold a value that changes over time. When the pseudocode says a, the assembler just picks a free cell — say address 13 — and from then on "a" means "cell 13." Rename it, move it, and the program is unchanged. And a label is the very same trick, aimed at code instead of data. top is a name pinned to the address an instruction happens to sit at, and JMP top just means "put that address in the PC." The assembler binds both kinds of name the identical way, because it cannot tell them apart either. Only the program counter decides which one turns out to be code. Bind a few variables to cells below.

Now look hard at the words "a free cell," because this machine has fewer of them than you think. The operand is four bits wide. Four bits count 0000 through 1111, so every address this machine can name runs from 0 to 15. Sixteen cells. That is not sixteen cells of variables — that is the entire world, and your program lives in it too. So when the assembler drops a at address 13, it isn't picking a spot in an ocean. It is claiming 1 of 16 slots that the instructions are also competing for, and every line you write eats one. Let the code grow long enough and it lands on top of a. Nothing will warn you, because nothing in there knows which cells you meant as code. That's the stored-program idea leaving philosophy and starting to charge you rent.

PROGRAM · RUNS EACH STEP a = a + 1 b = b + 2 c = c - 1 STEP COUNT 0 8 CELLS OF RAM — EACH ONE ITS OWN ADDRESS a b c tag = a name pinned here  ·  — = free, unclaimed cell
pick a name, then tap a free cell
a variable was never the number. it's a name pinned to one RAM address — move the pin to a new cell and the exact same program keeps working, because it only ever asks the name for "whatever's there right now." that's why a variable holding a changing value is nothing mystical.
Fig. 6. A variable is nothing but a name pinned to one RAM address — rebind the pin to a free cell and the exact same program still finds it, because it was only ever asking for the name, never the address.

So the whole descent is clear now. An idea becomes pseudocode. Pseudocode becomes mnemonics with named variables. And the assembler turns those into bytes. Some of those bytes are instructions, some are the numbers those instructions touch, and all of them live together in one RAM. Time to build a real one.

03Fibonacci, for real

Let's write something that actually loops — the Fibonacci sequence. The rule is famous and tiny. Start with two seeds, and every number after is the sum of the two before it. 1, 1, 2, 3, 5, 8, 13, 21… Generate a few terms and watch each one being born from its two predecessors.

each term = the two terms right before it 1 given 1 given 1 and 1 are given — press Generate
1, 1 — the two given seeds
the rule reaches back exactly two places
Fig. 7. Click Generate next term and watch a new box light up two feeder arrows — one from each of the two boxes right before it — then fill with their sum. Every term all the way out to 21 is born the same way: two arrows in, one sum out. However far the sequence grows, the arrows never reach past the immediate two neighbours — that is the whole rule, and it never needs more.

Here's the first thing that makes it programmable on a machine with almost no memory. To compute the next term you never need the whole history. You only need the last two numbers. So we keep just two cells, call them a and b, and compute c = a + b. Then we slide the window forward: the old b becomes the new a, and the fresh c becomes the new b. Two cells, reused forever. Watch the window slide.

the sequence — each number written once, on paper revealed 2/10 c = a + b the store — only ever 2 cells, reused forever a (older) b (newer) 0 + 1 store size: always 2 (a, b) — never grows history: 2 numbers on paper each step: a ← b    b ← c (← reads “takes the value of”)
window F0·F1 — 2 cells held
Press advance: the sum c = a + b flashes green, the two registers pulse, then the gold window glides one cell right and everything behind it dims. Two cells do all the work — the past is thrown away on purpose.
Fig. 8. The whole Fibonacci sequence sits up top — every number written out, once, on paper. But the machine computing it never re-reads that paper: down below sits the real engine, just two cells, a (older, blue) and b (newer, gold). Press advance and watch the recipe run: c = a + b is computed and flashes green, then the store slides — b's value becomes the new a, and the fresh sum c becomes the new b. Up top, the gold window glides one cell right in lock-step, and every cell it leaves behind goes gray: not erased from the page, just no longer needed by the machine. That's the whole trick, and the reason Fibonacci runs in almost no memory — the machine keeps two cells and throws the rest away.

That slide — a←b, b←c — looks trivial, but it hides the single most important truth about how a machine computes. And it's worth being wrong about first. Each store is a side effect: it overwrites a cell, and the old value is gone. So the order of the two moves isn't cosmetic. Get it backwards and you clobber a number you still needed. Predict what happens if you copy c into b before saving b into a, then reveal it.

slide the window forward — order the two stores a 8 b 13 computed:  c = a + b 21 1st · · · 2nd · · · next term → ? true Fibonacci: …8, 13, 21, 34, 55…
Predict first: if b←c runs before a←b, is a still 13?
pick an order below
a store doesn't just write — it destroys whatever was there. That's why order is the computation, not decoration.
Fig. 9. Two cells, a and b, and a sum c = a + b the ALU has already computed but not yet stored. Pick an order and watch it run: a←b, b←c copies b's value to safety before b gets overwritten — the slide lands on 13, 21, exactly as Fibonacci needs. Pick b←c, a←b instead and b is overwritten first, so the second store copies the new b (21) into a, not the old one — the 13 that a was supposed to receive is gone for good, and the next term comes out 42, not 34. Same two instructions, same computer, opposite answer: the order a store runs in is not bookkeeping, it is the computation.

That's the lesson under all imperative programming: instructions mutate state, so sequence is the computation. The same three instructions in a different order compute a different thing, or ruin it. Machines don't run "the set of your instructions." They run them one at a time, in order, and each one leaves a scar.

Now the loop. We want the machine to compute term after term without ever stopping. So after the slide, we simply JMP straight back to the top. There's no exit test at all. The jump is unconditionally taken every time, which is another way of saying the loop condition is always true. That's a legitimate program shape: an infinite loop, fetching and executing forever, the PC snapping back to the start each pass. Watch it circle.

passes: 0 JMP 00 — always taken PC 00 ADD C ← A + B 01 MOV A ← B 02 MOV B ← C 03 JMP → 00 PC → 00 · ADD C ← A + B no exit test — condition: always TRUE
paused — PC parked at 00
There's no exit test anywhere in this loop — JMP 00 is unconditional, so its condition is simply always TRUE. That's why a program can run forever: the jump rewrites the PC before fetch‑execute ever falls off the bottom of memory.
Fig. 10. The jump rewrites the PC — that's the whole loop. Press run and watch the program counter walk 00→01→02→03; instead of falling off the bottom at a nonexistent 04, JMP 00 snaps it straight back to the top every pass — the gold arc lights up each time. There is no test anywhere in this program; the loop condition is simply always TRUE, so it circles forever until you hit pause.

But an 8-bit machine climbing Fibonacci is heading for a wall, and it's a wall you met back in Chapter 14. A byte holds only 0 to 255. The terms run 89, 144, 233… and the next one, 233 + 144 = 377, simply does not fit. So it wraps. The adder computes mod 256, so 377 − 256 = 121 is what actually lands in the cell. Meanwhile the bit that fell off the top sets the carry flag. Push the sum past 255 and watch the odometer roll and the carry light.

89 + 55 = ? the raw arithmetic sum — no ceiling 0 255 → wraps 0 8 9 the register — 8 bits, 0 to 255 CARRY FLAG flag = 0 step 0
click to add the next two Fibonacci terms
press STEP to begin
aha: the chip never errors — it just wraps mod 256 and flips a flag that whispers "you lost a bit".
Fig. 11. The odometer shows what the register actually holds; the equation above it is just the raw sum, with no ceiling of its own. Click STEP → a few times to add the next two Fibonacci terms each time — on the step where 233 + 144, the true sum is 377, but the register only has room for 0–255. Watch the odometer roll straight past 255 and land on 121 (377−256), while the carry flag lamp lights — the machine's only note-to-self that a bit fell off the top.

So the machine doesn't error. It lies quietly, handing you 121 and setting a flag it's up to you to check. That honesty-with-a-catch is exactly why the carry flag exists. It's the machine's only way to whisper "there was a bit you didn't have room for." Now let's stop hand-waving and actually run the whole program — Fibonacci and a countdown — in a faithful interpreter, and diff its output against what we predicted.

fib.asm stdout · diffed vs hand-prediction PC 00 ACC 000 Z 0 C 0 idle each cell = one number the machine printed matched 0 / 14 · prediction untested 377 > 255 → rolls to 121, carry set
pick a guess, then run ▸
A faithful model of your transistor machine — it prints the same numbers the hardware would, wrap and all.
Fig. 12. The whole machine, run. A faithful interpreter of your machine — fetch, decode, execute, the same instruction set you wired from transistors — actually executes two real programs, and its stdout is diffed cell-by-cell against what we predicted by hand. Commit a guess for the one surprising value, then run: Fibonacci climbs 1, 1, 2, … 144, 233 and then prints 121, not 377, because 144 + 233 = 377 doesn’t fit in eight bits and rolls over mod 256 (carry set) — exactly the wrap the hardware would do; Countdown rides SUB to zero, where the Z flag trips JZ and the loop exits on 0. Every number matches. That is the payoff: no magic anywhere in the stack — a model of the transistor-built machine reproduces its numbers, wrap and all.

There it is: a real program, running on a faithful model of the machine you built from transistors, producing exactly the numbers — wrap and all — that the hardware would. No magic anywhere in the stack. Now let's put it into memory the way you'd have to on the real thing: by hand.

04Loading it by hand

Quick recap of the one thing the machine does to every byte it fetches, because we're about to build those bytes ourselves. It splits them. The top 4 bits are the opcode (which instruction), and the bottom 4 bits are the operand (which address or value). That split is done by wiring, not thinking. The instruction register just routes the two nibbles to two different places. Toggle a byte and watch it fall into its two halves.

the fetched byte — one wire per bit position 7 1 6 0 5 1 4 0 3 0 2 0 1 1 0 1 opcode — bits 7–4 operand — bits 3–0 OPCODE instruction register (IR) OPERAND 1 0 1 0 0 0 1 1 → decoder → operand path same byte, written in hex: 0x A 3
click any bit above to flip it — watch where it lands
opcode Ah → operand 3h
no comparison, no if — the IR just wires bit 7–4 one way and bit 3–0 another. That's why the split costs no thought.
Fig. 13. Click any bit in the fetched byte and watch it fall straight down its own fixed wire — the top four (opcode) always land in the left field of the instruction register, the bottom four (operand) always land in the right field, each then carried on to its own destination. Try the presets: whatever byte you load, the split never changes, because nothing is being decided — the two halves were wired to two places from the start. That's the same split you've been reading all along in a byte's hex form: the first hex digit is the opcode nibble, the second is the operand nibble.

Now, how does a program physically get into RAM before there's any program to load it? On the earliest machines, and on the one you built, you enter it through a front panel: a bank of address switches, a bank of data switches, and a deposit button. Set the address, set the byte, press deposit, and the byte is written to that cell. Repeat for every line of the program. Hand-load a few bytes below.

FRONT PANEL — deposit by hand ADDRESS A3 A2 A1 A0 addr = 0 DATA — one byte D7 D6 D5 D4 D3 D2 D1 D0 byte = 0x00 (0) ⏎ DEPOSIT writes the byte into the highlighted cell ↓ RAM 00
toggle the address bits, toggle the byte bits, hit DEPOSIT — repeat to hand-load a short program.
set address + byte, then deposit
That's why loading code is writing data — these twelve switches don't know "program" from "number," they only ever store bytes. The PC (program counter, the CPU's next-cell pointer) is what will later fetch this same byte and treat it as an instruction.
Fig. 14. The front panel — set the address switches to pick a RAM cell, set the data switches to a byte, then press DEPOSIT: the byte writes straight into that cell, nothing more. Repeat address-byte-deposit a few times and you've hand-loaded a short program, one byte at a time. The switches never know they're loading "code" — they only ever store numbers; it's the PC that will later read a cell and decide, by fetching it, that this byte means something.

Feel what just happened, because it's the whole chapter in your fingers: loading code is nothing but writing data. Those deposit switches don't know they're storing a "program." They're storing bytes, exactly the way you'd store the number 121. The instructions become instructions only later, when the program counter reaches them. Set the PC to the start, release the machine, and watch it walk the very bytes you just deposited — fetch, execute, advance — as a live program.

RAM — all sixteen cells, hand-loaded REGISTERS 0 0x00 1 0x00 2 0x00 3 0x00 4 0x00 5 0x00 6 0x00 7 0x00 8 0x00 9 0x00 10 0x00 11 0x00 12 0x00 13 0x00 14 0x00 15 0x00 0 PC = 0 OUT — the terms printed PC = program counter — which cell runs next ACC = accumulator — the running answer (8 bits wide, so sums wrap past 255) every byte = opcode (high nibble) + operand (low nibble) opcodes: 0 HLT · 1 LDA · 2 ADD · 3 SUB · 4 STA · 5 OUT · 6 JMP · 7 JZ LDA n: ACC←mem[n] ADD n: ACC+=mem[n] STA n: mem[n]←ACC OUT: print ACC JMP n: PC←n
press Step to fetch the first byte
terms printed: 0
aha: cells 13, 14 and 15 hold 0x01, 0x01, 0x00 — the variables a, b and c — deposited by hand into the same sixteen-cell address space as the loop, drawn with the same box, and nothing on them says "data." That seed byte 0x01 in cell 13 is a perfectly legal instruction too: opcode 0, which this machine decodes as HLT. The only thing that ever makes 0–8 "code" is the PC walking over them, in order.
Fig. 15. All sixteen cells of RAM — the whole world a 4-bit operand can reach — holding the bytes you hand-assembled and deposited in the last figure: the Fibonacci loop in cells 0–8, four spare cells, and the variables a, b, c in cells 13, 14, 15. Every cell is drawn identically; the machine has no idea which is which. Click Release to set the PC (program counter) to address 0, then Step — each click fetches the byte the PC points at, splits it into opcode and operand, decodes it against the table in the legend, executes it, and advances the PC. Watch cells 0–8 light gold and earn a decoded label as the PC walks them, while 13–15 only ever flash blue — read and written as plain numbers, never fetched, never "run." JMP 00 at cell 8 throws the PC back to the top, so this program never halts: OUT keeps printing 1, 1, 2, 3, 5, 8… until you stop it. (Past 233 the sums wrap — the ACC is eight bits wide, and that is the machine telling the truth about its own width, not a mistake.)

That is a computer, top to bottom. You wrote bytes into cells with switches, and a ROM-driven datapath walked those cells and obeyed them. Nothing was added between "data you typed" and "program that ran" except the program counter arriving. Which means it's time to admit what this machine actually is.

05You built SAP-1

The machine you've assembled — a bus, an ALU, an accumulator, RAM, a program counter, an instruction register, and a ROM control unit fetching and executing a small instruction set — is not a one-off toy I invented. It's a real, named design: the SAP-1, the "Simple As Possible" computer from Malvino's classic textbook. It's the canonical first CPU that generations of engineers cut their teeth on. Line your blocks up against the SAP-1 block diagram and see the same machine.

hover / click each row to confirm the match what you built Malvino's SAP-1 the wire everyone shares W BUS adds — and subtracts too ALU (Σ) the number it's holding ACCUMULATOR 16 bytes you can address RAM (16 × 8) counts the next line PROGRAM COUNTER holds the current line INSTRUCTION REG. (IR) the ROM sequencing it all CONTROL UNIT (ROM) matched 0 of 7
hover a block to compare names
click / tap a row to confirm it
SAP-1 = “Simple-As-Possible” — that's not a nickname we made up, it's Albert Malvino's own name for this exact machine, and the direct ancestor of every CPU block diagram since. Match all seven, and the last piece clicks.
Fig. 16. Seven blocks, two names each. Hover or click any row to line the machine you built up against Albert Malvino's own SAP-1 diagram — the bus, the ALU, the accumulator, the RAM, the PC, the IR, and the ROM control unit. Match all seven and the truth lands: you didn't build a metaphor for a computer — you built SAP-1, the textbook's first CPU.

I want to be scrupulously honest about one seam, because glossing it would be the kind of hand-wave this course refuses. Our toy runs on a five-state counter with a two-cycle fetch. Back in Chapter 15 we merged the PC-increment into the IR-load to save a beat. The textbook SAP-1 uses a six T-state ring counter with a three-cycle fetch, spending a whole separate T-state just to bump the PC. Same blocks, same shape — with two honest differences: the fetch is sliced differently, and our instruction set adds the store and the jumps (STA, JMP, JZ) that Malvino leaves to SAP-1's successor, SAP-2. That second one is what lets our loop loop. Align the two timelines and watch exactly where they diverge.

clock cycle 1 2 3 4 5 6 TOY this book 5 states S0PC→MAR S1IR←RAM,+1 S2execute S3execute S4execute done 1 state = T2 + T3 SAP‑1 Malvino 6 T‑states T1PC→MAR T2PC + 1 T3IR←RAM T4execute T5execute T6execute ▲ the extra T‑state TOY   S0  ·  PC→MAR SAP‑1 T1  ·  PC→MAR
Cycle 1 — both send the address
The whole difference, in one box. The toy loads IR and bumps the PC in a single state (green S1). Malvino's SAP‑1 gives PC+1 its own T‑state (red T2). Merge those two and fetch is 2 cycles, not 3 — so everything after slips by one.
Drag to cycle 2: the timelines split here and never re‑align — one honest bookkeeping choice, flagged, not hidden.
Fig. 17. Scrub one fetch across both machines, cycle by cycle. Both open the same way — PC→MAR, the address step. Then they split: Malvino’s SAP‑1 spends a whole separate T‑state (T2) just to bump the program counter, while the toy folds that same PC+1 into the very state that loads IR (the green S1). One merged state — that’s the entire reason a 5‑state toy and a 6‑T‑state textbook don’t line up beat‑for‑beat. Not a mistake; one honest bookkeeping choice, flagged rather than hidden.

With that admitted, look at the whole toolchain you now command, end to end, because this is the payoff of the entire volume. An idea becomes pseudocode. The assembler lowers it to bytes. You deposit those bytes into RAM. The program counter walks them. The control ROM decodes each one into control lines, and the datapath obeys. Every arrow in that chain is something you built from switches. Trace it once, whole.

the toolchain, end to end — click a stage 1 IDEA add a + b 2 PSEUDOCODE c = a + b 3 ASSEMBLER picks mnemonic 4 BYTES 0x2E 5 RAM @ addr 0x02 6 PC fetch cycle 7 CTRL ROM decode 8 DATAPATH ALU obeys   row 1 runs left→right, row 2 continues right→left
— pick a stage —
click a stage, or trace it →
trace all 8 stages to reveal the big idea
Fig. 18. Follow one idea all the way down: click a stage and watch what it hands to the next — pseudocode becomes an assembly mnemonic, the assembler turns that into bytes, the bytes get deposited in RAM, the PC walks over and fetches them, the control ROM decodes the opcode, and the datapath finally obeys. Trace all eight once and the payoff lands: every one of those arrows is a wire you built, stage by stage, from a switch.

And here's where all of it sits on the map you've been climbing since the first transistor: one clean ladder from a single voltage-controlled switch up to a running program. You are here.

the sand (raw silicon) rung 8 / 8 switch Ch01 gate Ch02–03 adder Ch04–06 register Ch07–08 RAM Ch09 bus Ch10 CPU Ch11–15 program Ch16 YOU ARE HERE
Click any rung, or step with the arrows — the flag is where you'd be reading, on the one ladder.
Ch16 · YOU ARE HERE — a program
AHA — a running program isn't special. It's rung 8 of the very same ladder that started at a grain of sand.
Fig. 19. The same climb you've been on since Ch01transistor → gate → adder → register → RAM → bus → CPU → program — laid out as one staircase, gold rungs marking how far up you've come. Click any rung, or step with the arrows, and the flag jumps there; hit home to snap back. Nothing about the shape changed by writing a program — you're still standing on switches, eight rungs up.

That's Chapter 16 — and, really, the top of the mountain we started climbing at the transistor. The big idea was one sentence: code and data are the same bytes, and meaning lives only in how they're read. That's von Neumann's stored program, named at last. You turned an algorithm into pseudocode, hand-assembled it into bytes, deposited those bytes into RAM with your own fingers, and watched the program counter sort code from data by nothing but where it pointed. Fibonacci looped, overflowed to 121, set the carry, and kept going. And the machine underneath it turned out to have a name — SAP-1 — a real CPU, honestly reconciled, timing seam and all. So here's the claim that should feel both impossible and, now, obviously true: the processor in your phone is not different in kind. Same fetch, same decode, same execute — just more of everything, running billions of times a second, with a couple of tricks the toy never needed. One of them, the stack, is what finally lets code call code and come back. Let's climb the last stretch of the ladder and meet a chip you could actually buy.

iolinked.com
Written by Ajai Raj