Sequential Elements and Clocking
The
gates
we have built so far are combinational: their outputs are a pure function of their
current inputs, with no memory. Feed the same inputs, get the same answer, always. But a computer must
remember — the result of one instruction becomes the input of the next, and a program is a
sequence of states unfolding in time. For that we need sequential elements: circuits that
hold a value, and a clock to tell them when to change. This lesson builds the
two workhorses of stored state — the latch and the flip-flop — and
introduces the single most important engineering discipline in all of digital design: making everything
change together, on the tick of one clock.
Bistability: how a circuit remembers
Memory in silicon starts with a trick of feedback. Wire two inverters in a loop — the output of one drives
the input of the other — and the loop has two stable states: 0-then-1, or 1-then-0. Each state
holds itself up forever; the loop remembers one bit. Add a couple of gates so you can
write into it and you have an SR latch (built from cross-coupled NOR or NAND gates), the
ancestor of every memory cell on the chip. The whole art of sequential design is controlling
when that stored bit is allowed to change.
The D latch: level-sensitive, and dangerously transparent
A D latch cleans up the SR latch: one data input D, one enable
(the clock). While the enable is high the latch is transparent — its output
Q simply follows D, tracking every wobble. While the
enable is low it holds — Q freezes at whatever value
D had at the falling edge. Because it responds to the level of the
clock, we call it level-sensitive.
That transparency is the problem. For the entire time the clock is high, changes on
D flow straight through to Q. If
Q feeds back through some logic to D, a signal can
race around the loop several times within one clock phase — the state updates an unpredictable
number of times per cycle. That is chaos, and it is exactly what edge-triggering was invented to kill.
The D flip-flop: capture only on the edge
A D flip-flop samples D at a single instant — the rising
edge of the clock — and holds that snapshot for the rest of the cycle, ignoring everything
D does in between. It is edge-triggered. The standard
construction is a master-slave pair: two D latches clocked on opposite phases. The master
is transparent while the clock is low and captures D; the slave is transparent
while the clock is high and passes the master's captured value out. Because the two are never transparent
at the same time, the data can cross only at the moment of the edge — a clean, once-per-cycle handoff.
Read the waveforms. D rises early and falls late. The latch
follows it the instant the clock is high — it goes high immediately and, crucially, keeps holding when
D falls during a low phase, only reacting at the next transparent
window. The flip-flop ignores all of that: it changes only at the marked clock
edges, taking a crisp snapshot of D each time. Same input, very different —
and much more predictable — behaviour.
- a latch is level-sensitive: transparent (Q follows D) while the clock is
high, holding while it is low;
- a flip-flop is edge-triggered: it captures D only at the clock edge and
holds it for the whole cycle;
- a flip-flop is built as a master-slave pair of latches on opposite phases, so data
advances exactly once per clock cycle.
Registers: flip-flops in a row
One flip-flop stores one bit. Line up n of them, all sharing the same clock,
and you have an n-bit register — a 32-bit register is 32
flip-flops that all capture their bits on the same edge. The register file, the program counter, the
pipeline latches between stages: every one is a bank of flip-flops. State, in a processor, is nothing more
than a collection of these banks, all snapping to new values together on the clock tick.
// A D flip-flop as an object: it only changes on tick(), never between edges.
class DFlipFlop {
private q: number = 0;
tick(d: number): number { this.q = d & 1; return this.q; } // capture D on the edge
get(): number { return this.q; } // read the held value
}
// A register is just a bank of flip-flops sharing one clock edge.
class Register {
private bits: DFlipFlop[];
constructor(n: number) { this.bits = Array.from({ length: n }, () => new DFlipFlop()); }
tick(word: number): void { this.bits.forEach((ff, i) => ff.tick((word >> i) & 1)); }
read(): number { return this.bits.reduce((acc, ff, i) => acc | (ff.get() << i), 0); }
}
const r = new Register(4);
console.log("after reset: " + r.read()); // 0
r.tick(0b1011); // one clock edge captures the whole word
console.log("after tick(1011): " + r.read().toString(2));
// Between edges the input can wobble all it likes — the register does NOT change until the next tick.
The synchronous clocking discipline
Here is the big idea that makes chips designable. Adopt one rule: all state updates
happen on the same clock edge. Between two edges, the combinational logic — adders, ALUs,
multiplexers — is given time to churn and settle to a final value; then, at the next edge, every
flip-flop captures those settled values at once. Time is chopped into discrete cycles, and within
a cycle nothing that matters changes. This turns a terrifying analog tangle of feedback and races into a
clean, discrete state machine you can actually reason about — exactly the model you will use to compute
the
critical path and clock frequency
next.
And this is why edge-triggering matters. A feedback loop that runs through a flip-flop can advance
only once per cycle — one edge, one update — so the oscillation and multiple-update chaos of a transparent
latch simply cannot happen. Edge-triggering tames feedback by allowing state to move
forward exactly one step per tick. Nearly every synchronous computer ever built rests on this discipline.
A latch is indeed smaller and faster than a full flip-flop, and some aggressive designs use
latch-based pipelines (with two-phase, non-overlapping clocks) to squeeze out performance —
transparency can even borrow time across stage boundaries, hiding a slow stage behind a fast one.
But that flexibility comes with a brutal timing-verification burden: you must prove no signal races through
a transparent latch incorrectly. The edge-triggered flip-flop trades a little area and speed for enormous
simplicity — a single clean sampling instant per cycle — which is why it dominates. As so often in
architecture, the winning design is not the fastest in principle but the one that is tractable to build
correctly at scale.
Casual writing calls any one-bit store a "latch," but the distinction is precise and it bites. A
latch is transparent for a whole clock phase; a flip-flop captures at an
instant. Confuse them and your timing analysis is wrong: a design that is safe with edge-triggered
flip-flops can race and fail if someone quietly drops in level-sensitive latches, because data can now
sail through during the transparent window and reach the next stage a full cycle early. When a datasheet
or a colleague says "register," pin down whether they mean edge-triggered — your setup/hold analysis
depends entirely on the answer.
Where this leads
We now have both halves of a synchronous machine: combinational logic that computes and
sequential elements that remember, marching to a shared clock. The only question left is how
fast that clock can tick — and the answer is set by the slowest chunk of logic that must settle
between two edges. That is the critical path, and it is where we go next.