Gates → GPUs · Notes
Unit 1 · Lessons 1–4

Build the smallest useful computer

How can a pattern of voltages become a machine that follows a program? We start with physical states and build up to instructions stored in memory. At each step, a limitation of the machine so far gives us a reason to add the next part.

You'll be able to
Explain how bits, circuits that compute, circuits that remember, and stored instructions combine into a programmable machine. Trace any small program tick by tick, without ever saying "and then the CPU just does it".
You need
Basic arithmetic and patience for keeping track of a few numbers. No electronics background.
A note on models
The machine in Lesson 4, NIB-8, is invented for teaching. It's deliberately tiny so that every bit of its state fits on screen. It is not a real commercial CPU.
Lesson 1

A bit is a state; its meaning is an agreement

LimitationA machine can only hold physical conditions, such as voltages. We need them to stand for information.
New mechanismBinary encoding: agreed ways of reading patterns of two-valued states.
New stateNone yet. Just bit patterns plus conventions for interpreting them.
CostA fixed-width representation has a limited range, so arithmetic on it can overflow.

Why two states?

A wire in a chip carries a voltage, and voltages are noisy. Neighbouring wires couple into each other, the supply ripples, and temperature drifts. If we tried to store ten different levels on one wire, a small disturbance could turn a 6 into a 7.

Digital logic avoids this by asking only one question of each wire: is it low or high? Any voltage in a wide "low" band counts as 0. Any voltage in a wide "high" band counts as 1. Voltages in the gap between the bands do not have a guaranteed 0-or-1 interpretation. A switching signal passes through this region, so a circuit that samples the signal must wait until it has settled into a valid band. Every gate also restores its output to a clean level, so noise doesn't pile up from one gate to the next: each stage regenerates a valid 0 or 1. (A chain of gates still adds delay, and every gate has loading and operating limits, so "regenerated" doesn't mean "free".)

highlow valid 1forbiddenvalid 0 noisy input a gate restored output
Wide bands give noise margins. The noise wobbles stay inside the valid bands, so the gate reads each level correctly and drives a clean 0 or 1. The signal does pass through the forbidden gap while it switches, which is fine: an input only needs a valid level when its value is sampled or relied on, after it has settled. The "1" is a range of voltages, not one magic value.

Place value in base 2

A bit is one 0-or-1 position. One bit gives us two patterns; two bits give us four: 00, 01, 10, 11. Every added bit doubles the possibilities, so n bits have 2n patterns. The next question is what we want those patterns to mean.

If we read a pattern as an unsigned (nonnegative) number, its positions have weights 1, 2, 4, 8, and so on from right to left. Just as decimal 305 means 3 hundreds + 0 tens + 5 ones, binary 00101101 means 32 + 8 + 4 + 1 = 45. The positions holding 0 contribute nothing; find the four 1s in the figure and add their weights.

Reading a byte. Only the positions holding a 1 contribute their weight. Eight bits make a byte, which as an unsigned number ranges from 0 to 255. Hexadecimal is just shorthand: each hex digit stands for exactly four bits, so 0010 1101 is written 0x2D.

In hexadecimal, the digits after 9 are A through F, standing for 10 through 15. Split the byte into groups of four bits: 0010 is 2, and 1101 is 13, written D. Together they make 0x2D; the 0x prefix says the digits are hexadecimal. This is a shorter spelling of the same bits, not another value stored alongside them.

Same bits, many meanings

A bit pattern has no meaning by itself. As an unsigned number, 11111111 represents 255. Under two's complement—the signed-number encoding explained below—the same 8-bit pattern represents −1. The same byte could be eight on/off flags, part of a character encoding, or an instruction fetched from memory. The bits have not changed; the operation using them supplies the interpretation. For example, adding two bytes treats them as numbers, while fetching a byte into an instruction register treats some of its bits as an operation code. Click the bits below to see several readings of one pattern, plus its hexadecimal spelling.

Try:
One pattern, four readings. The last box uses NIB-8's instruction format from Lesson 4: the top four bits choose the operation and the bottom four bits give an address. The byte contains no separate label identifying it as a number, character, or address. The operation being performed determines how the machine uses its bits.

Negative numbers: two's complement

How can the same 8 bits also represent negative numbers? In two's complement, the highest bit has weight −128 instead of +128; the other weights stay the same. Thus 11111111 means −128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = −1. A top bit of 0 still gives values 0 through 127; a top bit of 1 gives −128 through −1. The choice of signed or unsigned reading belongs to the operation, not to a hidden label inside the byte.

The wheel below assigns two sets of number labels to the same 16 bit patterns. Adding 1 advances to the next pattern under either reading. For example, adding 0001 to 1111 produces 1 0000; keeping four bits leaves 0000. This represents unsigned 15 + 1 wrapping to 0, or signed −1 + 1 producing 0. The circuit adds the bit patterns, so the same adder circuit works for signed and unsigned numbers. The interpretation determines whether the mathematical sum fits within the available range.

The 4-bit wheel. Outer labels are unsigned readings (0 to 15), inner labels are signed readings (−8 to 7), and the bit pattern sits on the ring between them. Unsigned values overflow at the top, going from 15 to 0. Signed values overflow at the bottom, going from 7 to −8.

The same wraparound rule explains how to get the bit pattern for the negative of a value B. At each position, B and its flipped bit add to 1, so B plus its flipped pattern equals 1111. The flipped pattern therefore equals 1111 − B; adding 1 makes 10000 − B. Keep the low four bits and the result acts like −B.

For example, 5 is 0101. Flip to 1010 and add 1 to get 1011, which reads as −5. Thus subtraction can reuse addition: A − B = A + (flipped B) + 1, discarding any fifth bit. Lesson 2 will put this rule into an ALU.

