COMPUTER ORGANIZATION & ARCHITECTURE / 4. SEQUENTIAL CIRCUITS

Sequential Circuits — Flip-Flops & Registers

Adding memory to circuits — state that persists across clock cycles


EXPLANATION

Sequential circuits have MEMORY — their output depends on both current inputs AND past history (stored state). This is what makes computers able to remember and compute over time.

The Clock:
All sequential circuits are driven by a clock signal — a square wave that alternates between 0 and 1 at a fixed frequency (e.g. 3 GHz = 3 billion cycles per second). State only changes on clock edges (rising edge: 0→1, or falling edge: 1→0). This synchronization is what makes complex digital systems work reliably.

SR Latch — the simplest memory element:
- Built from 2 cross-coupled NAND (or NOR) gates
- S (Set) input → forces output Q=1
- R (Reset) input → forces output Q=0
- Neither active → holds previous state (MEMORY!)
- Both active → FORBIDDEN state (both outputs try to be same value — race condition)
- Latch is level-triggered: responds to input levels, not edges

D Flip-Flop — the workhorse of digital design:
- D (Data) input + Clock
- On rising clock edge: Q captures whatever D is at that moment
- Holds that value until the next rising edge
- Eliminates the forbidden state problem of SR latch
- Built from: SR latch + gating logic

This is the key insight: the D flip-flop is a 1-bit memory cell. It stores ONE bit. It updates only on clock edges. The entire CPU state (all registers) is made of D flip-flops.

Register — N D flip-flops sharing a clock:
- 8 D flip-flops → 8-bit register (stores one byte)
- 64 D flip-flops → 64-bit register (x86-64 general purpose register: RAX, RBX...)
- Your CPU has 16 general purpose 64-bit registers = 16 × 64 = 1024 flip-flops just for registers

Register File:
- A bank of registers with read/write ports
- 2 read ports (can read 2 registers simultaneously for ALU inputs)
- 1 write port (write ALU result back)
- Controlled by register addresses from the instruction decoder

Counter — register that increments each clock cycle:
- Program Counter (PC/IP) is a counter: holds address of next instruction
- Increments by instruction size after each fetch
- Can be loaded with new value for jumps and branches

Shift Register — bits shift left or right each clock:
- Used for serial-to-parallel conversion
- Used in multiplication (shift left = multiply by 2)
- Used in CRC computation (checksums, error detection)

DIAGRAM

