The CPU’s conductor
A CPU has two halves:
- The datapath: registers (PC, MAR, MDR, IR, AC), the ALU, and the paths between them. It can move and combine data, but it doesn’t decide anything on its own.
- The control unit: on every clock tick it switches on a few control signals, such as “copy PC into MAR” or “read memory”, which tell the datapath what to do in that step.
A single register transfer like MAR ← PC is called a micro-operation. Every machine instruction is a short sequence of them.
One instruction = a few micro-operations
The model’s CPU has an accumulator (AC) and four instructions. Every instruction starts with the same three-step fetch:
| Step | Micro-operation | Control signals |
|---|---|---|
| T0 | MAR ← PC | PC→MAR |
| T1 | MDR ← M[MAR], PC ← PC + 1 | READ, PC+1 |
| T2 | IR ← MDR | MDR→IR |
Then the execute steps depend on the opcode:
| Instruction | T3 | T4 | T5 |
|---|---|---|---|
LOAD X |
MAR ← X | MDR ← M[MAR] | AC ← MDR |
ADD X |
MAR ← X | MDR ← M[MAR] | AC ← AC + MDR |
STORE X |
MAR ← X | MDR ← AC | M[MAR] ← MDR |
JUMP X |
PC ← X |
The control unit’s whole job is to produce the right signals at the right step. There are two classic ways to build it.
Hardwired control
A hardwired control unit is a fixed logic circuit:
- A step counter with a decoder gives one line per step: T0, T1, … T5.
- An instruction decoder turns the opcode in IR into one line per instruction: ADD, LOAD, STORE, JUMP.
- AND/OR gates combine them into each control signal:
| Signal | Equation |
|---|---|
| PC→MAR | T0 |
| READ | T1 + T4·(ADD + LOAD) |
| PC+1 | T1 |
| MDR→IR | T2 |
| IR→MAR | T3·(ADD + LOAD + STORE) |
| ADD | T5·ADD |
| MDR→AC | T5·LOAD |
| AC→MDR | T4·STORE |
| WRITE | T5·STORE |
| IR→PC | T3·JUMP |
On the last step of an instruction (T5, or T3 for JUMP) the step counter is cleared so the next fetch starts at T0.
Microprogrammed control
A microprogrammed control unit stores the signals instead of computing them. Each row of a small control memory (ROM) is a control word (micro-instruction): one bit per signal plus the address of the next word.
| Address | Signals on | Next |
|---|---|---|
| 0 | PC→MAR | 1 |
| 1 | READ, PC+1 | 2 |
| 2 | MDR→IR | map |
| 4–6 | ADD: IR→MAR · READ · ADD |
0 |
| 8–10 | LOAD: IR→MAR · READ · MDR→AC |
0 |
| 12–14 | STORE: IR→MAR · AC→MDR · WRITE |
0 |
| 16 | JUMP: IR→PC |
0 |
The control address register (CAR) points at the current word. Each clock step:
- Read the word at CAR and switch on its signals.
- Load the next address into CAR. When it says map, mapping logic turns the opcode into the routine’s start address: here 4 × (opcode + 1).
The words in the model have one bit per signal, which is called horizontal microcode: fast, and several signals can be on at once, but the words are wide. Vertical microcode encodes the signals in short fields that a decoder expands. The words are smaller, but there is an extra decoding delay.
Side by side
| Hardwired | Microprogrammed | |
|---|---|---|
| Built from | logic gates | control memory + sequencer |
| Speed | faster | slower: one control-memory read per step |
| Adding or fixing an instruction | redesign the circuit | rewrite words in the control memory |
| Many complex instructions | very hard to design | manageable |
| Typical use | RISC processors (MIPS, ARM, RISC-V) | classic CISC processors (IBM System/360, older x86) |
Modern x86 chips mix both. Common instructions are decoded by fast hardwired logic, while rare complex ones run from a microcode ROM. Because microcode can be patched, CPU makers can ship microcode updates to fix bugs after a chip is sold.
Code
mem = {0: ('LOAD', 6), 1: ('ADD', 7), 2: ('STORE', 8), 3: ('JUMP', 0), 6: 25, 7: 17, 8: 0}
OPCODE = {'ADD': 0, 'LOAD': 1, 'STORE': 2, 'JUMP': 3}
# Microprogrammed: address -> (signals that are on, next address)
control_memory = {
0: ({'PC->MAR'}, 1), 1: ({'READ', 'PC+1'}, 2), 2: ({'MDR->IR'}, 'map'),
4: ({'IR->MAR'}, 5), 5: ({'READ'}, 6), 6: ({'ADD'}, 0), # ADD
8: ({'IR->MAR'}, 9), 9: ({'READ'}, 10), 10: ({'MDR->AC'}, 0), # LOAD
12: ({'IR->MAR'}, 13), 13: ({'AC->MDR'}, 14), 14: ({'WRITE'}, 0), # STORE
16: ({'IR->PC'}, 0), # JUMP
}
# Hardwired: the same signals as gate equations of the step T and the opcode
def hardwired(t, op):
eq = {
'PC->MAR': t == 0,
'READ': t == 1 or (t == 4 and op in ('ADD', 'LOAD')),
'PC+1': t == 1,
'MDR->IR': t == 2,
'IR->MAR': t == 3 and op in ('ADD', 'LOAD', 'STORE'),
'ADD': t == 5 and op == 'ADD',
'MDR->AC': t == 5 and op == 'LOAD',
'AC->MDR': t == 4 and op == 'STORE',
'WRITE': t == 5 and op == 'STORE',
'IR->PC': t == 3 and op == 'JUMP',
}
return {s for s, on in eq.items() if on}
def step(signals, r):
"""Do the register transfers for one clock step."""
if 'PC->MAR' in signals: r['MAR'] = r['PC']
if 'IR->MAR' in signals: r['MAR'] = r['IR'][1]
if 'IR->PC' in signals: r['PC'] = r['IR'][1]
if 'READ' in signals: r['MDR'] = mem[r['MAR']]
if 'PC+1' in signals: r['PC'] += 1
if 'MDR->IR' in signals: r['IR'] = r['MDR']
if 'ADD' in signals: r['AC'] += r['MDR']
if 'MDR->AC' in signals: r['AC'] = r['MDR']
if 'AC->MDR' in signals: r['MDR'] = r['AC']
if 'WRITE' in signals: mem[r['MAR']] = r['MDR']
r = {'PC': 0, 'MAR': 0, 'MDR': 0, 'IR': None, 'AC': 0}
car, t, done = 0, 0, 0
while done < 4: # run the 4 instructions once
signals, nxt = control_memory[car]
op = r['IR'][0] if t >= 3 else None
assert signals == hardwired(t, op) # both control units agree
step(signals, r)
if nxt == 'map':
nxt = 4 * (OPCODE[r['IR'][0]] + 1) # mapping logic
t = 0 if nxt == 0 else t + 1
done += nxt == 0
car = nxt
print(r['AC'], mem[8]) # 42 42
Common mistakes
- Mixing up the program and the microprogram. The program (LOAD 6, ADD 7, …) lives in main memory and is written by programmers. The microprogram lives in control memory inside the CPU and says how to carry out each instruction.
- Calling microcode software. It is firmware stored in ROM inside the CPU, and the user program can’t change it.
- Forgetting the shared fetch. T0 to T2 (control words 0 to 2) are the same for every instruction. Only the execute steps differ.
- Forgetting that the PC moves during the fetch. After T1 the PC already points to the next instruction, which is why a JUMP has to overwrite it.
Complexity at a glance
| Case / operation | Time | Why |
|---|---|---|
| Fetch (every instruction) | 3 clock steps | T0 MAR ← PC, T1 MDR ← M[MAR] and PC ← PC + 1, T2 IR ← MDR. |
| LOAD, ADD, STORE | 6 clock steps | 3 to fetch + 3 to execute. |
| JUMP | 4 clock steps | |
| One microprogrammed step | 1 control-memory read | Hardwired gates answer in a few gate delays, which is faster. |
| Extra space | Control memory: 13 words × (10 signal bits + next address) |
Quick check
Test yourself — pick an answer to see if you got it.
1. What does the control memory of a microprogrammed control unit hold?
Each control word is one micro-instruction. Together they form the microprogram, often called microcode or firmware.
2. What is the main advantage of a hardwired control unit?
There is no control memory to read at every step. The cost is flexibility, because changing an instruction means redesigning the circuit.
3. In the model READ = T1 + T4·(ADD + LOAD). During which steps of a STORE instruction is READ on?
T1 is part of the fetch, which every instruction does. At T4 the STORE line of the decoder is on, not ADD or LOAD, so that term is 0.
4. After fetching an instruction, how does a microprogrammed control unit find the routine for its opcode?
In the model the start address is 4 × (opcode + 1), so ADD starts at 4, LOAD at 8, STORE at 12 and JUMP at 16.
5. Why did classic CISC processors use microprogrammed control?
Writing a routine in control memory is much easier than designing gates for hundreds of complex instructions. RISC chips have few, simple instructions and use hardwired control for speed.