The flip-and-add-one bit rule still works at the edge of the range, but the mathematical answer may not fit. An n-bit two's-complement number ranges from −2n−1 to 2n−1 − 1: −8 to +7 in 4 bits. There's no +8, so flipping 1000 (−8) to 0111 and adding 1 yields 1000 again. The bits follow the rule; interpreting the result as +8 would be wrong because +8 has no 4-bit signed representation.

Carry and signed overflow are also different things. 0111 + 0001 = 1000 has no carry out of the top bit, yet as signed numbers 7 + 1 came out as −8: signed overflow without a carry. 1111 + 0001 = 1 0000 does carry out, which means the unsigned sum 15 + 1 didn't fit, but as signed numbers it's −1 + 1 = 0, a correct result despite the carry. Signed overflow happens when two operands of the same sign produce a result of the other sign.

Fixed width means arithmetic wraps around

An 8-bit register can hold only eight bits, so it cannot hold the unsigned number 300. If a calculation produces more bits, this machine keeps the low eight and drops the rest. In arithmetic terms, it keeps the remainder after division by 28 = 256, also called the result modulo 256. Imagine a counter that goes from 255 back to 0 rather than gaining a ninth digit.

200 + 100 = 300. In binary that's 1 0010 1100, nine bits. Keep the low eight, 0010 1100, and you get 44, which is 300 − 256.

Now read the same bits as signed: the 8-bit pattern that means 200 unsigned means −56 in two's complement, and −56 + 100 = 44. The same bits give the correct signed answer. Nothing overflowed in the signed reading, even though the unsigned reading wrapped.

Our teaching machine wraps silently. Real instruction sets must define what happens on overflow, and programming languages add their own rules on top: some wrap, some trap, and in C, signed overflow is undefined. Don't assume every language behaves like this toy.

An address is not the value stored there

To run a program, we will soon need to ask both "which storage location?" and "what value is in it?" Those are different questions, even when the address and the stored value are both written as numbers.

Memory is a collection of numbered storage locations. The address selects a location; the contents are the bits found there. We write M[6] = 42 to mean "read address 6 and you get 42." It does not mean that 6 equals 42. This is abstract notation for now; Lesson 3 builds a circuit that stores and selects the values. In the picture below, a read of address 3 returns 6. What additional step would get us to 42?

read address 3"what's at 3?" use the value 6as an address (a pointer)
Address versus contents. Reading address 3 gives 6. That 6 is only another address if some instruction chooses to use it as one. Following it leads to 42. A value used this way, one that holds an address, is a pointer; using it to read another location, M[M[3]], is a dereference (an indirect read).

Use the returned 6 as the address for a second read: M[6] = 42. We can write the two reads together as M[M[3]] = 42. The extra pair of brackets means another memory access, not a property of the number 6 itself. This previews pointers and indirect addressing; NIB-8's instructions later in this unit use their address field directly.

A machine's widths are separate design choices: how many bits a register holds, how many bits an address has, how long an instruction is, and how much storage one address names. For example, a 4-bit address can name 24 = 16 locations whether each location holds one bit, one byte, or something else. The choices happen to match on some machines, which makes them easy to confuse:

MachineRegister widthAddress names…Instruction length
NIB-8 (this unit)8 bitsone 8-bit word; 16 addresses8 bits
RISC-V RV32I (Unit 2)32 bitsone byte; 232 addresses32 bits
x86-64 (a typical PC)64 bitsone byte; 48- or 57-bit virtual addresses in practice1–15 bytes
PredictAnswer before opening. The reasoning matters more than the number.
  1. What is 01100100 as an unsigned number? How many distinct patterns can a 6-bit register hold?
    Show answer

    64 + 32 + 4 = 100. A 6-bit register has 26 = 64 patterns (0–63 unsigned).

  2. Read 11110000 as unsigned and as 8-bit two's complement.
    Show answer

    Unsigned: 128 + 64 + 32 + 16 = 240. Signed: −128 + 64 + 32 + 16 = −16. Check: 240 − 256 = −16. For any pattern with the top bit set, signed = unsigned − 256.

  3. On an 8-bit machine, compute 180 + 90. Then read the inputs and the result as signed numbers. Is the signed answer correct?
    Show answer

    270 mod 256 = 14. Signed, 180 is −76, and −76 + 90 = 14. Yes, the same bits are the correct signed answer. The adder produces the same low eight bits under both readings. Here, the unsigned sum exceeds its representable range, while the signed sum remains within its range.

  4. M[2] = 9, M[9] = 4, M[4] = 2. What does a plain read of address 2 return? What if you follow the value as an address twice?
    Show answer

    A plain read returns 9. Following once gives M[9] = 4, and following again gives M[4] = 2. The values form a cycle: 2 → 9 → 4 → 2. Each additional read uses the preceding read's value as its address.

Explain the difference between a bit pattern, an interpretation of it, an address, and the contents at that address, using an example of your own.

Lesson 2

Circuits that calculate but can't remember

LimitationBit patterns just sit there. Nothing transforms them.
New mechanismCombinational logic: gates, multiplexers, adders, and an ALU built from them.
New stateStill none. Outputs depend only on the current inputs.
CostPropagation delay. More functions need more gates, which means more area and energy.

Lesson 1 gave us patterns that can stand for numbers. But writing 0111 + 0001 = 1000 on paper does not make any wires calculate it. We need a circuit that turns input bits into output bits. A combinational circuit does exactly that: its outputs depend only on its current inputs. Change an input and, after a short physical delay while the signals settle, the output changes too. It has no memory of earlier inputs.

Five gates on a bench

A gate is a tiny circuit (a handful of transistors) that computes one logical function. Set A = 1 and B = 1 in the bench below. OR lights up, but XOR does not; change B to 0 and both light up. The truth table highlights each combination as you try it.

Truth table

AND is 1 only if both inputs are 1. OR is 1 if at least one is. XOR (exclusive or) is 1 if exactly one is, which makes it a "not-equal" detector. NOT flips its input. NAND is "not AND", and it's special: every other logic function can be built from NAND gates alone.

