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 holdsQ 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.

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.