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

15The Control Unit is a ROM

At the end of last chapter I asked you to look one last time at who was still deciding. The machine could branch now. It could loop until a counter hit zero, or choose one path over another. And yet every one of those choices was really being made by a human hand. Yours. Across twelve chapters you have been sitting at the control panel, raising the control lines in the right order: drive the PC onto the bus here, latch the MAR there, enable the ALU, catch the result in register A. It felt like thinking. But watch yourself do it and you'll notice something deflating. You never actually chose anything. Every single time, you followed a fixed rule: for this opcode, on this step, with these flags — raise exactly these lines. Same situation in, same lines out. No judgement, no memory of what came before. Now, most tellings of this chapter spend the punchline right here: a fixed rule that maps an input straight to an output, with no memory of its own, is a lookup table. I'm going to say it and then take it straight back, because that sentence skips a step, and the skipped step is the whole chapter. So here is all I'll promise. The machine's entire "brain" — the thing that has been quietly running the show — is not some new kind of hardware we've been putting off. It's furniture you already built, back in Chapter 11: a ROM, the same decode-and-OR array you wired up to light a display. What I won't hand you is the reason. Why the situation has to be an address, and why the lines to raise are exactly its stored word — that part you're going to say out loud before I do. The rest is us earning it: building the machine's sense of time, packing the situation into an address, and reading the finished table with our own hands.

01You have been the control unit

Let's start by catching ourselves in the act. Here is the machine you've built, stripped down to its control lines — the little switches that steer everything. There are out-enables that push a value onto the bus, load-enables that latch it somewhere, and the ALU's op select. To run even one micro-operation, you raise these lines in a precise order. Do it by hand below. Push the buttons to walk a value from RAM into register A, and notice that you are the thing making it happen.

the datapath — four chips, one shared wire RAM byte @ addr 7 0x2A REG A 0x00 REG B 0x00 ALU A + B 0x00 OE OE LE OE LE OE BUS  — floating one wire · exactly one driver at a time
GOAL — walk RAM's 0x2A into REG A
  1. 1.raise RAM out-enable
  2. 2.raise A load-enable
  3. 3.tick the clock
