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

14Making Decisions

Last chapter we pulled our hands out of the machine and let it run. A program counter walks down RAM. A fetch that never varies pulls in each line. There's a handful of verbs, and a JMP that can throw the counter back to the top to spin a loop. It genuinely runs programs now. But sit with the one thing it still cannot do. Every jump it makes is unconditional: JMP always jumps, no matter what just happened. So the machine can repeat, but it can't react. It can't loop while a counter is still above zero. It can't stop when a total is finally reached. It can't take one path if a number came out big and another if it came out small. It has no way to look at its own last answer and let that answer change what it does next. That is the difference between a calculator running a fixed script and a computer that decides: a machine bending its own path based on what it just computed. And the whole trick turns out to be tiny. Let the ALU report one bit of news about each result — did it come out zero? did it carry past the top? Stash those bits in a small flags register. Then add two new jumps that check a flag before they leap. Two facts, two conditional jumps, and the loop learns to choose.

01The march that can't choose

Here's the whole machine as we left it. Every block hangs off one shared bus: the PC, the MAR, RAM, the instruction register, the ALU with its operand registers, and the output display. Take one last calm look at a machine that can compute anything straight-line but can't yet make a single choice.

THE 8-BIT CPU · EVERY BLOCK, EVERY CONTROL LINE 8-BIT DATA BUS ADDR A B Program Counter CO · CE · J +1 MAR MI → ADDR RAM RO · RI (WE) Instruction Reg II · IO A register AI · AO ALU SU · EO A + B B register BI OUT · display · OI 042
out-enable · drives the bus load-enable · reads the bus control · no bus traffic
out-enables — put a byte ON the bus
load-enables — take a byte OFF the bus
control — steer, don't carry data
Click any control line to light the wire it drives. Hover a block to read what it does — or run a real bus transfer below to watch a byte move.
watch a transfer · one out-enable + one load-enable
Hover any block for its job.
Fig. 1. Here is the whole machine at once — every block that matters, wired around one shared 8-bit bus. The vocabulary of this chapter is the set of control lines: the orange out-enables (CO, RO, IO, AO, EO) that shove a byte onto the bus, the green load-enables (MI, RI/WE, II, AI, BI, OI, J) that latch one off it, and the gold control lines (CE, SU) that steer without carrying data. Click any line to light exactly the wire it drives; hover a block to hear its job. Then run a transfer: every move inside a CPU is just one out-enable feeding the bus while one load-enable drinks from it — and an instruction is nothing more than a timed list of which lines to raise.

The one power it has for breaking out of a straight run is JMP. Recall exactly what JMP does: it slams a brand-new address into the PC, so the next fetch grabs whatever line that address names. Here's the counter marching on its own, with a JMP button that yanks it anywhere mid-stride. Press it a few times and feel the limit. It jumps every time you ask, no questions asked.

PROGRAM COUNTER · 4-bit CLK 0 0 0 0 8 4 2 1 0 address (decimal) each tick: PC ← PC + 1, and 1111 rolls over to 0000 wrap: 15 → 0 PC
jump to10
parked at 0  ·  0000
Run the counter and watch it march 0→15 then wrap. Then slam JMP mid-stride — the count carries on from wherever you land. That overwrite is the seed of every loop.
Fig. 2. A 4-bit Program Counter — the register that holds which instruction is next. Press Run and it marches: on every clock tick PC ← PC + 1, the marker stepping 0→1→2 up the address track. Reach 1111 (15) and the next tick doesn't hit 16 — a 4-bit counter has nowhere to put a fifth bit, so it silently wraps back to 0000. That's the whole trick behind repetition. Now the twist: hit JMP and you overwrite the counter live — slam it to any address mid-stride, and the march simply carries on from there. Counting forward, wrapping at the edge, and a jump that rewrites the next step: put those three together and you have a loop.