SR LATCH (NOR-based):
  S ──→ NOR ──→──┐ Q
        ↑        ├──→ NOR
  R ──→ NOR ──→──┘ Q'
  (cross-coupled: each output feeds back to other's input)

  State table:
  S R | Q(next)
  ────┼─────────
  0 0 | Q(prev)  ← HOLD (memory!)
  0 1 | 0        ← RESET
  1 0 | 1        ← SET
  1 1 | ???      ← FORBIDDEN

  D FLIP-FLOP (edge-triggered):
  D ──→[D FF]──→ Q
  CLK──→[  ]
  On ↑ of CLK: Q ← D

  REGISTER (4-bit, parallel load):
  D3 D2 D1 D0  ← data inputs
   │  │  │  │
  [FF][FF][FF][FF]  ← 4 D flip-flops
   │  │  │  │
  Q3 Q2 Q1 Q0  ← stored value

  All share same CLK → update together

  CPU REGISTER FILE:
  Read addr 1 ─→ ┌──────────────┐ ─→ Data out 1 (to ALU)
  Read addr 2 ─→ │   Register   │ ─→ Data out 2 (to ALU)
  Write addr  ─→ │     File     │
  Write data  ─→ │  (16 × 64b) │
  Write enable─→ └──────────────┘

CODE

PYTHON
1# Simulate sequential circuits
2
3# ── SR Latch (NOR-based) ───────────────────────────────
4class SRLatch:
5 def __init__(self):
6 self.Q = 0
7 self.Qb = 1 # Q' (complement)
8
9 def set_input(self, S, R):
10 if S == 1 and R == 1:
11 raise ValueError("FORBIDDEN state: S=R=1")
12 # NOR cross-coupled
13 self.Q = int(not (R or self.Qb))
14 self.Qb = int(not (S or self.Q))
15 # Iterate to stability (feedback)
16 self.Q = int(not (R or self.Qb))
17 self.Qb = int(not (S or self.Q))
18 return self.Q
19
20latch = SRLatch()
21print("SR Latch simulation:")
22print(f" Initial: Q={latch.Q}")
23print(f" SET (S=1,R=0): Q={latch.set_input(1,0)}")
24print(f" HOLD (S=0,R=0): Q={latch.set_input(0,0)}")
25print(f" HOLD (S=0,R=0): Q={latch.set_input(0,0)}")
26print(f" RESET(S=0,R=1): Q={latch.set_input(0,1)}")
27print(f" HOLD (S=0,R=0): Q={latch.set_input(0,0)}")
28
29# ── D Flip-Flop ────────────────────────────────────────
30class DFlipFlop:
31 def __init__(self):
32 self.Q = 0
33 self._prev_clk = 0
34
35 def tick(self, D, CLK):
36 rising_edge = (CLK == 1 and self._prev_clk == 0)
37 if rising_edge:
38 self.Q = D # capture D on rising edge
39 self._prev_clk = CLK
40 return self.Q
41
42print("
43D Flip-Flop (captures D on rising clock edge ):")
44ff = DFlipFlop()
45sequence = [(0,0),(1,0),(1,1),(0,1),(0,0),(0,1),(1,1),(1,0),(1,1)]
46print(f" {'D':>3} {'CLK':>4} {'Q':>3} {'edge':>8}")
47for D, CLK in sequence:
48 Q = ff.tick(D, CLK)
49 edge = "← CAPTURE!" if (CLK==1 and ff._prev_clk==1 and D==Q) else ""
50 print(f" {D:>3} {CLK:>4} {Q:>3} {edge}")
51
52# ── N-bit Register ─────────────────────────────────────
53class Register:
54 def __init__(self, width=8):
55 self.width = width
56 self.ffs = [DFlipFlop() for _ in range(width)]
57 self.value = 0
58
59 def tick(self, data, CLK, write_enable=1):
60 if write_enable:
61 for i, ff in enumerate(self.ffs):
62 bit = (data >> i) & 1
63 ff.tick(bit, CLK)
64 self.value = sum(ff.Q << i for i, ff in enumerate(self.ffs))
65 return self.value
66
67print("
688-bit Register:")
69reg = Register(8)
70for val, clk, we in [(0b10110101, 0, 1), (0b10110101, 1, 1),
71 (0b11001100, 0, 1), (0b11001100, 1, 1),
72 (0b00000000, 0, 0), (0b00000000, 1, 0)]:
73 result = reg.tick(val, clk, we)
74 print(f" data={val:#010b} CLK={clk} WE={we} stored={result:#010b}")
75
76# ── Program Counter simulation ─────────────────────────
77class ProgramCounter:
78 def __init__(self, width=64):
79 self.pc = 0
80 self.width = width
81
82 def increment(self, instruction_size=4): # 4 bytes for 32-bit ISA
83 self.pc = (self.pc + instruction_size) & ((1 << self.width) - 1)
84 return self.pc
85
86 def load(self, address): # for jumps
87 self.pc = address & ((1 << self.width) - 1)
88 return self.pc
89
90pc = ProgramCounter()
91print("
92Program Counter (simulating instruction fetch):")
93instructions = ["ADD R1, R2", "MOV R3, #5", "CMP R1, R3", "JEQ 0x1000", "SUB R4, R1"]
94for instr in instructions:
95 addr = pc.pc
96 if "JEQ" in instr:
97 target = int(instr.split()[1], 16)
98 print(f" PC={addr:#06x}: {instr} JUMP to {target:#06x}")
99 pc.load(target)
100 else:
101 print(f" PC={addr:#06x}: {instr}")
102 pc.increment()
← PREV3. Combinational CircuitsNEXT →5. ALU — Arithmetic Logic Unit