out-enable · drive the bus
load-enable · latch from bus
ALU op
All lines low — the bus is floating.
No chip "decided" anything. You raised RAM's out-enable and A's load-enable, in order, by hand — that hand has been the control unit for twelve chapters. Chapter 15's whole trick: bake this button-order into a ROM.
Fig. 1. The manual control panel. Four chips hang off one shared bus, and every chip has two hand-raised lines: an out-enable (drive your value onto the wire) and a load-enable (latch whatever's on the wire). Follow the goal — out-enable RAM, load-enable A, tick the clock — and 0x2A walks into register A. Try raising two out-enables at once and watch the bus short into conflict (one wire holds one value). Nothing in here decides the order… you do. For twelve chapters, that hand on the panel was the control unit — which is exactly why the "brain" we build next feels like nothing new.

Now the uncomfortable part. It felt like you were deciding, but you weren't. You were obeying a script you could write down in advance. Watch. I'll hand you a situation — an opcode, a step number, and the current flags — and ask you to pick which control lines to raise. There are exactly two flags, by the way, and you built them last chapter: Z for zero and C for carry. One bit each, set by the last ALU operation, then just sitting there waiting to be read. Commit to your answer, then reveal the "correct" one. They'll match every time, because there was never a choice. The situation alone forces the lines.

the situation op FETCH step T0 flags — Fetch, step 1: point the address bus at PC and read. control lines · click to raise the ones this step needs ROM row → { } seen this exact row 2× · identical lines both times same (opcode, step, flags) in → same lines out · no memory, no history
1 · click lamps to predict. 2 · reveal the row the situation forces.
Raised 0 · reveal to check
You never looked at the last step. Only the three inputs in front of you set the lines — that isn't choosing, it's a lookup.
Fig. 2. Same three inputs — opcode, step, flags — and the same control lines come out, every single time. Watch the two JMPZ·T2 rows: identical instruction, identical step, yet the Zero flag alone flips the answer — and the repeat proves it never once consulted what came before. You weren't deciding which lines to raise; you were reading a row out of a fixed table. That is exactly why a control unit can just be a ROM.

Sit with what that means. The output depends only on the present situation. It never depends on anything the machine remembers, on mood, or on history. Same three inputs in, same bundle of lines out, forever. So here's the sentence from the top, and here's the step it skips. A fixed, memoryless map from an input pattern to an output pattern is a truth table. That much is forced. Calling it a lookup table is not forced. A lookup table is one way to build a truth table, and we haven't earned that choice yet. Hold the two words apart for a few paragraphs. You have built the shape twice already — the seven-segment decoder in Chapter 11 (a 4-bit code in, seven segment-bits out) and the addressing logic of RAM. Line all three up and they are the same shape.

So let's do the thing that makes "fixed rule" stop being a phrase and start being an object. Count the rows. The opcode is 4 bits wide, so there are at most 16 of them. The flags are 2 bits, so 4 combinations. And the step? We haven't built it yet, so borrow one number from me and we'll earn it in a few paragraphs: it runs to 5 values. Multiply. 16 × 5 × 4 = 320 rows. That's it. That is the machine's entire mind, and it would fit on two printed pages. Not an infinity, not a metaphor — a list you could read on the train. Which leaves exactly one question, and it's an engineering question rather than a philosophical one: once the table is written down, what do you make it out of? You can freeze it into gates, a tangle of AND and OR that computes each output straight from the input bits. Or you can store it, row by row, and read the row back when you need it. Both are fixed. Both are memoryless. Both hand back bit-identical answers. Keep both doors open. We'll come back and choose.

CODE in → WORD out the same shape, three chapters the code in the fixed word out same: a CODE same: a WORD of bits
Step 0–7 — each row is code→word.
You’ve built this table twice already: the digit decoder and RAM both turn a small code into a fixed word. The control unit does the exact same thing — so its home is a ROM you burn once, not new circuitry.
Fig. 3. You’ve built this table twice already. Step the code from 0 to 7 and the same thing happens in all three: a small code goes in, a fixed word of bits comes out. A seven-segment decoder, a RAM read, a control unit — structurally one machine. That’s why the control unit’s destination is already a ROM: you recognise the shape from Chapter 11, not a new invention. Toggle reveal the shared shape to lock it in.

So the destination is already in view: the control unit is going to be a table in silicon. But a table needs a clean input, and right now one third of our situation is missing. The machine knows its opcode (it's in the instruction register) and it knows its flags. But it has no idea which step it's on. LDA takes several beats, and nothing in the datapath tells it whether this is the first beat or the fourth. Before we can build the table, we have to give the machine a sense of when.

02The step counter — a sense of when

Here's the missing organ, and it's almost embarrassingly small: a microstep counter. It's a tiny register that just counts 0, 1, 2, 3, 4. Then, instead of climbing to 5, it snaps straight back to 0 to begin the next instruction. Five states, on a loop. It's the machine's answer to the question "where am I inside the current instruction?" Tick it and watch it march and reset.

auto-reset 0 MICROSTEP 0 1 2 3 4 tick it: 0 · 1 · 2 · 3 · 4 · then snaps to 0 what the control ROM is addressed by opcode — same for all 5 steps 1 0 1 1 microstep — the counter (changes!) 4 2 1 0 0 0 picks ROM row 88 opcode never moves — the counter alone chooses a different row each microstep.
step 0 — a fresh instruction just began
The datapath wires look identical at step 0 and step 3 — the opcode never changes mid-instruction. Only this counter knows when you are. That's the new organ the datapath couldn't grow on its own.
Fig. 4. A five-state ring counter — the machine's sense of when. Tick it (or auto-run) and watch the microstep march 0·1·2·3·4, then snap back to 0. Those three counter bits are the low end of the control-ROM address (the two flag bits get spliced in beneath them in §4; ignore them for now): the opcode above them never moves during an instruction, so the counter is the only thing that tells step 0 from step 3 — the missing organ the datapath alone could never supply.

Where does its clock come from? It doesn't need its own, and this is the quietly beautiful part. The same clock edge that latches a value into a datapath register also ticks the step counter forward. One rising edge does two jobs at once. The datapath does the work of this step, and the counter advances to point at the next one. So the machine's sense of time comes for free, riding the very clock that's already driving everything. Watch a single edge do both.

one edge, two jobs CLK LOW fan-out REG D flip-flops ▷clk D · input you set D→ Q · latched output STEP CTR T0 ▷clk which micro-step the control ROM reads
Change D — notice Q doesn't budge. Then fire the edge.
The step counter is just another register — clocked by the very same wire. So the machine's sense of "when" costs nothing extra: it rides the clock already driving the datapath.
Fig. 5. The step counter that tells the control ROM which micro-step (T-state) to read is just one more edge-triggered register. Fire a single rising edge: the same instant it latches D into the datapath register, it ticks the counter to the next beat. That's why a machine's sense of when is free — it rides the clock that's already there.

Now let's spend the counter's first two states, because they're identical for every instruction the machine will ever run. This is the fetch. On step 0, the program counter holds the address of the instruction we're about to run, so we copy that address into the MAR — the register that tells RAM which cell to look at. One out-enable, one load-enable, done. That's the whole of step 0.

FETCH · step 0 of every instruction the shared bus CO · PC out PC · program counter 42 MI · MAR in MAR · address reg ▲ latched on the clock edge RAM · memory no address yet which cell? unknown the control unit is a ROM — its step-0 word (6 of its 16 lines):
Press Run — copy the PC's address into the MAR.
Exactly one out-enable and one load-enable fire. Two out-enables at once and two registers would fight over the same bus.
That's why fetch always starts the same way: say where the instruction lives before you can read it.
Fig. 6. Fetch begins by naming a place. Press Run step 0: the control unit's ROM holds the whole word steady — only CO (PC out) and MI (MAR in) are 1. That single out-enable lets the program counter drive its address (42) onto the shared bus; that single load-enable lets the MAR catch it on the clock edge. Now RAM knows which cell to read. The strip is a slice: six of this machine's sixteen control lines, drawn because six are readable and sixteen are not — the other ten are still there, still 0 this beat. Every instruction, every time, starts exactly here — because before you can fetch an instruction, you must first say where it lives.

Step 1 is where two things happen on one beat. RAM now hands back the instruction byte, and we latch it into the instruction register. But on that same edge, we also nudge the PC up by one, so it already points at the next instruction. We could have spent a whole separate state just incrementing the PC. We don't need to, so we fold the PC++ into the IR-load and save a beat. Be careful about what that merge buys, though. It is not the reason the counter has the length it has. It's a discount, applied evenly to every instruction: each one comes out one beat shorter than it would have been. That is why our toy is a five-state machine rather than a six-state one, and why the counter runs 0→4 and not 0→5. But the rule that decides that number in the first place is a different rule, and we'll meet it in a few paragraphs.

one rising clock edge ↑ RAM 0011 1010 @ 42 — held by the MAR IR · · · · instruction register PC 42 +1 ↑ control word — 3 of the 16 lines, the ROM's output this beat RO RAM→bus II →IR CE PC + 1 the beats of ADD — the chapter's longest instruction T0 T1 T2 T3 T4 T5 ADD needs 5 beats → counter runs 0 → 4 ■ fetch   ■ ADD's execute   ■ the extra beat
Step 1 armed — fire the clock edge.
Predict first: give PC++ its own beat and every instruction gets one beat longer. ADD needed 5. What does the counter need now? Fire both modes and watch the ribbon.
AHA — the counter is as long as the longest instruction, and that's ADD at 5 beats (0→4). Folding PC++ into the IR-load is a −1 charged to every instruction alike: without the fold ADD would need 6, and so would the counter.
Fig. 7. Fire step 1 of the fetch. In merged mode one rising clock edge does two jobs at once — the byte the RAM is serving from cell 42 (the address the MAR latched in step 0) flows across the bus into the IR while the PC ticks 42→43 — because the control ROM raises RO, II and CE on the same beat. (Three of the sixteen control lines; the other thirteen are low.) Flip to split and the increment demands its own beat — a beat charged to every instruction alike. The ribbon shows ADD, the chapter's longest: it needs 5 beats merged, and 6 when the fetch costs two. The counter is as long as the longest instruction — that is the rule. The folded increment is the −1 that makes that longest instruction 5 beats instead of 6.

Here's a subtlety worth catching before it trips you later. During those two fetch steps, the machine asserts a fixed set of control lines. It does so without knowing what instruction it's running. It can't know, because the opcode only arrives in the IR at the end of step 1. So the fetch prefix is opcode-blind by necessity. Pick any opcode you like below and confirm that steps 0 and 1 come out byte-for-byte identical no matter what you chose.

One column = one micro-step the control ROM emits FETCH · opcode-blind EXECUTE · opcode steers II loads the opcode in Step 1 — Steps 0–1 run before the IR holds it opcodes tried 0/4 · Steps 0–1 changed
ROM address = opcode ‖ step ‖ flags — opcode field still ????
Pick an opcode → watch Steps 0–1 stay put.
Chips: gold = drives the bus, blue = latches from it, green = acts off the bus (count, ALU mode, flag latch). The grey strip under each header is that step's 9-bit ROM address — opcode ‖ step ‖ flags(Z,C). The IR doesn't hold this instruction's opcode until II fires in Step 1, so Steps 0–1 cannot depend on it — the ROM simply stores those two rows identically under all 16 opcodes.
Fig. 8. Steps 0 and 1 are one shared prefix. The control ROM is addressed by opcode ‖ step ‖ flags — but the IR doesn't hold this instruction's opcode until II fires in Step 1. So during Steps 0–1 those four address bits still carry the previous instruction's code, and the machine has no idea what it's running: the ROM is written so that all 16 opcode copies of those two rows are byte-for-byte identical, and every instruction therefore runs the same fetch — CO, MI, then RO, II, CE. Pick LDA, STA, ADD, JMP — the first two columns never flinch. Only from Step 2, once the opcode exists, does the ROM branch. (The opcode field is shown by mnemonic; its four bits are the code from the roster. The two flag bits sit in the address at every step too — none of these four instructions reads them, so all four flag-copies of each row are identical, which is the redundancy the chapter pays for later. LDA, STA and JMP finish early and idle out Step 4; ADD needs all five, and it is ADD that sets the counter's length.)

Two steps down, always the same. Whatever the instruction turns out to be, the machine has now fetched it. The opcode is sitting in the IR, the flags are set from the last operation, and the step counter reads 2. From here on, the steps finally get to care what the opcode is. That's where the interesting part lives.

Before it does, though, we should be straight about what the opcodes are — and I owe you an apology, because I've been sliding between names. Here's the roster, one name each, and these are the names I'll hold to. LDA: load register A from a cell in RAM. (One of the panels labels it LOAD. Same instruction.) STORE: write A back out to a cell. LDI: load A with a constant carried inside the instruction itself, so there's no RAM read to wait on. ADD and SUB: run the ALU and catch the result in A. These are the long ones; they need all five beats. JMP: overwrite the PC with a new address. JZ and JC: the same, but only if the zero or the carry flag is set. (The predict panel up at the top called the first of these JMPZ. Same instruction.) NOP: assert nothing and go round again. The opcode field is 4 bits, so there's room for sixteen instructions and we use a fraction of that. Which 4-bit code goes to which instruction is a free choice, and nothing downstream cares, so long as we choose once and never change our minds.

03Fetch, decode, execute

One naming note first, because the panels are about to switch dialects on you. The step counter's states are conventionally written T0, T1, T2. The T is for timing. T3 means step 3, and that's the whole of it, so when a caption says T0–T4, read it as steps 0 to 4. Now: you'll hear the machine's whole rhythm named in three words — fetch, decode, execute — and it's worth being honest about where each one lives. Fetch is steps 0–1, the part you just built. Execute is steps 2 onward, coming next. But decode? Look for it and you won't find a step for it anywhere. That's not an oversight. Decode isn't an action the machine does. It's the invisible instant the opcode gets turned into control lines — the table lookup itself. Here's the loop with decode drawn where it actually happens: in the wires, not on the clock.

THE INSTRUCTION CYCLE FETCH EXECUTE loop closes here — the next instruction begins at T0 IR holds the opcode FLAGS Z · C CONTROL ROM opcode + step + flags → one word ???? · T0 · 00 → drives T0's control lines read continuously — no clock edge required here always live always live
step through T0–T4 — the ROM never stops reading
T0 · fetch — opcode isn't in the IR yet
That's why you never see a decode step. The opcode and the flags are wired straight into the ROM's address lines — on every beat alike, whether you're at T0 or T4. Only the green step tap ever moves. The blue and red wires never turn off, because decoding isn't an action the clock reaches; it's the table lookup itself, running the whole time in the copper.
Fig. 9. The five beats up top are the real clock: fetch (T0–T1), then execute (T2–T4), looping back to T0 for the next instruction. Click through them and watch the green tap hop from beat to beat — that's the only thing that changes. The opcode (from the IR) and the flags stay wired straight into the control ROM's address lines the whole time, on every beat alike, glowing without a single flicker. That's decode: not a sixth station squeezed between fetch and execute, but the ROM continuously reading whatever those wires already say.

Let's make execute concrete with one instruction, and read it the way the control unit does — not as "what LDA means," but as which control lines light up on each step. Step through LDA's full five-beat life below. Steps 0–1 are the shared fetch. Steps 2–3 do the real work: drive the operand to the MAR, then RAM's data into A. Step 4 asserts nothing at all. Read the word of control lines as it changes beat by beat.

And now the rule that turns reading into checking. The bus is one set of wires, and a wire can hold exactly one value at a time. So every legal beat obeys one law: at most one out-enable. Two drivers pushing at once isn't a clever trick, it's a short, and you can watch it fight in the very first panel of this chapter. Load-enables are the opposite. Raise as many as you like. Everything hanging off the bus sees the same value, so any number of registers can catch it on the same edge. That isn't a loophole — it's the whole reason step 1 works, where RAM drives once and the IR and the PC's count-enable both act on that one beat. So from here on, don't just watch the lamps. Scan each beat, count the out-enables, and if you ever find two, either I've made a mistake or that row isn't a row.

LDA — one instruction, five beats, five words the control word · 16 lines · lit = asserted this beat FETCH Fetch · put the program counter onto the address bus PC BUS MAR
Beat 0 — asserts CO + MI
Step through beats 0→4. Watch the word change — not the meaning. Beats 0–1 are the same fetch every instruction runs; only 2–4 belong to LDA.
Nothing here understands "LDA." The control unit is a tiny ROM: feed it (opcode, beat, flags) and out drops this fixed 16-bit word. Beat 4 addresses a row of all zeros — so it asserts nothing, and LDA is done.
Fig. 10. LDA is not a meaning — it's five words. Step through the beats. Each one lights the exact bundle of control lines the control unit asserts, and beat 4 lights none. The unit never "reads" LDA; a ROM addressed by (opcode, beat, flags) just emits a fixed 16-bit word each tick — that is executing an instruction.

You just read LDA out one beat at a time. Five control words, in order, each one doing a single tiny thing. That column is a program, written in the machine's smallest instructions — one per beat, a level below LDA itself. So it earns the prefix: it is a microprogram. Now put two of them side by side and the shape of the whole thing jumps out. Here are the microprograms for LDA and ADD, aligned step for step. Notice that steps 0 and 1 are letter-for-letter identical — the shared fetch we just proved is opcode-blind. The two only diverge at step 2, the moment execute begins. The opcode's entire job is to steer the back half of the sequence.

FETCH EXECUTE step 0 1 2 3 4 LDA opcode 0001 ADD opcode 0011 CO · MI Counter → address RO · II · CE RAM → Instr, PC+1 IO · MI IR addr → MAR RO · AI RAM data → A done ✓ (idle · finished) CO · MI Counter → address RO · II · CE RAM → Instr, PC+1 IO · MI IR addr → MAR RO · BI RAM data → B EO · AI · FI A + B → A done ✓ = = = operand address rides in the instruction word
Step 0 · fetch — both run in lockstep
Steps 0–1 are the fetch — identical for every instruction, run before the opcode is even read. Only the execute half (step 2 on) is opcode-steered.
Fig. 11. The same two microprograms, laid side by side: LDA (load the accumulator) on the left, ADD on the right. Each row is one step of the instruction cycle; the mnemonics are the control lines the ROM raises that step — CO counter-out, MI address-in, RO RAM-out, II instruction-in, CE count-enable, AI/BI load reg A/B, EO adder-out. Drag step and watch: through fetch (steps 0–1) the two panels light identical rows — the green = ties them — because fetching the next instruction is the same job no matter what that instruction is. The opcode hasn't even been read yet. Only at step 3 do they fork: LDA aims at the operand, drops it into A and is done by step 3, while ADD routes it into B and spends step 4 summing. That's the whole point of the opcode — the fetch prefix is shared and opcode-blind, so the opcode's only job is to steer the execute half. That's exactly what a ROM address line does: same low bits (the step counter) feed both, and the opcode bits pick which back-half words come out.

Which raises a fair question: why five steps, exactly? Not four, not eight. The answer is a rule that governs every stepped machine: the counter is only ever as long as its longest instruction. One counter serves all of them, and it can only be built one length. Most of our opcodes finish well before step 4. But at least one needs every beat, and that one sets the ceiling for all of them. Here that one is ADD, and now the fetch merge comes back to collect. ADD needs five beats. It would have needed six if we hadn't folded PC++ into step 1. So the two facts stack instead of competing: the longest instruction picks the number, and the merge shaves one beat off it, and off every other instruction too. Compare the instructions' lengths below and watch the tallest bar fix the counter's size.

clock beats ceiling = 5 THE STEP COUNTER must count to 5 states T0–T4
ADD & SUB need all 5 — they set the ceiling
Click any bar to drop that instruction from the machine. The counter is a shared ruler — it only needs to be as long as the one instruction that needs the most beats. Cut it shorter and that instruction runs out of steps mid-execution.
Fig. 12. Every instruction runs as a burst of clock beats — the first two are the same fetch for all of them (grab the instruction from memory), then a tail of execute beats that differs by instruction. One shared step counter ticks those beats: T0, T1, T2… The counter can only be built one length — so it must be long enough for the tallest bar. Here ADD and SUB each need all five, so the counter counts to 5 — even though NOP finishes in two. Click a bar to pull that instruction out of the machine: knock out the five-beat ones and watch the ceiling fall to four. That's why it's five and not four or eight — it isn't a magic number, it's just the height of the neediest instruction, and every other instruction rides the same ruler.

So what happens to a short instruction — one that's completely finished by step 2? It doesn't get to reset early. The counter still marches to 4 before wrapping. The leftover steps simply assert no control lines. They're no-ops, empty beats where the machine does nothing but wait for the counter to come around. Watch a short instruction finish its work and then idle out the tail.

the µstep counter — 5 fixed beats, always LDI counter = 0 / 4 · length is fixed at 5 PC → MAR control lines the ROM asserts this beat
LDI & JMP are short; ADD is full-length.
T0 · PC → MAR · 2 lines hot
Short instructions finish early — but the counter is a fixed 5 beats, so the leftover beats still fire, asserting no lines at all.
Fig. 13. Every instruction rides the same microstep counter — a small counter that ticks T0, T1, T2, T3, T4 and then wraps. The first two beats are the shared fetch; the rest come from the control ROM. A short instruction like LDI (load a number straight into A) or JMP does its real work by T2 — but the counter can’t reset early. So T3 and T4 still fire, and the ROM simply asserts no control lines on those beats: empty no-op padding on the tail. That’s the whole reason a one-line instruction still costs five beats — the counter length is fixed (set by the longest instruction, ADD), so early finishers just idle to the wrap.

Now we have everything the table needs as input: the opcode (from the IR), the step (from the microstep counter), and the flags (from last chapter). Three fields, fully known on every beat. Time to build the table.

04The control unit is a ROM

So: three fields, each finite, each known on every beat. Glue them end to end, which is all concatenation means, and three things become one binary number. The order is a free choice. It just has to be the same choice every beat. The opcode is 4 bits and takes the top. The step is 3 bits (enough to count 0–4) and sits in the middle. The flags are 2 bits (zero and carry) and sit at the bottom. So the situation is now one 9-bit address: opcode×32 + step×4 + flags, a plain number from 0 to 511. Now stop. A 9-bit pattern goes in. A fixed bundle of lines has to come out. You have built exactly one device with that shape: hand it an address, get back a stored word. Name it before you scroll. It's the ROM from Chapter 11. We never made the control rule be a ROM. It already was one. Set each field below and watch the address assemble.

THE SITUATION THE CONTROL UNIT IS IN OPCODE which instruction · 4 bits STEP micro-step · 3 bits FLAGS Z, C · 2 bits Z C = 3 (ADD) = 1 = 01 ‖ concatenate — paste the bits end to end, in order ‖ the 9-bit ROM address 101 = ROM row 101 of 512
click any bit box above to flip it, or pick a situation
ADD · step 1 · C → addr 101
Order gives each field its place-value: opcode ×32, step ×4, flags ×1. That weighted sum is the address.
9 bits → 512 rows. Every situation the CPU can be in is exactly one row the ROM just looks up.
Fig. 14. The control unit's whole job is to answer one question — given the situation, what should every wire do next? — and a situation is just three facts glued together: the opcode (which instruction, 4 bits), the step (how far into that instruction we are, 3 bits), and the flags (Z = last result was zero, C = it carried, 2 bits). Paste them end to end and you get one 9-bit number — and because order fixes each field's place-value (opcode ×32, step ×4, flags ×1), that number is a plain address from 0 to 511. That's the whole trick: the situation is an address, so the "brain" can be a lookup table — a ROM — with one pre-written answer per row.

And the word the ROM stores at that address? It's the bundle of control lines to raise, one bit per line: 1 means "assert this line," 0 means "leave it alone." That's the entire trick, and it's why the reveal lands. The situation is an address, the control signals are a word, and "decoding an instruction" is nothing more exotic than a memory read. Feed an address in below, and read the control lines out.

ADDRESS IN · 9 BITS (0–511) opcode ×32 step ×4 flags ×1 LDA 4 bits · from the roster 0 S2 1 S1 1 S0 0 Z 0 C addr = opcode×32 + 3×4 + 0×1  (Fig 14’s layout) select picks the ROM row LDA · T3 Z=0 · C=0 CONTROL ROM the stored 16-bit word one row of 512 CONTROL WORD · 16 LINES · FIG 10’S ORDER · 1 = ASSERT
LDA · T3 · Z=0 C=0 → raise RO · AI
Decode = a memory read. The address is opcode ‖ step ‖ flags — 9 bits, one of 512 rows — and the row is a 16-bit word whose every bit is one control line: 1 = assert, 0 = leave alone. The flags sit in the address, which is why JZ needs no logic of its own: Z simply picks a different row. It is also why every other instruction is stored four times over — once per flag combination.
Fig. 15. Feed an address in and the control ROM hands back the word stored there. The address is the 9 bits of Fig 14opcode ‖ step ‖ flags, at place values ×32, ×4 and ×1 — so flip the step and flag bits yourself, and pick the instruction by name (its 4-bit code is the roster’s to fix, not this figure’s). What comes back is this machine’s whole 16-line control word, in Fig 10’s order: a lit lamp means “assert this,” a dark one means “leave it alone.” Check it by hand — LDA at T3 returns RO and AI: RAM onto the bus, A latches it. That’s all “decoding an instruction” ever was — a plain memory read that returns the lines to raise.

I want to be careful here, because "decode is just a lookup" can sound like a cheap trick. So let me show it's a genuine engineering choice with a real alternative. You could build this control logic the hardwired way: a custom tangle of AND/OR gates, minimized by hand, that computes each control line directly from the situation bits. Or you could build it the microcoded way: drop the answers into a ROM and look them up. Flip between the two below. They produce the identical control word, but one is frozen in gates and the other is just data you could rewrite.

Before we wire it up, there's a question you should feel scratching. Look at ADD's execute beat. The ROM raises the ALU's op-select to "add" and, in the same control word, raises register A's load-enable. Same beat. So what does A catch — the sum, or whatever garbage was on the ALU's output while it was still settling? Commit to an answer before you read on. If you said garbage, you've made the one assumption that breaks here: you heard same beat as same instant. It isn't. A beat is an interval, not a moment. The control word goes up at the start of the beat, and the ALU spends that beat settling, because that's all combinational logic ever does — chase its inputs until it catches them. The register doesn't look until the end, on the clock edge. By then the ALU has been holding a stable sum for most of the beat. Nothing races, because the two things you thought were simultaneous are separated by the entire width of a beat. That separation is exactly what the clock period is for.

CONTROL-ROM ADDRESS · 9 bits · one of 512 rows LDA opcode 4b 011 step 3b 00 flags 2b LDA · T3 flags held at Z=0, C=0 CONTROL WORD 6 of this CPU's 16 lines · a SLICE HARDWIRED · a fixed tangle of gates over the opcode decoder (LDA…JMP) and step lines T0–T4 CO T0 0 MI T0 + T2·(LDA+ADD+STA) 0 RO T1 + T3·(LDA+ADD) 1 AI T3·LDA + T4·ADD 1 RI T3·STA 0 J T2·JMP 0 the tangle IS the logic · etched once, never edited CO PC drives the bus 0 MI MAR latches the bus 0 RO RAM drives the bus 1 AI register A latches 1 RI RAM stores the bus 0 J PC loads the bus (jump) 0 word = 001100 · same either way
LDA · T3 → 001100 · gates and ROM agree
Pick a beat, then flip the implementation and watch the output word never move. The gates freeze the decode in copper — and look at the shape of it: CO is one wire, MI is an OR of an AND of three decoder lines. That mess is the whole control unit, fixed at fabrication. The ROM holds the very same bits as data you could rewrite. Same word, either way — that's the fork, and it's real engineering, not a cheat.
Fig. 16. One control unit, two honest ways to build it — and they are indistinguishable from outside. The control ROM is addressed by nine bits: the opcode (4 bits, high), then the step (3 bits, middle), then the flags Z and C (2 bits, low) — one plain address, 0 to 511. Held here at Z=0, C=0, since none of these four instructions reads a flag. The panel draws a deliberate slice: six of this CPU's sixteen control lines (CO, MI, RO, AI, RI, J); the other ten are still there, just not drawn — all 0 on this beat, and the opcode is shown by mnemonic rather than by its code. Pick an instruction and a beat, then flip the implementation. Hardwired computes each line with a fixed gate tangle over the opcode decoder and the step lines — CO = T0 is one wire, but MI = T0 + T2·(LDA+ADD+STA) is already a knot, and there are sixteen of these, one per line, etched for good. Microcoded throws the tangle away and just stores the answer: the address picks a row, the row is the word. Watch the output on the right — it doesn't budge when you flip. (Notice too that LDA at T4 asserts nothing: LDA finished at T3. The empty beats are the price of a counter cut to fit the longest instruction, ADD.) That is why “decode is a lookup” is legitimate engineering: microcode trades a frozen tangle for the same truth kept as rewritable data.

Wire that ROM's output straight to the control lines and step back, because the machine is now complete. The step counter names the beat, the ROM reads the situation and asserts the lines, the datapath obeys, and the edge advances the counter. Around it goes, with no human hand anywhere. That is the split we just watched, said once more in the machine's own words: some control lines act the instant the ROM asserts them. The ALU's op select is combinational — flip it and the result changes live. The registers, though, only capture their inputs on the clock edge; they're sequential. The microprogram is written assuming exactly that split. Watch the finished machine run itself.

µstep counter 0 fetch CONTROL ROM address in op‖0‖zc word out ↓ drive PC → bus op: pass load MAR reg: one ↑ edge = latch + count++
press Start — then watch it run itself
live · combinational — acts instantly edge-latched · sequential — waits for ↑
Watch step 4: the ALU op flips to ADD live, yet the register still shows the old value until the edge. That split is why the microprogram is written the way it is.
Fig. 17. The finished machine, closing its own loop: the µstep counter hands the ROM an address, the ROM asserts a word of control lines, the datapath obeys — and the same clock edge that latches a register also ticks the counter to the next beat, with no hand on the panel. Watch the split the whole scheme leans on: the gold op-select lines act the instant the ROM asserts them (combinational), while the blue register waits for the ↑ edge (sequential). That's why the microprogram is written the way it is — it assumes op-select is already live when the edge arrives to capture the result.

That is the keystone of the entire course, so let it settle: the CPU's brain is the same decode-and-OR ROM you built to light up a seven-segment display. Nothing more. Now let's actually read the table it stores, and meet the one ugly compromise a raw ROM forces on us.

05Reading the control ROM

The best way to understand the table is to drive it. Pick an opcode, then scrub the step counter from 0 to 4, and watch which control lines the ROM asserts at each beat. You're reading the machine's microprogram straight off the silicon. Every instruction is just a little vertical strip of this table.

shared FETCH EXECUTE · this opcode
read ROM[ LOAD·T0 ]
PC → MAR

Address = opcode · step. The word stored at that address is the set of switches to flip this beat — you read the machine's behaviour straight off the silicon.

Fig. 18. Pick an opcode, then scrub the step. Each beat is one address in the control ROM; the stored word lights the control lines it asserts. FETCH (T0–T1) is identical for every instruction — only the EXECUTE strip changes.

Here's the compromise. Our address includes the two flag bits, but for almost every instruction, the flags don't matter. LDA does the same thing whether the zero flag is set or not. A smart design would say "for LDA, don't care about the flags." But a raw ROM has no way to say that. Every address is a distinct row with its own stored word. So each instruction has to be written into the ROM four times over, once for each combination of the two flags, with identical words in all four copies. Only JZ and JC ever put those four copies to real use. See the duplication below.

control ROM — one opcode, stored once per (Z, C) flag pair flags RegWr ALU LoadPC ADDRESSED RIGHT NOW HOLD Z0 C0 → this row distinct words needed 1 copies kept in ROM 4 wasted duplicates 3 AHA — the control ROM has no ‘don’t care’ address line. LDA’s word is duplicated across all 4 rows — 3 are pure waste.
pick an instruction
set the flags — watch which row is addressed
LDA ignores Z and C — all 4 rows identical
Z = the zero flag (last result was 0), C = the carry flag. Only JZ/JC read them — every other instruction stores the same word 4 times over.
Fig. 19. Four copies per instruction. A raw ROM addresses its rows by bits, and it has no way to say “ignore these two.” So the flags Z (zero) and C (carry) become part of every address — which means each opcode must be stored four times, once per flag combination. Pick LDA, ADD or JMP and all four rows come out identical: three pure copies. Only JZ/JC put anything different in those rows — and even they need just two. That gap between what’s stored (always 4) and what’s needed (usually 1) is exactly why a plain control ROM is so wasteful.

Zoom out to the whole table and you can see the waste as a pattern. There are vast blocks of identical rows (the four flag-copies), plus rows that can never be addressed at all. Steps 5, 6, and 7 don't exist in a five-state machine, so those addresses are don't-cares — dead storage. Real designs don't tolerate this. They replace the raw ROM with a PLA (a programmable logic array that does honour don't-cares), or they minimize the logic, or they mask the flag bits out of the address for instructions that ignore them. Here's a slice of the control-ROM table — six opcodes across the eight step-addresses, one flag-copy each — its redundant and unreachable regions shaded — the map you carry out of this chapter.

control ROM  ·  address = opcode × microstep T FETCH EXECUTE unused hover a shaded block  ·  toggle a fix and watch the ROM shrink ROM words needed 48 reclaimed as waste 0 raw ROM: every address gets its own row
redundant copy (fetch)
real per-op work
unreachable don't-care
48 addresses burned — but how many are real?
→ toggle a fix, or hover a shaded block
A ROM must store every address. A PLA honours don't-cares — so the copied and unreachable rows never have to exist.
Fig. 20. The control unit is really a lookup table: each address opcode × microstep T stores one word of control signals. (Fig 19's flag axis is held aside here — this is one flag-copy of six of the sixteen opcodes.) Laid out flat, the waste is glaring — the fetch steps T0–T1 are byte-for-byte identical in every row (a copy, not new information), and steps T5–T7 are never reached because no instruction runs that long, so their words are don't-cares the counter can't even address. Toggle fold and mask, or drop in a PLA: because a PLA honours don't-cares and shares product terms, the 28 redundant and unreachable rows simply never have to exist — which is why real designs don't burn the raw ROM.

That's Chapter 15. The machine's "brain" turned out to be no new invention at all. It's just a microstep counter to supply a sense of when, plus a ROM addressed by opcode ‖ step ‖ flags whose stored word is the control lines to raise. Fetch is the shared, opcode-blind prefix; decode is the invisible ROM read; execute is the back half the opcode steers. The whole thing runs with no hand on the panel. The one scar is the raw ROM's inability to say "don't care," which real designs sand off with a PLA, minimization, or address-masking. So the machine now runs itself. That leaves one last, vertiginous question — the one that turns this whole toy into a computer. The instructions the ROM decodes are just bytes sitting in RAM. The data those instructions chew on are also just bytes sitting in RAM. So what, exactly, is the difference between code and data? Hold that thought. The answer — that there isn't one — is where we go next.

iolinked.com
Written by Ajai Raj