Flip-flops and Registers -- How Computers "Remember" Data
From combinational logic to sequential logic: understanding how D flip-flops and registers work
Life analogy: camera shutter
Imagine you have a camera in your hand. Before you press the shutter, the outside world is constantly changing—pedestrians walking, leaves swaying, light alternating between bright and dim.
But at the instant you press the shutter (Clock rising edge), the current scene is "latched" into the photo.
Afterward, no matter how the outside world changes, the content in the photo is forever frozen at that moment. This is the core behavior of a D flip-flop—capturing and saving data at the rising edge of the clock signal.
Combinational logic vs sequential logic
The AND gates, OR gates, NOT gates, and other circuits we studied in previous lectures all belong toCombinational logiccombinational logic. Their outputs are determined solely by the current inputs—when the inputs change, the outputs change immediately (ignoring propagation delay).
But for a computer to "remember" data, it needs a circuit that can maintain its output even after the input disappears. This is sequential logic.Sequential logic。
Core difference
Combinational logic is like a lamp—when the switch is pressed, it lights up; when released, it goes out; the output depends entirely on the current input.
Sequential logic is like a safe—you put something in and lock it (when the clock rising edge arrives), and even if you walk away afterward (when the input disappears), the contents remain inside.
| Comparison Dimension | Combinational logic | Sequential logic |
|---|---|---|
| Output dependency | Depends only on current inputs | Depends on current inputs and previous state |
| Memory capability | None (stateless) | Has (can remember history) |
| Typical circuit | AND gate, OR gate, NOT gate, adder | D flip-flop, register, counter |
| Clock dependency | Does not depend on clock | Relies on clock signal to synchronize state updates |
| Feedback loop | No feedback (acyclic) | Has feedback loops to hold state |
Structure and behavior of the D flip-flop
D Flip-FlopIt is the most basic sequential logic unit. It has two inputs and one output:
D (Data): the data input, representing the value to be stored (0 or 1).
CLK (Clock): the clock input, controlling when data is latched.
Q: the data output, outputting the currently stored value.
Key behavior: The D flip-flop latches data only at the clock signal'srising edgerising edge (the instant it transitions from 0 to 1), latching D's value into Q. At all other times, no matter how D changes, Q remains unchanged.
This is like a camera recording a scene only at the moment the shutter is pressed; at any other time, no matter how the outside scene changes, the photo is unaffected.
Why is a clock needed?
Without a clock signal, the D flip-flop's output would keep changing with the input, turning it into just another combinational logic circuit. "Memory" would be out of the question.
The clock signal provides aSynchronous timing, allowing all flip-flops to update their state at a unified point in time. The entire computer system coordinates its work under this rhythm.
Register: multiple flip-flops in parallel
A single D flip-flop can store only 1 bit of data. To store a byte (8 bits) or a word (32 bits), multiple flip-flops must be connected in parallel.
Connect N D flip-flops in parallel so that they,Share the same clock signal, forming an N-bitRegister。
when the clock rising edge arrives, simultaneously latch the values at their respective D inputs, saving the complete N bits of data at once.
Multi-stage registers and pipelining
Inside the CPU, register outputs are often connected to the next stage of combinational logic, which in turn connects to the next stage of registers—formingRegister-Combinational Logic-Registerpipeline structure.
a pipeline in which, each clock cycle, data flows from one register stage through combinational logic and is stored into the next register stage at the next rising edge. This is the foundation of modern CPU pipelining.
Code demonstration: simulating D flip-flops and registers in Python
The following Python code simulates the behavior of a D flip-flop and a 4-bit register. Pay special attention to the rising edge detection logic: data is captured only when clk is detected transitioning from 0 to 1.
Example
"""
D Flip-Flop: latches input data on the rising edge of the clock
Behavior rules:
- When the clock transitions from 0 to 1 (rising edge), the D input is latched to the Q output.
- At other times (clk=0, or clk=1 but not at a transition edge), Q remains unchanged
"""
def __init__(self, name="DFF"):
self.q = 0 # Output Q, initially 0
self.prev_clk = 0 # The clock value at the previous time instant, used to detect the rising edge
self.name = name
def update(self, d, clk):
"""
Update flip-flop state
Parameters:
d: Data input (0 or 1)
clk: Clock signal (0 or 1)
Returns:
Current Q output value
Rising edge detection: prev_clk == 0 and clk == 1
Only when this condition is satisfied is the value of d latched into q
"""
# === Rising edge detection ===
if self.prev_clk == 0 and clk == 1:
self.q = d # Latch the value of D to Q on the rising edge
self.prev_clk = clk # Record the current clock state for the next cycle
return self.q
def reset(self):
"""Reset flip-flop state"""
self.q = 0
self.prev_clk = 0
class Register4Bit:
"""
4-bit register: 4 D flip-flops in parallel, sharing the same clock signal
Structure diagram:
D3 ──▶ [DFF3] ──▶ Q3 (high bit)
D2 ──▶ [DFF2] ──▶ Q2
D1 ──▶ [DFF1] ──▶ Q1
D0 ──▶ [DFF0] ──▶ Q0 (low bit)
↑↑↑↑ Share the same CLK
When the clock rising edge arrives, the 4 flip-flops simultaneously latch their respective D inputs.
"""
def __init__(self):
# Create 4 flip-flops, with bit[3] as the most significant bit and bit[0] as the least significant bit.
self.flip_flops = [
DFlipFlop("bit3"),
DFlipFlop("bit2"),
DFlipFlop("bit1"),
DFlipFlop("bit0"),
]
def update(self, data, clk):
"""
Update register
Parameters:
data: data to be stored (integer 0-15, out-of-range will be truncated)
clk: clock signal (0 or 1)
Returns:
Value currently stored in the register (0-15)
"""
data = data & 0b1111 # Ensure only 4 bits are valid
# Feed each bit into the corresponding flip-flop, most significant bit first
for i in range(4):
# Extract each bit from high to low: bit3, bit2, bit1, bit0
bit = (data >> (3 - i)) & 1
self.flip_flops[i].update(bit, clk)
return self.read()
def read(self):
"""Read the value currently stored in the register (without changing state)"""
value = 0
for ff in self.flip_flops:
value = (value << 1) | ff.q
return value
def reset(self):
"""Reset all flip-flops"""
for ff in self.flip_flops:
ff.reset()
# ============================================================
# Simulation Demo: Register Behavior over Multiple Clock Cycles
# ============================================================
if __name__ == "__main__":
reg = Register4Bit()
# Test data sequence: simulate writing different data at different clock cycles
test_data = [5, 10, 15, 0, 7, 13, 3, 8, 1, 14]
# 5=0101, 10=1010, 15=1111, 0=0000, 7=0111
# 13=1101, 3=0011, 8=1000, 1=0001, 14=1110
print("=" * 55)
print(" example - 4-bit register simulation demo")
print(" Observation: data is latched into the register only on the rising edge of the clock")
print("=" * 55)
print(f"{'Period':<6} {'Clock':<10} {'Input data D':<12} {'Register value Q':<12} {'Description'}")
print("-" * 55)
for cycle in range(10):
data = test_data[cycle]
# Stage 1: Clock low (clk=0), register values should not change
reg.update(data, 0)
# Stage 2: Clock rising edge (clk transitions 0→1), latch data
result = reg.update(data, 1)
data_bin = f"{data:04b}"
result_bin = f"{result:04b}"
note = "← Latch on rising edge" if result == data else "Status unchanged"
print(f"{cycle:<6} {'0→1':<10} {data}({data_bin}){'':<4} {result}({result_bin}){'':<4} {note}")
print("-" * 55)
print()
# ============================================================
# Extra demo: data is latched only on the rising edge and held at other times
# ============================================================
print("=" * 55)
print(" Demo 2: When not on the rising edge, data changes are not latched")
print("=" * 55)
reg2 = Register4Bit()
# First write the initial value 5
reg2.update(5, 0)
reg2.update(5, 1)
print(fWrite 5 on the rising edge → register value: {reg2.read()})
# clk stays high, change data to 10—should not be latched
reg2.update(10, 1)
print(fWhen clk=1, change data to 10 → register value: {reg2.read()} (should still be 5))
# clk returns to low level, then write 10 on the rising edge
reg2.update(10, 0)
result = reg2.update(10, 1)
print(fOn clk 0→1, write 10 → register value: {reg2.read()} (now 10))
print()
print("=" * 55)
print(" Demo complete! example - Understanding Flip-Flops and Registers")
print("=" * 55)
Interactive demo: D flip-flop timing diagram
use belowChart.js's stepped:trueThe option plots the timing diagram of the clock signal (square wave) and the data signal.Red vertical lines mark each clock rising edge (the moment when CLK transitions from 0 to 1).These moments are precisely the critical instants when the D flip-flop latches data.
D flip-flop timing diagram — rising-edge latching demonstration
Q output updates only on the rising edge and remains unchanged at all other times.