That "no questions asked" is the flaw. A JMP can't say "jump back to the top of the loop, but only if we're not done yet." It has nothing to look at. And the thing it should be looking at is sitting right there in the datapath: the ALU. Every time the machine adds or subtracts, the ALU produces a result. That result is the freshest news the machine has about what it just did. Watch it churn out an answer for any operation you pick.

the shared bus — 4 bits shown; the real one is 8 bits wide A = 10 B = 6 + ALU OE=0 Hi-Z · floating
Click the bits inside registers A and B to set the operands — they wire straight into the ALU. Pick an op. The answer exists the instant you change anything, but it can't touch the bus until you flip the buffer's output-enable. (Narrowed to 4 bits so the cells stay readable — the machine's own registers and bus are 8 bits.)
tri-stated — result held back, bus floats
buffer disabled — the ALU has an answer, but the bus is floating (Hi-Z)
Fig. 3. An ALU doesn't fetch its inputs from the bus — it has two dedicated operand registers, A and B, wired straight into it. Click their bits and pick an op: the result appears immediately at the ALU's output. But that output isn't hard-wired to the bus — it passes through the ALU's own tri-state buffer. With output-enable OFF the buffer is high-impedance: the answer just waits, and the bus floats, free for some other unit to drive. Flip OE on and the buffer connects the result to the wire — only now does A op B reach the bus. Same discipline as every other talker on the bus: compute privately, speak only when enabled.

So what do we feed the jump? Your first instinct is the honest one: keep the result, and let the jump compare it when it gets there. Price that. A jump is a single yes-or-no, so you would need a comparator to boil eight bits down to one — and you would still have to store the old result somewhere for the comparator to read. You have just invented a register that holds facts about the last answer, with eight bits in it instead of one, and a comparator bolted on top. The reduction to one bit is forced, not chosen. So: right beside the ALU we hang a tiny register, the flags register. Its job is to catch a few one-bit facts about the result the ALU just made. Not the number itself — just yes-or-no notes about it. Our toy keeps exactly two of these facts: did the result come out zero? (the zero flag) and did it carry past the top bit? (the carry flag). Here are the two flag bits, latched from the ALU's output like any other register. Flip the ALU's result and watch the facts update.

But why a register? Why not run a wire from the zero-detect circuit straight to the jump and skip the storage? Trace it and watch the answer die. The SUB lands its result. Then the machine fetches the next instruction — and that fetch drives the bus, reloads the instruction register, and hands the ALU whatever operands the next line wants. By the time a jump one or two instructions later asks "was it zero?", ask it out loud: where is the answer now? Nowhere. A wire holds nothing; it only carries what is being driven onto it right now. That is the whole reason the flags register exists — the fact has to outlive the instruction that made it. And the same trace hands us the loading rule for free. The flags must load only on the edge that writes an ALU result. Latch them on every edge instead and the jump's own fetch would erase the very fact it came to read.

And why two? Not taste either — count the questions a loop actually asks. "Am I done?" and "Am I past it?" That really is the whole list. Subtract one number from the other and read the zero flag: zero means they were equal, so subtract-then-test-zero answers A == B. Read the carry flag instead and, as we will see once the adder is on screen, it answers A >= B. Those two bits cover every unsigned comparison there is. A != B is the zero flag low. A < B is the carry flag low. Two is not a number the toy happened to land on. It is the number the questions needed.

A 200 B 56 ALU A + B raw sum 256 → low 8 bits kept the result — a number 0 0 0 0 0 0 0 0 128 64 32 16 8 4 2 1 carry-out · bit 8 all bits 0? FLAGS REGISTER 1 Z zero 1 C carry latches on every result · 2 bits, not the number
The flags don't keep the answer — they keep facts about it. Drag until the result wraps past 255: watch it land on zero and carry at the same instant, so Z and C both latch 1.
Fig. 4. The flags register — a two-bit scratchpad hung beside the ALU. Every time the ALU produces a result, this little register latches two facts about it, never the number itself: Z (zero) goes high when every result bit is 0, and C (carry) goes high when the addition spilled out of the top of the byte. Push A and B until the sum crosses 255 and you catch the sharpest case — the result wraps all the way back to 0 while carrying out, so both flags snap to 1 in the same instant. That yes/no gossip is what the next instruction reads to decide.

Two bits. That's the entire memory the machine keeps about its own last move, and it's enough. The rest of the chapter is just this: build the circuit that computes each flag, then build the jumps that read them. Start with the simpler flag.

02The zero flag

"Is this number zero?" sounds like it needs arithmetic, but it's the opposite. It needs no arithmetic at all, just a definition. A byte is zero when every one of its eight bits is off. Not "the sum is small" — literally all eight bits reading 0 at once. Flip any single bit on and the byte is no longer zero. Play with that below. The ZERO lamp lights only in the one arrangement where the whole byte is dark.

byte = 0000 0000 = 0 click a switch to flip it ZERO — every bit is off
the byte is zero — all eight bits are off
A byte reads zero only when all eight switches are off at once. Turn a single one on and the whole byte is no longer zero — no matter which bit it was.
Fig. 5. A byte is eight switches. Click any of them and watch the running value and the binary readout change. The ZERO lamp is wired as an eight-input NOR: it glows only when every bit is off at once. Flip a single switch on — any one, worth 128 or worth 1 — and the lamp goes dark, because the byte is no longer zero. Zero is the one state where nothing is on.

So detecting zero is really detecting "are all eight bits off?" And we already have the exact gate for an all-or-nothing question. Recall the AND gate: its output goes high only when every input is 1, and a single 0 anywhere drags it low. It's the demanding gate, the one that insists on unanimity.

A B 0 0 click to flip AND 0 output Y truth table A B Y 000 010 100 111
Before you flip them — of the four combinations, which single one lights the output?
not both 1 → output 0
logic · and
0 ∧ 0 = 0
arithmetic · minimum
min(0, 0) = 0
sets · intersection
A ∩ B =
Watch the three tiles agree, always. Logical and, the minimum of two bits, and set intersection are not three facts to memorise — they're one operation wearing three costumes.
Fig. 6. The AND gate — the demanding one. Flip the two inputs and the output lamp stays dark until both read 1. The same click ripples through three readings that never disagree: logical A ∧ B, the minimum of the two bits, and the intersection of two sets. One operation, three costumes — that's the whole trick.

There's one twist to bridge. AND fires when its inputs are all 1, but we want to fire when the byte's bits are all 0. So we slip a NOT in front of each bit first. Invert every one of the eight, so an all-zero byte becomes all-ones. Then feed those eight inverted lines into one big AND. Now the AND is satisfied exactly when the original byte was zero. That's the whole zero-detect circuit: eight inverters into one wide AND, and its single output is the zero flag. Step through it and watch one stray bit veto the verdict.

Now put that circuit next to the one you may already be picturing, because they are not two circuits. Eight inverters feeding a wide AND fires when none of the eight bits is on. But "not any of them" is precisely what a NOR says. That is De Morgan's law, the identity you already own: invert every input and AND them, and what you have built is an OR with its output inverted — a NOR. So the zero-detect we just derived from the definition is an eight-input NOR. One circuit, two faces. Draw it whichever way you like; the silicon does not care, and now neither do you.

byte bit invert wide AND flag AND all 8 0 Z = zero?
00000000
decimal 0
ZERO detected — all eight bits are 0
Click any bit to flip it. Each bit is inverted, then all eight feed one wide AND. The AND fires only when every inverted line is 1 — which happens exactly when the byte was 0. One stray 1 makes its line drop to 0 and vetoes the whole verdict.
Fig. 7. Invert all eight bits, then AND the inverted lines together. That wide AND is satisfied only when every inverted line is 1 — which is exactly the case where the original byte was 00000000. Its single output is the zero flag. Flip any bit and its inverter drops to 0, vetoing the AND: the flag can't lie about a byte that isn't zero.

And like any fact worth keeping, the flag isn't left dangling on a wire. It's latched into the flags register on the clock edge, right when the ALU's result is latched too. Recall the edge-triggered register: it looks at its input only at the instant the clock rises, and holds that value steady until the next tick. The zero flag is just one such bit, sampling the zero-detect circuit's answer once per beat.

D flip-flop one bit of memory 0 present D D CLK the clock Q 0 Q — what it holds Q 1 Q̄ — its opposite box on a tick -->
Q holds 0. Change D, then tick to capture it.
Notice: flipping D alone does nothing to Q — the box only looks at D on the clock's rising edge.
Fig. 8. The whole edge-triggered register, sealed into one symbol you'll draw from here on: D goes in, the little triangle on the clock pin says edge-triggered — it only listens on the clock's rising edge — and out come Q and , its permanent opposite. Flip D and watch Q ignore you; then tick the clock and see Q snap to whatever D was at that instant, with mirroring it. Present, tick, hold — that's a bit of memory.

One flag down. The zero flag answers "is the result exactly nothing?" That's the question every counting loop lives and dies on. Now the second flag, which is subtler, because it's the one people get wrong.

03The carry flag

Our registers are eight bits wide, so the biggest number one can hold is 255. Add two bytes whose sum climbs past that, and something has to give: the answer no longer fits. Watch the 8-bit adder chew through two bytes, and pay attention to the very top column. When it overflows, a carry is born that has nowhere to land. It falls off the left edge, out of a ninth bit the register doesn't have.

A B Σ carry‑out 0 carry‑in 0 tied to 0 ◀ the carry ripples this way
Click any bit in row A or B to flip it. Each column is one full adder; the carry it makes is fed into the next column to its left — the visible wave is what ripple-carry is named for.
A = 15  +  B = 9
15 + 9 = 24 · carry-out 0
Fig. 9. Eight full adders in a row — one per bit — are all it takes to add two whole bytes. Click the bits of A and B and watch each column hand its carry to the neighbour on its left; the gold wave travelling from the 1s place up to the 128s is exactly why this is called a ripple-carry adder. The rightmost carry-in is tied to 0 (nothing carries into the ones place), and the leftmost carry-out is the 9th bit: when it lights, the true sum didn’t fit in eight bits. Try 127 + 1 to see the carry ripple the whole way, and 255 + 1 to watch the byte overflow to 0 with the carry-out held high.

That stray bit falling off the top is the carry. Because it's lost, the byte that stays behind is the true sum wrapped around. The count rolls over like an odometer running out of digits, back through zero. Eight bits give 256 patterns, so the arithmetic the machine actually does is modulo 256: every result is the real answer with multiples of 256 quietly subtracted off until it fits. Walk the wheel and watch a value climb past the top and reappear at the bottom.

a signed byte — top place now weighs −128, the rest +64…+1 SIGN BIT ↓ −128 +64 +32 +16 +8 +4 +2 +1 0 0 0 0 0 0 0 0 b7 b6 b5 b4 b3 b2 b1 b0 −128 −64 0 +64 +127 negative half positive half = −128 −128 the sign it carries negative
byte  10000000
= −128
top bit is 1 — so the byte is negative
Click any switch. Nothing new here — it's the 4-bit wheel widened to eight places. The top place just flipped its sign to −128, so the wheel now spans −128…+127, and the top bit alone tells you which half you're on.
Fig. 10. Nothing special ever happened at four bits. Widen the wheel to eight and the same rule rides along: give the top place a negative weight — here −128 — leave the other seven at +64 down to +1, and a byte spans −128 to +127. Flip the switches: the top bit alone is the sign it carries — 1 lands you on the negative half, 0 on the positive. Same idea, wider wheel.

Now the trap. This is the single most-confused pair of ideas in the whole build, so let's not open with the warning — let's open with an addition. Four bits: 1111 + 0001. The answer is 0000, and a bit drops off the top. Before you read on, commit: did that go wrong? Say yes and you are right. Unsigned, that is 15 + 1 = 16, and 16 will not fit in four bits, so the answer came out 0. Say no and you are also right. Read the same bits as signed and 1111 is −1, so it is −1 + 1 = 0, which is exactly correct. Same bits, both verdicts true — the question was underspecified. That is the whole lesson. The carry flag answers one specific question: did a bit fall off the top of an unsigned add? That is all it means — unsigned wrap out of the most-significant bit. It is not the same thing as signed overflow, the Chapter-5 idea where two same-signed numbers add to a result whose sign has flipped. That is also exactly when the sign column's carry-in and carry-out disagree, because that column is the only place a same-signed pair can change sign. Read the very same addition through both lenses below. The unsigned lens asks "did a bit fall off the top?" (the carry). The signed lens asks "did the sign go wrong?" (overflow). They can disagree — on 15 + 1 they do, the carry fired and the overflow did not — and in our toy machine, only the carry is captured as a flag. Never call the carry flag "overflow detection." It detects a wrap, not a sign error.

sign bit A + B sum carry off top 8 4 2 1 THE UNSIGNED LENS 3 + 2 = 5 fits 0…15 → stored as 5, nothing fell off CARRY-OUT · dormant THE SIGNED LENS +3 + +2 = +5 fits -8…+7 → stored as +5, correct OVERFLOW · dormant
A = 0011 = unsigned 3 / signed +3 B = 0010 = unsigned 2 / signed +2
neither fires — the stored bits are correct in both lenses
Tip: click any A or B bit to flip it. The carry-out asks "did a bit fall off the top?"; overflow asks "did the carry into the sign bit and the carry off it disagree?" Two questions, one addition.
Fig. 11. One 4-bit addition, read through two lenses at once. The unsigned lens asks a hardware question — did a bit fall off the top? That is the carry-out. The signed lens asks a two's-complement question — did the carry into the sign bit disagree with the carry off it? That is overflow. Try carry only (15 + 1) and overflow only (7 + 1): the same adder, the same bits, and the two flags fire on completely different rows. Sometimes one, sometimes the other, sometimes both, sometimes neither — and it is the distinction this chapter's carry flag is built on.

With that settled, the carry flag is simple to wire. It's just the carry-out of the top adder, latched into the flags register beside the zero flag on the same clock edge. Two facts, one little register, both fed straight from the ALU. Here they are together. Set an ALU result and watch both flags update as one.

So what does that carry-out mean when the adder is subtracting? Do one by hand. The machine has no subtractor: it computes 3 - 1 as 3 + 255, because 255 is how eight bits spell −1. That sums to 258. The byte left behind is 2 — correct — and 258 is past 255, so a bit carries off the top. The carry fires on a plain, boring subtraction that did not go wrong at all. Read it the right way round and it is not a broken flag, it is a different fact: the carry fired because the subtraction never had to borrow. Borrowing is what you do when the top number is too small. So during a subtract, carry set means A >= B, and carry clear means A < B. Check it: 0 − 1 is 0 + 255 = 255, which never reaches 256, so no carry — and sure enough 0 is not at least 1. That is what makes the carry flag worth having.

ALU 256 sum A+B carry-out RESULT · the stored byte (bits 7..0) = 0 ? NOR C 1 carry flag Z 1 zero flag one clock edge — both latch together
(Z, C) = (1, 1) · sum wrapped past 255 — byte is 0 and it carried out
One edge, two verdicts. C is just the top adder's carry-out; Z is a NOR that fires when every result bit is 0. They latch side by side — a running two-bit opinion of the last arithmetic.
Fig. 12. Both flags, fed from the ALU. Drag the sum (or tap a preset): the stored byte is only the low eight bits, so the top adder’s carry-out latches into C while a NOR watching every result bit latches into Z — both on the same clock edge. Push the sum to 256 and watch it happen at once: the byte rolls to 0, Z lights, and C carries.

Now the machine keeps a running two-bit opinion of its last arithmetic: Z for "came out zero," C for "carried out the top." All that's left is to give the instruction set a way to act on that opinion.

04Jumps that check first

We already have the raw mechanism for changing course, and it's worth seeing again, because the new instructions add almost nothing to it. A JMP is nothing but a store aimed at the program counter: it drives the operand onto the bus and lets the PC latch it instead of RAM. The next fetch then starts wherever that address points. Watch the operand overwrite the PC.

MEMORY operand → PC PROGRAM COUNTER 0 PC + 1 every fetch bumps by one … unless a JMP overwrites it 0
PC = 0 · press Step to fetch & execute one instruction
A JMP is just a store whose destination is the PC. An ordinary op lets the +1 incrementer nudge the counter along; JMP N writes N straight in — so the next fetch reads from N. Addresses 4–5 never run: the jumps leap right over them.
Fig. 13. The program counter is just a register — and the fetch–execute loop keeps it moving. For an ordinary instruction the +1 incrementer nudges it to the next address, so execution walks straight down memory. A JMP does something almost boring in hardware: it is a store whose destination happens to be the PC. The operand is written straight in, overwriting the count — so the very next fetch reads from there, and control teleports. Step it and watch 3→6 and 7→1 leap over addresses 4 and 5, which never run at all. Loops, branches, function calls — every one of them is nothing but a carefully aimed store into the program counter.

JZ — "jump if zero" — must do something JMP cannot. Before you read on: which new control lines does it need? Now look. Here is JMP's execute step in Fig 1's vocabulary: raise IO to put the operand on the bus, raise J to let the PC latch it. Two lines. JZ's list is those same two lines, same order, one character changed — J becomes J AND Z. One AND gate, the Z flag on one input, sitting on one enable wire. That is the entire idea of a conditional jump: take that PC-overwrite and gate it on a flag. JZ writes the operand into the PC only when the zero flag is set. If the flag is clear, the write is suppressed. The PC just does its ordinary +1, so the machine falls through to the next line as if the JZ weren't there. Fall-through costs no hardware at all: the +1 was already winning by default. Watch a subtract land on zero, the Z flag snap high, and the PC get overwritten with the target. That's a jump that only happened because the last answer was zero.

executing JZ 0xC · at PC 0x3 1 · the subtract A 5 − B 5 = byte 0 2 · the flags Z = 1 C = 1 3 · the pc's load enable JZ target · onto the bus 0xC J · the PC's load-enable J = 1 AND load enable = 1 program counter 0x3 +1 INCREMENTER
byte = 0, so Z is set → J AND Z = 1 → the gate is open → Execute latches the target 0xC into PC.
Fig. 14. A conditional jump is nothing more than the PC-write, gated on a flag. The subtract is the adder doing A + (~B) + 1, and it latches two flags: set A and B equal and the byte lands on 0, so Z snaps high. (C — the top adder's carry-out — rides along, high whenever A ≥ B; JZ simply never looks at it.) JZ's control-line list is JMP's list unchanged, with one difference: the PC's load-enable J is ANDed with Z. When Z is high the gate is open and the JZ target — the only thing on the PC's bus input — is latched. When Z is low the write is suppressed, nothing travels anywhere, and the PC's own +1, the box drawn inside it, wins by default. Hit Execute JZ: with Z set, 0x3 is overwritten with 0xC; with Z clear, the PC falls through to 0x4 — and that fall-through costs one gate's worth of nothing.

JC — "jump if carry" — is the identical trick reading the other flag. It takes the branch only when the carry flag is set, and falls through otherwise. So the two conditional jumps are one mechanism pointed at two facts. The general shape is a fork: the flag is checked, and the machine goes one of two ways. Flip the flag below and watch a single conditional jump either leap to the target or fall straight through to the next instruction.

So let JC do some real work. SUB then JC is this machine's entire ability to say "A is at least B" — because the carry during a subtract means no borrow, and no borrow means A >= B. Fall through instead and you have just learned A < B. That is the other half of the comparison kit. The zero flag tells the machine equal or not. The carry flag tells it which way. Two one-bit facts, and there is no unsigned comparison left that the toy cannot make.

PROGRAM · executes top → bottom 10 CMP R0, R1 11 JZ done 12 MOV R2, 1 13 JMP end 20 done: HLT fall through JUMP → done ZERO FLAG 0 branch taken fall through the set flag picks the next line
Z = 0 → fall through to the next line
One mechanism, two names: JZ watches the zero flag, JC the carry — each leaps only when its flag is 1, and falls straight through when it is 0.
Fig. 15. One mechanism, two facts: JZ and JC each take the branch only when their flag is set (1), and fall straight through to the next line when it is clear (0) — one instruction, two possible next lines chosen at runtime.

Step back and notice what we just built, because it's bigger than two opcodes. A plain instruction has one successor: the next line. A conditional jump has two possible successors, and the flag picks between them at runtime. That branch — a fork in the path chosen by a computed condition — is precisely the "if this, do that; otherwise, do the other" that hides inside the textbook definition of an algorithm. Sequence, we had. Repetition, JMP gave us. This is the missing third: selection. Here it is as the pure decision it is.

But keep the fork on the hardware, because a test like n >= 5 is not something this machine can do. It owns two one-bit facts and two jumps that read them. There is no comparator and no >= instruction. So the test has to be compiled down into what the toy actually has: load n, SUB 5, then JC. Carry set means no borrow, which means n was at least 5, so the branch is taken. That is what every high-level condition really is, here and on the silicon in your laptop — a subtract, then one flag test. The abstraction is not free. It is translated, and one AND gate is still down there doing the deciding.

7 input · n n ≥ 5 TRUE computed condition true false THEN · if true “BIG” ELSE · otherwise “small” one input · one test · two possible outcomes · exactly one runs
condition TRUE → the THEN branch runs
Sequence and repetition could only ever do one fixed thing. Selection is the fork: a value is tested, and the answer alone picks which branch runs — that if / else is the last piece that makes the toy able to compute anything.
Fig. 16. The third and final control structure: selection. A value flows in, a computed condition tests it — here n ≥ 5 — and the answer alone decides the road. Drag n and watch the token switch tracks the instant the test flips: true routes down the THEN branch, false down the ELSE. Change the test itself and the fork still holds. Sequence does things in order, repetition does them again — but only this if / else lets the toy choose, and with all three it can compute anything computable at all.

Three control structures now: go in order, repeat, and choose. That is genuinely everything you need to express any computation at all. The theorem is Böhm and Jacopini's, from 1966, and we are borrowing it, not proving it here. What it says is that any flowchart can be rewritten using only sequence, selection, and one loop. The toy is, quietly, complete. Let's prove it does something useful.

05The countdown loop

The canonical use of all this is a loop that ends itself: start a counter at some value, do a little work each pass, knock the counter down by one, and keep going until it hits zero. That "down by one" is a decrement, just subtract 1. Here the earlier chapters pay off again. The machine has no subtractor, so it computes it as A + (−1), building −1 by the two's-complement trick. That is what the SU line in Fig 1 actually is. Raise SU and it inverts B on its way into the adder and forces the carry-in to 1 — flip and add one, the two's-complement reflex you already know, spent as a single wire instead of an instruction. So there really is no subtractor in there. There is an adder, and one control line that lies to it about B. Watch a value step down with no subtractor anywhere in sight.

the subtraction how −B is made the one adder — identical circuitry for + and − 4-bit adder · dumb column-by-column 1 2 4 −8 + A −B
A−B is A+(−B). Watch the negate-then-add flow through the one adder — it never learns it subtracted.
Fig. 17. The trick the whole loop rests on: a machine with no subtractor in it subtracting anyway. Pick any A − B and step it through. The subtraction rewrites itself as A + (−B); −B is built by the two-step reflex — flip the bits, add one; then A and −B pour into the identical four-bit adder, column by column, carries and all. On 7 − 5 the top carry drops off the end — a full lap around the wheel, worth 0 — and out falls 2. The adder did the same dumb column-add it always does. It never knew it subtracted, and that's exactly why two's complement won.

Now assemble the pattern. Each pass, decrement the counter. The subtract sets the zero flag for free as a side effect. Then a single JZ asks the flag — not zero yet? then fall through and JMP back to the top. Is it zero? Take the JZ and leap out of the loop. That's the whole shape of every bounded loop ever written. Run it and watch the counter walk down to zero, then the branch finally fire to break out.

program LDI 4 top: DEC A JZ done JMP top done: HLT counter register A ZERO FLAG · Z 0 clear ◼ HALTED — the loop ended itself
Press Run — watch A walk down to zero and the branch finally fire.
laps completed · 0
Fig. 18. The canonical self-ending loop. DEC A — the same SUB 1 through the same adder, one byte shorter — counts down and, for free, sets the zero flag the instant it hits 0. JZ done reads that flag: still counting (Z=0), the jump’s load into the PC is suppressed, so it falls through to JMP top and loops back; zero at last (Z=1), it leaps clean out. No hard-coded jump count — the loop knows when it’s finished because the arithmetic told the flag, and the branch listened.

And to see it as a real program rather than an animation, here's our little interpreter again. It's the same fetch–advance–execute loop from last chapter, now taught the two conditional jumps, running an actual countdown from a starting value down to zero. It fetches, checks the flag, branches or falls through, and prints each step so you can read the decision happening.

program 0 LDI 3 ; A ← 3 1 OUT ; print A 2 DEC ; A ← A+0xFF 3 JZ 5 ; if Z → 5 4 JMP 1 ; loop → 1 5 HLT ; stop machine 0 A · accumulator 0 PC · next line flags Z 0 C 0 zero · carry-out C=1 · no borrow output _
fetchadvanceexecute
ready · PC points at line 0
Step it. Each pass the interpreter fetches a line, advances the PC, and executes — then JZ reads the zero flag and decides: branch, or fall through.
Fig. 19. The interpreter running a branching countdown. The very same fetch–advance–execute loop from Chapter 13, now fluent in JZ. Step it and read the trace: DEC is just SUB 1 — the ALU adds 0xFF on the same eight adders — and it refreshes both flags. The zero flag is what JZ reads to make the call: fall through and loop, or branch to HLT. Watch the carry flag too, and don't misread it: C is the top adder's carry-out and nothing else, so it lights on 3→2, 2→1 and 1→0 — every pass that didn't need to borrow. Only a subtract that borrows leaves it dark. The tape prints 3, 2, 1 and stops the instant A hits zero. That single flag-driven branch is a program deciding.

Read that trace and you're watching a machine decide: subtract, is it zero? no — jump back; subtract, is it zero? no — jump back; … subtract, is it zero? yes — fall out and halt. The loop knows when it's done. Nobody told it. It looked at its own last answer and chose.

That's Chapter 14. Two one-bit facts about each ALU result, latched into a tiny flags register. The zero flag comes from a circuit that inverts all eight bits and ANDs them. The carry flag catches the unsigned wrap out of the top bit (and, we were careful to say, is not signed overflow). Then two jumps that read them, JZ and JC, each just a plain JMP gated on a flag. That gives the machine the one thing it lacked: the power to branch. With sequence, repetition, and selection all in hand, our toy can now run any bounded loop and any decision. It is a real, complete little computer. But look one last time at who's still deciding. Every one of those choices — which control lines to raise, on which micro-step, given this opcode and these flags — is being made by a fixed rule we worked out by hand. And a fixed rule that maps inputs to outputs, with no memory of its own, is a thing you've already built twice: a lookup table. The machine's whole brain is about to turn out to be furniture from Chapter 11 — a ROM. That's next.

iolinked.com
Written by Ajai Raj