These gates work on one bit at a time. To apply AND to two bytes, line up their bits and use an AND gate for each position: 00001100 AND 00001010 = 00001000. The 8's place is 1 in both inputs, so it is 1 in the result; all other positions give 0. This is a bitwise operation, different from treating the whole bytes as numbers and adding them.

The multiplexer: choosing a source

A multiplexer (mux) has several data inputs and a select input that picks which one passes through. In the demo, A stays 7 and B stays 12; changing S alone changes which value reaches the output. A processor uses the same choice when one input must receive values from different sources at different times. Later, for example, a mux will let our memory receive either the address of an instruction or the address of data.

Flip the select line. The data inputs never change, only which one reaches the output.

A 2-to-1 mux on 8-bit values. Each of the two inputs is 8 bits wide. One select bit chooses between them: S = 0 passes A, and S = 1 passes B. A 4-to-1 mux chooses between four inputs and needs two select bits; an 8-to-1 mux needs three. The number of selectable inputs and the bit width of each input are separate choices.

Building addition: the full adder

Adding binary numbers column by column works like decimal addition: each column adds two digits and any carry from the column to its right. For example, 1 + 1 is binary 10: write 0 in this column and carry 1 into the next. If another 1 came in as a carry, 1 + 1 + 1 is 11: write 1 and carry 1.

A full adder handles one such column. It takes A, B and carry-in, then produces a sum bit and carry-out. The two outputs must account for the value of the three inputs: A + B + Cin = sum + 2 × Cout. Gates implement those outputs with these Boolean formulas:

sum = A ⊕ B ⊕ Cin     Cout = A·B + Cin·(A ⊕ B)

In these Boolean formulas, ⊕ means XOR, · means AND, and + means OR; that last + is not numerical addition. Read the carry formula in words: a carry comes out if both inputs are 1 (the column generates a carry), or if exactly one is 1 and a carry came in (the column propagates it). Toggle the inputs and follow the highlighted wires:

A full adder from five gates. Copper wires carry 1 and grey wires carry 0. The first XOR computes P = A ⊕ B ("exactly one input is 1"), and the first AND computes G = A·B ("both are 1"). The sum is P ⊕ Cin. The carry-out is G OR (P AND Cin).

Chaining columns: the ripple-carry adder, and why it takes time

Connect four full adders so each carry-out feeds the next column's carry-in, starting with carry-in 0 at the rightmost column, and you can add 4-bit numbers. For 0111 + 0001, the rightmost 1 + 1 produces sum 0 and carry 1; the next two columns each have a 1 and pass that carry left, giving 1000. Gates aren't instantaneous: each takes a small propagation delay to respond, and each column may have to wait for its incoming carry. Set some bits, then press Watch it ripple:

Hatched cells haven't settled yet. Count columns from the right, starting at i = 0. In the worst case, the carry out of column i settles only after about 2i + 3 gate delays, so the fourth column's carry settles after 9. A 64-bit ripple adder would need over 100 gate delays. Real CPUs use faster carry structures instead, such as carry-lookahead adders, which compute carries in parallel using more gates. It's the first of many times we'll trade extra hardware for less waiting.

The ALU: many operations, one chosen result

An adder solves one problem, but a program will need to choose among operations. An arithmetic logic unit (ALU) offers add, subtract, AND, OR and XOR. Try A = 23 and B = 19 below: ADD gives 42; SUB gives 4. The input values stay put. The operation-select signal determines which calculation's result reaches the output.

In this circuit, the bitwise units and one adder work in parallel, then an output mux makes that choice. The adder is shared: for subtraction, the select signal makes it calculate A + (NOT B) + 1, with the "+1" entering as its first carry-in. Choosing SUB therefore changes the adder's inputs and the mux selection. Real ALUs make different choices about sharing circuits and disabling unused work.

Several operations are ready; one result is chosen. The bitwise units and the selected add/subtract variant compute in parallel; grey results are discarded. Parallel hardware costs energy but avoids starting each unit in sequence. The answer still takes time to travel through the chosen unit and output mux. The Z output indicates whether the selected result is zero; a processor could save that indication in a flag register for a later branch.

The demo's Z output is 1 when the chosen ALU result is zero. It is a combinational indication, not a value stored for later. Our NIB-8 machine in Lesson 4 does not have a saved zero flag: its JZ instruction checks whether the value already held in ACC is zero.

What we still can't do

Suppose the ALU has produced 42 and the next step needs that 42. Where will it find the value after we change the inputs for the next operation? The ALU doesn't keep its old answer; after the delay, its output changes with its inputs. We have built a calculator circuit, but a sequence of steps needs somewhere to hold each result. Lesson 3 adds that storage.

PredictTrace it on paper first.
  1. A mux has A = 7 and B = 12. What's the output for S = 0? For S = 1?
    Show answer

    7 for S = 0 and 12 for S = 1. The mux doesn't compute anything with the values. It only routes one of them through.

  2. What are the sum and carry-out of a full adder with A = 1, B = 1, Cin = 1?
    Show answer

    1 + 1 + 1 = 3 = binary 11: sum 1, carry 1. Via the gates: P = 0, G = 1, sum = P ⊕ Cin = 1, and carry = G + P·Cin = 1. Set it in the full-adder demo to check.

  3. Compute 9 − 5 in 4 bits the way the ALU does, as A + NOT B + 1.
    Show answer

    As unsigned inputs, 9 = 1001 and 5 = 0101. Flip 5 to 1010; then 1001 + 1010 + 1 = 1 0100. Drop the fifth bit to get 0100 = 4. The carry-out of 1 means no unsigned borrow was needed. Although 1001 would read as −7 if signed, the four-bit adder still produces the same low bits; the interpretation changes, not the circuit.

  4. Why doesn't building a much faster adder, on its own, give us a programmable computer?
    Show answer

    A faster adder reduces the time needed for one calculation, but it does not retain the result for the next calculation. This ALU changes operation when its operation-select input changes. To execute a program, the machine needs storage for its working state and a controller that derives the select signals from stored instructions. Lesson 3 adds storage; Lesson 4 adds instruction execution.

