COMPUTER ORGANIZATION & ARCHITECTURE / 6. CPU DATAPATH & CONTROL

CPU Datapath & Control Unit

How instructions flow through the CPU — fetch, decode, execute


EXPLANATION

The CPU has two major subsystems that work together: the Datapath (the hardware that moves and processes data) and the Control Unit (the logic that tells the datapath what to do for each instruction).

The Datapath contains:
- Program Counter (PC) — holds address of next instruction
- Instruction Memory — stores the program (read-only during execution)
- Register File — 16–32 general purpose registers
- ALU — does all computation
- Data Memory (RAM) — stores data the program reads/writes
- Sign extender — extends immediate values to full width
- MUXes — select between different data sources

The Control Unit:
- Takes the instruction opcode as input
- Produces control signals that configure the datapath MUXes and units
- Decides: does the ALU add or subtract? Does the result go to a register or memory? Is the next PC = PC+4 or a branch target?
- Implemented as a truth table (combinational) or state machine (for complex ISAs)

The Fetch-Decode-Execute cycle in detail:

FETCH:
① Read instruction from Instruction Memory at address PC
② Instruction register holds the fetched instruction
③ PC = PC + 4 (increment for next instruction)

DECODE:
④ Split instruction into fields: opcode, source registers, destination register, immediate
⑤ Control unit reads opcode → sets all control signals
⑥ Register file reads source registers (happens in parallel with control decode)

EXECUTE:
⑦ ALU performs operation on register values (or register + immediate)
⑧ If load/store: compute memory address (register + offset)
⑨ If branch: compute branch target address, evaluate condition flags

MEMORY:
⑩ If load: read data memory at computed address
⑪ If store: write register value to data memory

WRITE-BACK:
⑫ Write result back to destination register in register file

Single-cycle vs Multi-cycle:
- Single-cycle: one instruction per clock cycle. Clock must be slow enough for slowest instruction (memory access limits this)
- Multi-cycle: different instructions take different number of cycles. Clock can be faster (tuned to one stage). But requires state between cycles → more hardware

RISC vs CISC:
- RISC (ARM, RISC-V): simple fixed-length instructions, load-store architecture (only LOAD/STORE access memory, all computation on registers), many registers. Easier to pipeline.
- CISC (x86): complex variable-length instructions, memory operands in arithmetic, many addressing modes. x86 CPUs internally translate CISC instructions to RISC-like micro-ops before execution.

DIAGRAM

