Hardwired vs Microprogrammed Control

We have a datapath full of gates and load-enables, and a control FSM that must, in every state, assert exactly the right bundle of those signals. One question remains, and it is one of the great forks in computer design: how do you actually generate that bundle of control signals for each state?

There are two answers. Hardwired control bakes the state machine straight into logic gates — fast and rigid. Microprogrammed control stores the signal patterns as a little program in a ROM and reads them out one row per state — slower but wonderfully flexible. The choice ripples all the way up to the RISC-vs-CISC debate and lives on inside every x86 chip you own.

Hardwired control: the FSM as raw logic

A hardwired controller is the textbook finite state machine, built the textbook way. A state register holds the current state; a block of combinational logic takes that state (plus the opcode and a few condition flags) and computes two things directly with gates: the control signals to assert this cycle, and the next state to load. There is no memory of micro-instructions — the behaviour is frozen into the wiring of AND/OR gates (or a PLA).

The upside is speed: signals pop out after one shallow layer of logic, so the clock can run fast. The downside is rigidity: changing the control means redesigning and re-fabricating the chip. Fix a bug in a hardwired controller and you are taping out new silicon. This is the natural fit for a RISC machine, whose instructions are so simple and uniform that the FSM is small and rarely needs to change.

Microprogrammed control: the FSM as a tiny program

Maurice Wilkes' insight in 1951 was that a control unit is really just a program, so why not store it? In microprogrammed control the signal pattern for each state is written as a row of bits — a microinstruction — in a small fast ROM called the control store. A microsequencer holds a μPC (micro-program counter) that indexes the control store; each cycle it reads one microinstruction, squirts its bits out as the datapath control signals, and uses the microinstruction's next-address field (steered by the opcode and flags) to pick the next μPC. Executing one machine instruction is running a little microroutine — a straight-line or branching walk through the control store.

The loop is the whole idea: μPC → control store → microinstruction → (signals out) + next address → μPC. Change the machine's behaviour and you don't touch a single gate — you just rewrite the ROM. That is the flexibility that made microprogramming the darling of CISC: rich, irregular instructions (string copies, BCD arithmetic, whole call sequences) become just longer microroutines, no extra random logic required.

The two philosophies, side by side

HardwiredMicroprogrammed
Signals come fromcombinational logic / PLAa ROM (the control store)
Speedfast — one logic layerslower — a ROM read per state
Flexibilityrigid — re-fabricate to changeflexible — rewrite the microcode
Complexity it likessimple, uniform instructionsrich, irregular instructions
Natural homeRISC (ARM, RISC-V, MIPS)CISC (VAX, classic x86, 68k)

Horizontal vs vertical microcode

Microinstructions come in two flavours, trading ROM width against speed:

Horizontal is a wide, sparse truth table; vertical is a dense, encoded assembly language for the datapath. Real machines mix them — a nano-code layer, common fields encoded, timing-critical signals left horizontal.

Simulate it: a microsequencer walking the control store

Here is a miniature control store holding the microroutines for FETCH, ADD and LDR. The microsequencer starts at μPC 0, reads a microinstruction, asserts its signals, and follows the next-address field — dispatching on the opcode at the DECODE state. Notice ADD's microroutine is one micro-cycle while LDR's is three: on a microprogrammed machine, an instruction's cost is literally the length of its microroutine.

// A tiny microsequencer walking a control store (ROM). Each row = one microinstruction. interface Micro { label: string; signals: string[]; next: number | "DISPATCH"; } const store: Record<number, Micro> = { 0: { label: "FETCH1", signals: ["GatePC", "LD.MAR"], next: 1 }, 1: { label: "FETCH2", signals: ["MemRead", "LD.MDR"], next: 2 }, 2: { label: "FETCH3", signals: ["GateMDR", "LD.IR", "PC++"], next: 3 }, 3: { label: "DECODE", signals: ["(dispatch on opcode)"], next: "DISPATCH" }, 10: { label: "ADD", signals: ["ALU_ADD", "GateALU", "LD.REG"], next: 0 }, 20: { label: "LDR.ea", signals: ["ALU_ADD", "GateALU", "LD.MAR"], next: 21 }, 21: { label: "LDR.rd", signals: ["MemRead", "LD.MDR"], next: 22 }, 22: { label: "LDR.wb", signals: ["GateMDR", "LD.REG"], next: 0 }, }; const dispatch: Record<string, number> = { ADD: 10, LDR: 20 }; // the DECODE table const program = ["ADD", "LDR"]; // the machine-code instructions to run let uPC = 0, ip = 0, cycles = 0; while (ip < program.length) { const m = store[uPC]; cycles += 1; console.log(`uPC=${String(uPC).padStart(2)} ${m.label.padEnd(7)} assert: ${m.signals.join(", ")}`); if (m.next === "DISPATCH") { uPC = dispatch[program[ip]]; // decode: jump into this opcode's microroutine } else { if (m.next === 0) ip += 1; // next==0 means "instruction done, back to FETCH" uPC = m.next; } } console.log(`Ran ${program.length} instructions in ${cycles} micro-cycles (ADD:1, LDR:3, + fetch).`);

Modern x86 is a fabulous hybrid. Simple, common instructions (ADD, MOV, loads) are cracked by fast hardwired decoders straight into internal RISC-like micro-ops (μops). Only the rare, gnarly instructions fall through to a microcode ROM that spits out a whole sequence of μops. So Intel and AMD get RISC-like speed on the hot path and CISC-like compatibility on the long tail. Better still, the microcode ROM is shadowed by writable SRAM, so a microcode update — shipped in a BIOS or OS patch — can rewrite the control of a chip already in your machine. That is how Spectre and Meltdown mitigations and countless erratum fixes reached shipped CPUs without a single new transistor. Wilkes' 1951 "store the control as a program" idea is quietly patching your laptop today.

The word "microprogram" trips people up. The control store is not where your machine-code program lives (that is in main memory, fetched via the PC). The control store holds microinstructions — the bundles of gate/load signals that implement each machine instruction. One line of your program (say a single \text{ADD}) is carried out by running a short microroutine of several microinstructions. Two completely different levels: the program is the sheet music; the microcode is how the orchestra's muscles move to play each note.