Point at each part of the ALU figure and say whether it computes, selects, or (so far, nothing) remembers.

Lesson 3

State and the clock turn calculation into a sequence

LimitationA combinational circuit's output depends on its current inputs; it does not preserve a previous result. To use that result in a later clocked step, the machine needs storage.
New mechanismRegisters that capture a value at a clock edge (if enabled) and hold it until the next one.
New stateThe contents of every register. From now on, the machine has a current state.
CostThe clock must be slow enough for the slowest logic path. Storage and clock wiring cost area and power.

Feedback makes memory

The missing storage begins with feedback: a circuit can use its own output to keep a value in place.

Not every loop of gates remembers anything useful, so take a specific one: two inverters, each feeding the other. If the right-hand node is 1, the lower inverter makes the left-hand node 0, and the upper inverter turns that 0 back into 1. The loop agrees with itself, so it stays that way. The opposite state (right 0, left 1) is just as self-consistent. Two stable states: one bit of storage.

The loop alone gives us no way to choose or change its value. It might settle in either state when powered on. To make useful storage, we need a way to write a chosen bit and then let the loop hold it. The mux from Lesson 2 provides exactly that choice.

1 · Two inverters in a loop: it holds Q = 1Q̄ = 0 Q = 1 feeds the lower inverter → Q̄ = 0 →upper inverter → Q = 1 again. Stable. (Q = 0, Q̄ = 1 is the other stable state.) No way in yet. 2 · Add a way to write: a D latch D 10 enable inverter loop(two inverters) Q enable = 1: Q follows D (transparent) enable = 0: Q is fed back, so it holds flip-flop = two latches, opposite clock halves
From a loop that holds to a cell you can write. The inverter loop stores a bit but gives no way to change it. Putting a mux in the loop (the same mux as Lesson 2) chooses between "take new data" and "keep what I have". That's a D latch: while enabled it's transparent, so Q follows D; when disabled it holds. This is a teaching construction; real latch circuits are built more compactly at the transistor level.

While a latch is enabled, its output follows its input. Suppose a latch stores A and its input is computed as A + 1. The latch takes that incremented value, which feeds the adder and produces another increment. That next value can enter the latch while it is still enabled, allowing several updates during one enable interval. To take one step per clock cycle, we need storage that captures a value at an edge.

A flip-flop controls that timing by chaining two latches enabled on opposite halves of the clock. In this rising-edge design, the first follows D while the clock is low. At the rising edge it closes, and the second passes along the first latch's held value. Later changes to D cannot pass through until the next rising edge. From outside, the flip-flop captures D at the rising edge and holds its output for the rest of the cycle. Line up 8 flip-flops sharing one clock and you have an 8-bit register.

A register usually also has a write enable. When it's off, the register keeps its old value even though the clock keeps ticking. Look at how that's built: it's just the mux from Lesson 2, feeding the register's own output back in.

new value 10 write enable D Register8 flip-flops clock ↑ Q (stored value) old value fed back: "keep what I have"
Write enable is a mux. On each rising clock edge the register captures whatever D is. With enable = 0, D is the register's own old value, so nothing changes.

The synchronous model: register state updates at clock edges

We can now describe a clocked machine with a simple rhythm:

  1. Between edges, combinational logic reads the current register values and computes the next values. Its outputs may change several times as signals propagate. For reliable capture, each register's input must settle before the edge and remain stable for the required interval around it.
  2. At the edge, every enabled register captures its next value at the same instant.

Think of an edge as a single handoff. If A holds 3 and B holds 9, and the wiring says A ← A + B and B ← A, the logic computes next-A = 12 and next-B = 3 from the old pair. At the edge, the pair becomes (12, 3) together. B never sees the newly written 12 during that update. This rule lets us trace a whole machine without guessing which register changes "first."

"At the same instant" is the synchronous teaching abstraction. A real flip-flop needs its input stable for a setup time before the edge and a hold time after it, and the clock reaches different registers at slightly different moments (clock skew). Designers budget for all three; as long as those constraints are met, the simple model gives the right answer.
Q changes only at rising edges. D may change between capture windows, even mid-cycle, but it must stay stable during the setup interval before the capturing edge and the hold interval after it (both shaded). Q ignores D until the next edge. In this ideal model Q updates exactly at the edge; in real hardware Q becomes valid a short clock-to-Q delay after the edge (the small slanted arrows), the same delay that appears in the critical-path example below.

Try it: registers read old values

Because all registers capture at the same instant, each next-value computation sees the old values of every register, never a half-updated mix. This is very different from lines of code running one after another. Pick a rule, predict the next values, then tick the clock:

Swap works in one edge because both next values are computed from the old pair. Sequential assignments such as A = B; B = A do not swap them: the first assignment overwrites the old A before the second can use it. Save the old A in a temporary variable to swap with sequential assignments: temp = A; A = B; B = temp. Counter adds a write enable, so turn it off and the value holds while the clock keeps ticking. All arithmetic is 8-bit and wraps at 256.

Why the clock can't just go faster

The clock period is the time from one rising edge to the next. It must be long enough for the slowest path from one register, through logic, to another register to settle. That is the critical path. In the example below, the sending register takes 60 ps to produce its output, the logic takes 700 ps, and the receiving register needs the input stable 40 ps before the next edge. The period must therefore be at least 60 + 700 + 40 = 800 ps. A faster setting would not make the computation finish sooner; it would make the captured result unreliable.

Reg combinational logicits longest path = the critical path Reg clk→Q60 ps logic 700 ps setup40 ps minimum clock period = 800 ps → at most 1.25 GHz At 1.6 GHz (625 ps), the secondregister would capture a valuethat hasn't finished computing.
The minimum clock period here is 800 ps. This simplified calculation ignores clock skew, hold constraints and manufacturing and temperature variation. Selecting a shorter period does not reduce the circuit's propagation delay; the receiving register may capture before its input settles. A shorter period requires changing the circuit: faster transistors, fewer gates in a row, or cutting the path into pieces with registers in between. That last option is pipelining, and it's the first lesson of Unit 3.