SINGLE-CYCLE DATAPATH (simplified RISC):

  ┌─────┐  addr  ┌──────────┐ instr
  │ PC  │───────→│  Instr   │──────────────────────────────┐
  └──┬──┘        │  Memory  │                              │
     │           └──────────┘                              ↓
     │ PC+4                                      ┌─────────────────┐
     └──────────────────────────────────────→MUX │  Control Unit   │
                                               ↑ │  (opcode → ctrl)│
                                          Branch  └────────┬────────┘
                                          target           │ control signals
                                                          ↓
  instr[rs1] ──→ ┌───────────┐         ┌─────┐    ┌──────────────┐
  instr[rs2] ──→ │ Register  │→ A ────→│     │    │              │
  instr[rd]  ──→ │   File    │→ B ─┬──→│ ALU │───→│  Data Memory │
  write data ──→ │           │     │  │     │    │  (RAM)       │
                 └───────────┘     │  └─────┘    └──────────────┘
                                   │     ↑ ALU_op      │ read data
                             imm ──┴─MUX              ↓
                                                  ┌───────┐
                                                  │  MUX  │→ write back
                                                  └───────┘   to reg file

  CONTROL SIGNALS (example for ADD R1, R2, R3):
  RegDst=1  (dest = rd field)
  ALUSrc=0  (ALU B input = register, not immediate)
  MemtoReg=0 (write ALU result, not memory, to register)
  RegWrite=1 (write to register file)
  MemRead=0  (don't read memory)
  MemWrite=0 (don't write memory)
  Branch=0   (not a branch)
  ALUop=ADD

CODE

PYTHON
1# Simulate a simple RISC CPU (fetch-decode-execute)
2
3class CPU:
4 def __init__(self, mem_size=256):
5 # State
6 self.pc = 0
7 self.regs = [0] * 8 # 8 general purpose registers
8 self.mem = [0] * mem_size # data memory (bytes)
9 self.instr_mem = [] # instruction memory
10 self.flags = {"Z": 0, "N": 0, "C": 0}
11 self.cycles = 0
12
13 def load_program(self, instructions):
14 self.instr_mem = instructions
15 self.pc = 0
16
17 # ── ALU ────────────────────────────────────────────
18 def alu(self, op, A, B):
19 if op == "ADD": result = A + B
20 elif op == "SUB": result = A - B
21 elif op == "AND": result = A & B
22 elif op == "OR": result = A | B
23 elif op == "XOR": result = A ^ B
24 elif op == "SHL": result = A << (B & 31)
25 elif op == "SHR": result = A >> (B & 31)
26 else: raise ValueError(f"Unknown ALU op: {op}")
27 result &= 0xFFFF
28 self.flags["Z"] = int(result == 0)
29 self.flags["N"] = int(bool(result & 0x8000))
30 return result
31
32 # ── Fetch-Decode-Execute ───────────────────────────
33 def step(self):
34 if self.pc >= len(self.instr_mem):
35 return False
36
37 # FETCH
38 instr = self.instr_mem[self.pc]
39 self.cycles += 1
40
41 # DECODE + EXECUTE
42 op = instr[0]
43
44 if op == "ADD":
45 _, rd, rs1, rs2 = instr
46 self.regs[rd] = self.alu("ADD", self.regs[rs1], self.regs[rs2])
47 elif op == "ADDI":
48 _, rd, rs1, imm = instr
49 self.regs[rd] = self.alu("ADD", self.regs[rs1], imm)
50 elif op == "SUB":
51 _, rd, rs1, rs2 = instr
52 self.regs[rd] = self.alu("SUB", self.regs[rs1], self.regs[rs2])
53 elif op == "LW": # Load Word
54 _, rd, rs1, offset = instr
55 addr = (self.regs[rs1] + offset) & 0xFF
56 self.regs[rd] = self.mem[addr]
57 elif op == "SW": # Store Word
58 _, rs2, rs1, offset = instr
59 addr = (self.regs[rs1] + offset) & 0xFF
60 self.mem[addr] = self.regs[rs2] & 0xFF
61 elif op == "BEQ": # Branch if equal
62 _, rs1, rs2, offset = instr
63 if self.regs[rs1] == self.regs[rs2]:
64 self.pc += offset
65 return True
66 elif op == "JUMP":
67 _, target = instr
68 self.pc = target
69 return True
70 elif op == "HALT":
71 return False
72
73 self.pc += 1
74 return True
75
76 def run(self, verbose=True):
77 if verbose:
78 print(f"
79{'PC':>4} {'Instruction':<25} {'Regs R0-R4'}")
80 print("─" * 60)
81 while self.step():
82 if verbose:
83 instr = self.instr_mem[self.pc - 1]
84 regs = " ".join(f"R{i}={self.regs[i]}" for i in range(5))
85 print(f"{self.pc-1:>4} {str(instr):<25} {regs}")
86 if verbose:
87 print(f"
88Halted. Cycles: {self.cycles}")
89 print(f"Final registers: {self.regs[:5]}")
90 print(f"Flags: {self.flags}")
91
92# ── Program: compute 5 + 3, store result ──────────────
93cpu = CPU()
94cpu.load_program([
95 ("ADDI", 0, 0, 5), # R0 = 0 + 5 = 5
96 ("ADDI", 1, 0, 3), # R1 = 0 + 3 = 3
97 ("ADD", 2, 0, 1), # R2 = R0 + R1 = 8
98 ("SW", 2, 0, 100), # mem[100] = R2
99 ("LW", 3, 0, 100), # R3 = mem[100]
100 ("HALT",),
101])
102cpu.run()
← PREV5. ALU — Arithmetic Logic UnitNEXT →7. Pipelining