CS ATLAS A map of computing

Sequential Logic

Sequential logic: from computing to remembering

Everything so far is combinational: change an input and the output follows immediately, and nothing is remembered. But a CPU must keep a result in a register for the next instruction. What this layer adds is when to update.

01Where memory comes from: two gates locked together

The gates so far are combinational: change an input and the output follows. A number computed by the adder exists on that wire for an instant and is stored nowhere.

NOTNOTQ′QTwo NOTs locked in a loop: they hold each other while powered

Wire two NOTs nose to tail: left output to right input, right output back to left input. In steady state Q = NOT(Q′) and Q′ = NOT(Q), each holding the other — as long as power stays on the state persists. That is bistability, the physical origin of all memory (one bit of SRAM is exactly this).

The physical problem: glitches. Why not simply wire the adder output back to its input and accumulate?Because gates have delay. If bits arrive a few hundred picoseconds apart, the combinational output bounces around and produces glitches.Left as an uncontrolled loop, the system would cycle tens of thousands of times in a nanosecond and run away.

Why an even number? With an odd number (three NOTs in a ring) every stage inverts and the signal never settles — that is a ring oscillator, not a memory. Only an even number is stable: remembering costs a loop that agrees with itself.

02DFF: the clock as a sluice gate

A bistable remembers, but you cannot control when it changes: nudge it and it flips at once, outside your control. To make writing happen only at a defined instant you need edge triggering — that is the DFF.

DFF (D flip-flop) is the smallest storage element: on each clock edge it latches its input to its output; the rest of the time the output holds, whatever the input does.

DFFDclkQclkDQupdates on the rising edge only

The clock is a sluice gate: the water may slosh violently inside the pool (gate delays, glitches), but at the click of the rising edge only the settled level is let through. Discretising time buys the digital system its absolute certainty.

On the left is the symbol: D is the data input, and the small triangle on clk means rising-edge triggered. On the right is the timing: Q takes D only at the rising edge of clk and holds it in between.

CHIP DFF {
  IN in;
  OUT out;
  // latch in to out every clock cycle; hold it otherwise
}

A primitive: no PARTS, implemented directly in hardware. The course does not ask you to build it from gates; doing so would be two latches making the “edge”, and the timing only gets subtler.

03Bit: adding a “write or not” switch

With only a DFF it would be written every cycle, but we often want it to hold — so a MUX goes in front: with load=1 it takes the new value, and with load=0 it feeds its own output back, which means no change.

MUXoutinloadDFFclkoutout fed back to the input (hold)
out0ticks so far: 0

load=0: nothing changes — out feeds its current value back to itself.

CHIP Bit {
  IN in, load;
  OUT out;
  PARTS:
  // outv is an internal net: the DFF output drives both out and outv
  Mux(a=outv, b=in, sel=load, out=x);
  DFF(in=x, out=out, out=outv);
}

With load=0 the DFF output is fed back (outv), which holds the value; load=1 writes in. Note that out is the chip’s output pin and cannot be used as an input.

Note Mux(a=outv, ...): it reads outv, a copy of the DFF output, not the chip’s out pin — “An output pin cannot be an input” is a hard HDL rule. Feeding an output back through an internal net is common in sequential circuits — that is what makes state.

04Register: sixteen Bit chips side by side

A 16-bit register is sixteen Bit chips sharing one load — either all 16 bits are written or none move.

Bit 0Bit 1⋯Bit 15load (one line, shared by all 16 Bit chips)in[16]out[16]
in00000000000000000x0000
ticks: 0
out00000000000000000x0000
Register (16 bits)HDLVerify in the Web IDE ↗
CHIP Register {
  IN in[16], load;
  OUT out[16];
  PARTS:
  Bit(in=in[0], load=load, out=out[0]);
  Bit(in=in[1], load=load, out=out[1]);
  Bit(in=in[2], load=load, out=out[2]);
  Bit(in=in[3], load=load, out=out[3]);
  Bit(in=in[4], load=load, out=out[4]);
  Bit(in=in[5], load=load, out=out[5]);
  Bit(in=in[6], load=load, out=out[6]);
  Bit(in=in[7], load=load, out=out[7]);
  Bit(in=in[8], load=load, out=out[8]);
  Bit(in=in[9], load=load, out=out[9]);
  Bit(in=in[10], load=load, out=out[10]);
  Bit(in=in[11], load=load, out=out[11]);
  Bit(in=in[12], load=load, out=out[12]);
  Bit(in=in[13], load=load, out=out[13]);
  Bit(in=in[14], load=load, out=out[14]);
  Bit(in=in[15], load=load, out=out[15]);
}

Sixteen Bit chips share one load: either all 16 bits are written, or none move.

05PC: the register that walks forward on its own

A program counter must do three things: +1 (fall through), load a new value (jump) and reset to zero. Three controls form a chain, later ones winning: reset > load > inc.

MUXincselMUXloadselMUXresetselout[16]in[16]out fed back into the incrementer (auto-increment)
in00000000000000000x0000
ticks: 0
out00000000000000000x0000
PC (program counter)HDLVerify in the Web IDE ↗
CHIP PC {
  IN in[16], load, inc, reset;
  OUT out[16];
  PARTS:
  // outv is an internal copy of out: an output pin cannot be used as an input
  Inc16(in=outv, out=incout);                 // +1
  Mux16(a=outv, b=incout, sel=inc, out=o1);
  Mux16(a=o1,  b=in,      sel=load, out=o2);
  Mux16(a=o2,  b=false,   sel=reset, out=o3);
  Register(in=o3, load=true, out=out, out=outv);
}

Three MUXes in series, the later one winning: reset overrides load, load overrides inc. A copy of the output (outv) feeds Inc16, which gives the increment.

Reading PARTS once explains the priority: three MUXes in series, the later one winning. reset is last, so it overrides everything.

06The clock: one beat, two phases

Every sequential element shares one clock line. Two things happen in a cycle:

  • Edge: the clock flips and every DFF and register updates at once — the new values are photographed at that instant.
  • Settling: between edges the combinational logic (ALU, adders) computes from the new values, waiting for the next edge to store the result.

So how many cycles an instruction takes depends on how long the combinational path is — that is the physical meaning of clock frequency: however fast the clock, it must wait for the slowest path to finish.