A tiny addressed memory, built from registers

Lesson 1 used M[address] as if selecting a storage location were automatic. We can now build a four-word version from registers and the parts we already know. In the figure, we want to read word 1 while preparing to write 99 to word 2.

A read mux with four inputs uses the 2-bit read address 01 to pass register 1's value to read data. For writing, a decoder turns the write address 10 into four separate enable wires; only wire 2 is high. If the overall write enable is on, register 2 captures 99 at the next clock edge. The two addresses select different locations, so the read can return 42 while the write changes word 2.

write data = 99 write enable = 1 write addr = 10 decoder2 → 4 mux read address = 01 read data= 42
M[address] as a circuit. Write: write address 10 (= 2) with write enable 1 makes the decoder raise only its line 2, so at the next clock edge only R2 captures the write data: 5 becomes 99. The other registers see the same data but aren't enabled, so they keep their values. Read: read address 01 (= 1) sets the mux to pass R1, so read data is 42, with no clock edge needed. This is a teaching implementation; large SRAM and DRAM arrays use more specialized structures, but they perform the same two jobs.

Registers, memory, datapath, control

Both registers and memory hold bits. In a processor, a small set of named working registers is close to the arithmetic circuits, while a memory array offers locations selected by addresses. The four-word example and NIB-8's 16-word memory are intentionally tiny; practical main memories are vastly larger.

RegisterMemory array
SizeOne value per register (say 8–64 bits); a simple CPU exposes a small set of working registers to its instructionsFour locations in our example, 16 in NIB-8, often billions of byte locations in a real main memory
How you pick oneBy its wiring or a small register numberBy an address
AccessSmall and directly selected by instructions, so typically the fastest storageSelected by a memory address; typically slower in real systems, with access time depending on the design

A register file may itself use a small number to select a register, so the distinction is not simply "numbers versus no numbers." The useful distinction for this course is the role: working registers are operands that CPU instructions can name directly; memory supplies many addressed locations reached through memory operations. Exact size, speed and number of simultaneous accesses depend on the machine.

We now have circuits that hold values and circuits that transform them. Together, registers, an ALU, muxes and memory form a datapath. Control logic chooses the ALU operation, mux inputs and write enables. So far those choices are built into a particular circuit, as with the counter above. Lesson 4 gives the same hardware a way to follow different sequences: instructions read from memory.

PredictRemember: all registers read old values.
  1. A = 3, B = 9. At one edge, A ← A + B and B ← A. What are A and B afterwards?
    Show answer

    A = 12, B = 3. B receives the old A (3), not the new 12. Both next values were computed from the state before the edge. Check with the "A←A+B, B←A" rule above.

  2. A register holds 12, and its input logic is producing 13, but write enable is 0. What happens at the next edge?
    Show answer

    It stays 12. The 13 is computed and discarded. The enable mux feeds the old value back in.

  3. Starting from A = 0, B = 1 with the Fibonacci rule (A ← B, B ← A + B), what is B after 5 ticks?
    Show answer

    (A, B) goes (0,1) → (1,1) → (1,2) → (2,3) → (3,5) → (5,8). So B = 8. Each tick advances the whole pair at once.

  4. A design's longest logic path is 700 ps and its registers add 100 ps of overhead. Why can't we just double the clock frequency?
    Show answer

    The period must be at least 800 ps (1.25 GHz). At double that frequency (400 ps), the capturing register would sample the logic's output long before it settled, violating its setup time. The captured value would be unreliable: it might be the old value, the new one, or a mix. To go faster you must change the circuit, for example split the path with extra registers (pipelining).

Describe any clocked machine as three things: its current state, the combinational logic that computes the next state, and the rule for when state changes (the clock edge and write enables).

Lesson 4

Stored instructions choose what happens next

LimitationOur counter always does the same thing. Changing its behaviour means rebuilding the circuit.
New mechanismInstructions stored as numbers in memory, a program counter, and control logic that decodes each instruction into select and enable signals.
New statePC, instruction register, accumulator, a halted flag, and the control phase.
CostEach instruction uses one fetch tick, one decode tick and one execute tick. Arithmetic uses a single working register, ACC, so keeping several intermediate values requires additional LOAD and STORE instructions. Instruction fetches and data accesses share one memory.

The counter from Lesson 3 has state, but its wiring always tells it to do the same thing. Making it count down or stop at zero would require different control. A stored-program machine keeps those choices as numbers in memory. One fixed circuit fetches a number, decodes it, and performs the requested action; change the numbers and the circuit follows a different sequence.

Meet NIB-8

Our machine is small enough that its entire specification fits in one table. Keep an eye on two values in particular: PC chooses which instruction comes next, while ACC holds the working data that arithmetic changes.

PartWhat it is
M[0] … M[15]16 memory words of 8 bits each. Instructions and data share this memory.
ACCThe 8-bit accumulator: the one working register that arithmetic reads and writes.
PCThe 4-bit program counter: the address of the next instruction to fetch.
IRThe 8-bit instruction register: holds the fetched instruction while it's decoded and executed.
phaseWhere the control is: fetch, decode, or execute.
haltedA flag that stops the machine.

On reset, NIB-8 starts with PC = 0, ACC = 0 and phase = fetch. Every instruction is one byte, split into two nibbles of four bits each. The upper nibble is the opcode, which chooses an action. The lower nibble usually supplies an address: a data location for LOAD, STORE, ADD or SUB, or a destination for JMP or JZ. HALT ignores it. The memory bytes themselves carry no "instruction" or "data" label. Fetching a byte using PC treats it as an instruction; reading that same location as an operand would treat it as data.

opcode 0011 = 3 → ADDaddress 1010 = 10 0x3A means "ADD 10": ACC ← ACC + M[10]

In the table, [a] is a placeholder for one hexadecimal address digit (0 through F), not literal characters stored in the byte. For example, 0x1[a] means an upper nibble of 1 followed by whichever address nibble the instruction uses.

InstructionByteWhat happens during execute
LOAD a0x1[a]ACC ← M[a]
STORE a0x2[a]M[a] ← ACC (ACC unchanged)
ADD a0x3[a]ACC ← (ACC + M[a]) mod 256
SUB a0x4[a]ACC ← (ACC − M[a]) mod 256
JMP a0x5[a]PC ← a
JZ a0x6[a]PC ← a if ACC = 0; otherwise nothing
HALT0xF0halted ← true. Any 0xF_ byte counts as HALT, since the low nibble is ignored.

Thus LOAD 8 is 0x18 and STORE 10 is 0x2A. LOAD 8 means "read the byte at address 8," not "put the literal number 8 in ACC." This small machine has no instruction for loading an immediate constant; constants are placed in memory. Bytes with other opcodes aren't NIB-8 instructions. If one is fetched, our teaching rule is that the machine halts.

One program as memory contents. Put 0x18, 0x39, 0x2A, 0xF0 at addresses 0–3. Put the data 23 and 19 at addresses 8 and 9, and start address 10 at 0. The four instruction bytes mean LOAD 8, ADD 9, STORE 10, HALT.

LOAD brings M[8] = 23 into ACC. ADD reads M[9] = 19 and changes ACC to 42. STORE copies that 42 into M[10]; it does not change ACC. HALT stops. The addresses 8, 9 and 10 are written inside the instructions, while 23, 19 and eventually 42 are the contents at those addresses.

The datapath

Read the diagram in two passes. To fetch an instruction, the address mux sends PC to memory; the returned byte enters IR, and the small adder prepares PC + 1. To execute LOAD or ADD, the same memory instead receives the address in IR. Its data goes to the ALU, whose result can enter ACC. One memory serves both jobs because the mux selects an address source at the right time.

STORE takes another route, sending ACC's value to memory. JMP and a taken JZ overwrite PC with an address from IR. The control unit chooses these routes and enables writes; it does not move data by itself. The simulator will light up the route used on each tick.

Memory16 words × 8 bits addrread datawrite data addr mux PC +1 next PC IR Controldecode sets mux selects, ALU op, enables halted ACC ALU PCIR.addrIR.addr (jump target)opcodeACC = 0?M[addr]result
The NIB-8 datapath. In the simulator below, the wires and blocks used in the current tick light up. LOAD sends memory data through the ALU unchanged ("pass B"), so one wire back into ACC serves LOAD, ADD and SUB. The dashed wire lets control test whether ACC is zero for JZ.

Fetch, decode, execute

NIB-8 spends three clock ticks on every instruction. This is our teaching timing model, not a claim about real memory speeds:

  1. Fetch. The address mux sends PC to memory, and the byte that comes back is captured in IR. At the same edge, PC ← (PC + 1) mod 16: the 4-bit PC wraps from 15 to 0. Both updates use the old PC, just like in Lesson 3.
  2. Decode. Control looks at IR's opcode and determines the required selects and enables. The phase register advances to execute at the edge, but PC, IR, ACC and memory do not change.
  3. Execute. The instruction's effect happens: ACC, memory or PC changes, or the machine halts.

Trace the first instruction of the example above. At tick 1, fetch reads M[0] = 0x18 into IR and advances PC from 0 to 1. At tick 2, decode recognizes opcode 1 (LOAD) and address 8. At tick 3, execute uses that address to read M[8] = 23 and captures 23 in ACC. Only then does the phase return to fetch, with PC still 1. The next three ticks fetch, decode and execute ADD 9; the three after those do the same for STORE 10. Separating these moments makes it possible to say exactly which value each circuit sees.

One instruction cycle (fetch, decode, execute) here takes three clock cycles. The two terms are easy to blur. Also notice that PC already points to the next instruction while the current one executes. That's why a jump simply overwrites PC.

Inside the control box

The controller is a small state machine: a phase register plus combinational logic. The register records whether the machine is fetching, decoding or executing. The logic reads the current phase, the opcode in IR and whether ACC is zero. It produces control signals for the datapath and the next phase.

For example, during execute, LOAD enables a write to ACC and selects memory data as the ALU's output. STORE enables a write to memory and leaves ACC alone. The diagram and table below spell out the choices.

opcode (IR bits 7–4) ACC = 0? halted Control decode logic(AND / OR / NOT gates) e.g. ACC write =execute AND(LOAD OR ADD OR SUB) LOAD = NOT o3 AND NOT o2AND NOT o1 AND o0 phase registerfetch → decode →execute → fetch next phase (unchanged once halted)
Decoding is a Boolean function. Every control signal is some combination of the phase bits, the four opcode bits and ACC = 0?. Recognizing LOAD (opcode 0001) is one AND gate over the opcode bits and their inverses. The phase register steps fetch → decode → execute → fetch on each clock edge; once halted is set, the controller stops asserting any write.

The whole controller fits in one table. A dash means the signal doesn't matter; every write enable not listed is 0.

Phase / instructionMemory address fromALU opWrites enabledNext phase
fetch (every instruction)PC—IR ← M[PC]; PC ← PC + 1 (next-PC mux selects +1)decode
decode——noneexecute
execute LOADIR.addrpass BACCfetch
execute ADD / SUBIR.addradd / subACCfetch
execute STOREIR.addr—memory (write data = ACC)fetch
execute JMP——PC (next-PC mux selects IR.addr)fetch
execute JZ——PC (selects IR.addr) only if ACC = 0fetch
execute HALT or unknown——halted ← 1stays put

Run it: the NIB-8 simulator

Start with Add two numbers. Before pressing Tick, predict the first three edges: which byte enters IR, when PC advances, and when ACC finally becomes 23. Then follow ADD and STORE the same way. Memory cells are editable: type a hex byte (one or two digits, 0–9 and A–F) into any cell to change the program or its data, and the machine resets. Reset restarts from the memory as you've edited it; clicking a program button restores that program's original bytes.

Program:

Memory (hex, editable)

outlined = PC · teal = read · copper = written

Highlighted: last completed phase · next phase: Fetch

FetchDecodeExecute

Things to watch. During fetch, PC advances while ACC doesn't move. ADD changes ACC but not memory. STORE can change memory; no other NIB-8 instruction writes to it. In the loops, JMP and JZ don't move any instructions around. They only change which address the next fetch uses.

Branches turn a sequence into a loop

Without branches, execution follows consecutive addresses. In NIB-8, the four-bit PC wraps from 15 to 0, so a program with no HALT would eventually run through memory again. Branches let a program choose a different next address, including making a choice based on the data. The Countdown program is a loop that ends by testing ACC:

0: LOAD 8ACC ← n 1: SUB 9ACC ← ACC − 1 2: STORE 8n ← ACC 3: JZ 5ACC = 0? yes 5: HALT no 4: JMP 1 PC ← 1: fetch SUB again
The loop's instructions stay in memory; PC revisits them. Starting with n = 3, ACC goes 3 → 2 → 1 → 0. STORE changes the data at M[8]; the instruction bytes stay put. The fixed circuit follows those bytes, and JZ or JMP changes which one PC selects next.

Follow the first trip around the Countdown loop. LOAD puts 3 in ACC. SUB leaves 2; STORE writes that 2 to M[8]. JZ sees that ACC is not zero, so PC continues to JMP, which sends it back to SUB at address 1. ACC already holds the current count, so the loop need not LOAD it again. When SUB eventually leaves 0, JZ sends PC to HALT instead.

Countdown has only one changing value to keep in ACC. Try the Multiply program next: it has both a growing product in M[12] and a decreasing counter in M[14]. Its instruction bytes occupy addresses 0–8. The loop at 0–7 adds M[13] = 6 to the product, then subtracts M[15] = 1 from the counter. Because ACC holds only one working value at a time, each pass must LOAD and STORE both values.

Starting from product 0 and counter 3, the first pass leaves (product, counter) = (6, 2), the second (12, 1), and the third (18, 0). JZ then skips the JMP and fetches HALT at address 8. The first two passes execute eight instructions each; the final pass executes seven, followed by HALT: 8 + 8 + 7 + 1 = 24 instructions. If the counter began at 0, this particular loop would still run once before testing it.

What is an "execution context"?

Suppose we pause NIB-8 and later resume it. Its architectural state includes the program-visible register and memory values: PC, ACC, halted and the memory contents. At a clean instruction boundary (between an execute and the next fetch), the saved CPU context consists of PC, ACC and halted. Memory remains separate from this register snapshot and must also be available. IR and phase record the hardware's progress through an instruction:

Saved CPU context

  • PC: where to continue
  • ACC: the working value
  • halted

Must also be available

  • The same memory contents: left in place for a pause on this machine, or shared or copied for a different machine

Can be dropped at a boundary

  • IR: the next fetch refills it
  • phase: always "fetch next" at a boundary
  • ALU outputs and mux settings

Pause mid-instruction, after fetch but before execute, and saving PC, ACC and halted is no longer enough: you'd also need IR and the phase. Switching at an instruction boundary avoids saving those intermediate values. Correct resumption still needs the same memory contents. On this machine they can stay in place. A second independent NIB-8 would need access to the same memory or a copy of it. A real operating system saves a processor's necessary registers and preserves the program's memory and mapping while switching between tasks; Unit 2 develops that picture.

A first performance calculation

For CPU execution time, three factors multiply together. We will use this accounting throughout the course:

time = instructions executed × cycles per instruction (CPI) × seconds per cycle

The Multiply program (6 × 3) executes 24 instructions, counted above from its three loop passes and HALT. NIB-8 always takes 3 clock cycles per instruction, so that's 72 ticks. At a hypothetical 100 MHz clock (10 ns per tick), its CPU execution time is 720 ns.

A faster clock would help only if the circuit could still meet its timing (Lesson 3). A smarter design could lower CPI by overlapping instructions (Unit 3). A better algorithm could lower the instruction count. Clock frequency alone never tells you which machine finishes first.

Two terms need care once we leave NIB-8. Instruction count means instructions actually executed, including every repetition of a loop, not merely the number of instructions stored in the program: Multiply has nine instructions in memory (eight in the loop plus HALT) but executes 24. CPI, cycles per instruction, is an average over that run: total CPU cycles ÷ instructions executed. NIB-8's CPI is always 3, but on real machines it changes with the program and its data. At a fixed clock period the equation gives CPU execution time; elapsed time can also include waiting for I/O or for the operating system to schedule the program.

PredictUse the simulator only to check.
  1. In "Add two numbers", right after the 6th tick, what are PC, ACC and M[10]? Why hasn't M[10] changed yet?
    Show answer

    Tick 6 completes the execute of ADD 9: PC = 2, ACC = 42 (23 + 19), M[10] = 0. STORE is fetched on tick 7 but does not write M[10] until its execute phase on tick 9.

  2. Change the inputs to M[8] = 250 and M[9] = 10 (0xFA and 0x0A). What gets stored?
    Show answer

    260 mod 256 = 4. NIB-8 stores 4 and provides no carry or overflow flag. This program performs no overflow check; detecting overflow would require additional software logic (for unsigned addition, checking whether the result is smaller than an operand) or assumptions about the inputs.

  3. How many instructions does Countdown (starting n = 3) execute, including HALT, and how many ticks?
    Show answer

    LOAD, then three rounds of SUB-STORE-JZ, with JMP after the first two rounds only, then HALT: 1 + 3×3 + 2 + 1 = 13 instructions, so 39 ticks.

  4. In Multiply, what happens if the counter M[14] starts at 0?
    Show answer

    The loop adds once, then computes 0 − 1 = 255, which isn't zero, so it keeps going. It runs 256 times before the counter wraps back to 0, and the product becomes 6 × 256 mod 256 = 0. The loop tests the counter after adding, so a zero count was never handled. Try it in the simulator (set cell 14 to 00) and use Run.

  5. For "Add two numbers", count instruction-fetch reads, data reads and data writes.
    Show answer

    4 instruction fetches (one per instruction, including HALT), 2 data reads (LOAD 8, ADD 9) and 1 data write (STORE 10): 7 memory accesses in total. The program takes 4 × 3 = 12 ticks under NIB-8's fixed timing rule. Counting accesses does not tell us which component limits execution time. In Unit 2, additional registers let a program retain more working values in the CPU, reducing some LOAD and STORE instructions.

  6. You pause right after tick 4 of "Add two numbers" and save only PC and ACC. Why can't a second NIB-8 continue from exactly that point?
    Show answer

    Tick 4 was a fetch: IR now holds ADD 9 and PC is already 2. With only PC = 2 and ACC = 23, the second machine would fetch address 2 (STORE) and skip the ADD entirely. It needs IR and the phase too, or else you should pause only at instruction boundaries.

Without the simulator: explain why the machine needs both PC and ACC, why ADD and STORE are separate instructions, and how JMP changes control flow without moving any instructions.

Unit 1 · Summary

Putting it together

Use LOAD 8 as a final test. Explain aloud how the machine finds the instruction, reads M[8], and changes ACC at the execute edge. The two chains below are prompts: if an arrow seems mysterious, retrace that part in the datapath figure.

Data path: a typical arithmetic result, from operands to a stored value.

bit patterns in registers and memory→ selected by muxes asoperands→ transformed by theALU→result→ written back at theclock edge

Control path: what decides which values move.

PC→ addresses the nextinstruction→ fetched intoIR→ decoded bycontrol→ setsmux selects, write enables, next PC

Keep the distinction from Lesson 1 in view throughout: an address picks a location, the contents there are a bit pattern, and the operation using it decides how it is interpreted. At an instruction boundary, NIB-8 can resume from the saved PC, ACC and halted values, provided its memory contents are preserved. These saved CPU values form its context. Resuming mid-instruction also requires IR and the control phase.

LessonMechanism addedState it addsWhat still limits us
1Binary encoding—An encoding assigns meaning to bits; we still need circuits to compute with them.
2Gates, mux, adder, ALU—Combinational outputs depend on current inputs; no stored state carries a result to a later clocked step.
3Registers + clockRegister contentsThe circuit's wiring determines its operations; stored instructions cannot select them yet.
4Stored programPC, IR, ACC, phase, haltedOne accumulator: keeping several working values requires frequent saves to and reloads from memory

That last limitation is where Unit 2 begins. Look at the Multiply loop. It occupies eight instructions (addresses 0–7), and four of them, LOAD 12, STORE 12, LOAD 14 and STORE 14, transfer the product and the counter between memory and ACC, because there's only one place to keep a working value. With separate registers for the product and counter, arithmetic instructions can read and update those registers directly, avoiding the LOAD and STORE instructions used to move these working values through memory.

One more, unseen: a transfer exercise

Memory holds M[0..4] = [0x18, 0x39, 0x64, 0x2A, 0xF0], with M[8] = 250, M[9] = 6 and M[10] = 99. Everything else is 0, and the machine starts with PC = 0, ACC = 0. Decode the program by hand, then predict the final PC, ACC and M[10], the number of instructions executed, and the number of ticks.

(after you've made your prediction)
Show the worked trace

The program is LOAD 8 · ADD 9 · JZ 4 · STORE 10 · HALT. The trick is the ADD: 250 + 6 = 256, which wraps to 0 in 8 bits, so JZ 4 is taken and the STORE never runs.

tickphasePC afterIRACCwhat happened
1fetch10x18 LOAD 80IR ← M[0]; PC ← 1
2decode1LOAD 80opcode 1 = LOAD, address 8
3execute1LOAD 8250ACC ← M[8] = 250
4fetch20x39 ADD 9250IR ← M[1]; PC ← 2
5decode2ADD 9250opcode 3 = ADD, address 9
6execute2ADD 90ACC ← 250 + 6 = 256, wraps to 0
7fetch30x64 JZ 40IR ← M[2]; PC ← 3
8decode3JZ 40opcode 6 = JZ, target 4
9execute4JZ 40ACC is 0, so taken: PC ← 4 (STORE is skipped)
10fetch50xF0 HALT0IR ← M[4]; PC ← 5
11decode5HALT0opcode 15 = HALT
12execute5HALT0machine halts

Final: PC = 5, ACC = 0, M[10] = 99 (unchanged), after 4 instructions, 12 ticks. PC is 5, not 4, because fetching HALT already advanced it.

Variation: with M[9] = 5, the sum is 255, so JZ is not taken (PC stays 3). STORE 10 then writes 255 into M[10], and HALT ends the run with PC = 5 and ACC = 255: 5 instructions, 15 ticks. If your prediction differs, find the first row of the table where it goes wrong.

NIB-8 versus real machines. These are invented teaching rules, not facts about real processors: 16 memory words, one accumulator, a 4-bit opcode and 4-bit address in every instruction, exactly 3 clock ticks per instruction, silent 8-bit wraparound, and halting on an unknown opcode. The real-machine comparisons on this page come from published sources: RISC-V RV32I's byte addressing and 32-bit base instructions (RV32I specification); x86-64's 1–15-byte instructions (Intel Software Developer's Manuals); setup, hold and clock-period constraints (MIT 6.004 timing notes); and what an OS saves on a context switch versus a process's address space (OSTEP, limited direct execution and address spaces). Links checked September 2026. The 800 ps timing example uses made-up round numbers.