CPU Organization: ALU, Registers, Control Unit and Buses
The datapath is the ALU, registers and the connections between them; the control path tells the datapath what to do each cycle.
Concept
The Main Components ALU (arithmetic logic unit) Adds, subtracts, AND/OR/XOR, shifts, compares; sets status flags Register file
A small set of very fast storage locations (x86-64: 16 general-purpose 64-bit registers; ARM64 and RISC-V: 31–32) Program counter (PC / RIP) Address of the next instruction Instruction register (IR)
The instruction currently being executed Flags / status register
Results of the last operation (x86: RFLAGS) Control unit
Decodes the instruction and generates the control signals (hardwired or microprogrammed)
Address bus (where), data bus (what), control bus (read/write, timing)
Memory address and data registers between CPU and memory (textbook model)
The datapath is the ALU, registers and the connections between them; the control path tells the datapath what to do each cycle.
N/S (negative/sign) top bit of result is 1 carry out of the top bit (unsigned overflow); for subtraction it indicates a borrow unsigned comparisons, multi-word arithmetic V/O (overflow) signed result doesn't fit
A comparison cmp a, b is just a subtraction that sets flags and throws the result away; conditional branches then test the flags (je tests Z, jl tests N ≠ V, jb tests C on x86).
Performance: the CPU Time Equation CPU time = instruction count × cycles per instruction (CPI) × clock period.
A program gets faster by executing fewer instructions (better algorithm or compiler), needing fewer cycles per instruction (better microarchitecture, fewer cache misses) or a faster clock. Amdahl's law limits the benefit of speeding up one part: speedup = 1 / ((1 − f) + f / s) when a fraction *f* of the time is sped up by *s*.
flowchart LR
CU["Control unit: decodes instructions, drives control signals"] --> ALU["ALU: arithmetic and logic"]
CU --> RF["Register file"]
RF --> ALU
ALU --> RF
ALU --> FL["Flags: Z N C V"]
PC["PC and IR"] --> CU
RF --> BUS["Buses: address, data, control"]
BUS --> MEM["Memory and I/O"]
MEM --> BUSWhy It Matters
Flags explain how if statements work in hardware; the register count explains why compilers "spill" variables to the stack; the CPU time equation and Amdahl's law are the basis of every performance discussion.
Syntax
x86-64 examples: add rax, rbx (ALU + flags), cmp rdi, 10 then jl label (branch on flags), mov rax, [rbx] (memory via buses).
Example
4.1 An 8-bit ALU with Z, N, C and V flags
#include <stdio.h>
#include <stdint.h>
/* An 8-bit ALU that sets the four classic status flags, like a real CPU's flags register */
typedef enum { ADD, SUB, AND, OR, XOR, SHL } Op;
typedef struct { uint8_t result; int Z, N, C, V; } AluOut;
static AluOut alu(Op op, uint8_t a, uint8_t b)
{
AluOut o = {0};
unsigned wide = 0;
switch (op) {
case ADD: wide = a + b; break;
case SUB: wide = a + (uint8_t)~b + 1; break; /* subtraction = addition of the two's complement */
case AND: wide = a & b; break;
case OR: wide = a | b; break;
case XOR: wide = a ^ b; break;
case SHL: wide = a << 1; break;
}
o.result = (uint8_t)wide;
o.Z = o.result == 0;
o.N = (o.result >> 7) & 1;
o.C = (wide >> 8) & 1; /* carry out of bit 7 (for SUB: 1 = no borrow) */
if (op == ADD) o.V = (~(a ^ b) & (a ^ o.result)) >> 7 & 1; /* same-sign inputs, different-sign result */
if (op == SUB) o.V = ((a ^ b) & (a ^ o.result)) >> 7 & 1;
return o;
}
static void show(const char *what, Op op, uint8_t a, uint8_t b)
{
AluOut o = alu(op, a, b);
printf("%-18s = %3u (signed %4d) Z=%d N=%d C=%
…Expected Result
100 + 28 = 128 fits in 8 unsigned bits (C=0) but not in signed 8 bits: the result reads as −128 and V=1.
200 + 100 = 300 overflows unsigned 8 bits: C=1, result 44 (300 − 256), no signed overflow.
5 − 5 sets Z=1 — this is how if (a == b) compiles.
3 − 5 gives −2 (N=1); C=0 signals a borrow, i.e. 3 < 5 as unsigned numbers.
100 + 27 = 127 (signed 127) Z=0 N=0 C=0 V=0
100 + 28 = 128 (signed -128) Z=0 N=1 C=0 V=1
200 + 100 = 44 (signed 44) Z=0 N=0 C=1 V=0
5 - 5 = 0 (signed 0) Z=1 N=0 C=1 V=0
3 - 5 = 254 (signed -2) Z=0 N=1 C=0 V=0
0xF0 AND 0x3C = 48 (signed 48) Z=0 N=0 C=0 V=0
0x81 << 1 = 2 (signed 2) Z=0 N=0 C=1 V=0Common Mistakes
Confusing carry (unsigned overflow) with overflow (signed overflow).
Assuming more registers or a higher clock alone make a CPU faster (CPI and memory matter as much).
Forgetting Amdahl's law: speeding up 20% of a program by 10× gives only 1.22× overall.
Best Practices
Use unsigned comparisons for sizes and indices, signed for quantities that can be negative — the compiler then emits the right conditional branch.
Estimate performance with the CPU time equation before optimizing.
Optimize the common case (Amdahl).
Interview Relevance
High: "what are the components of a CPU?", "what do the carry and overflow flags mean?", "how does a comparison work in hardware?", "explain Amdahl's law", "what is CPI?".
Practice Questions
Compute the flags for 8-bit 0x80 - 1 and 0x7F + 1.
A program runs 10⁹ instructions at CPI 1.5 on a 3 GHz CPU. How long does it take?
60% of a program is parallelizable. What is the maximum speedup on 4 cores and on infinitely many cores?
Add a CMP operation to the ALU that sets flags without storing the result.
Quick Quiz
Q1. (Multiple choice) Which flag signals signed overflow? A) Z B) N C) C D) V
Answer: D (verified: 100 + 28 set V=1).
Q2. (Predict the output) 8-bit 200 + 100: result and carry?
Answer: 44 with C=1 (verified).
Q3. (True/False) cmp a, b stores a - b in a register.
Answer: False. It performs the subtraction only to set the flags.
Q4. (Find the bug) "We doubled the clock frequency, so every program runs twice as fast."
Answer: Memory latency doesn't scale with the clock; memory-bound programs see little improvement (CPI grows).
Coding Challenge
Write a function that computes the Amdahl speedup for a parallel fraction *f* and *n* cores, and print a table for f = 0.5, 0.9, 0.99 and n = 2, 8, 64.
#include <stdio.h>
static double amdahl(double f, int n) { return 1.0 / ((1.0 - f) + f / n); }
int main(void)
{
double fs[] = {0.5, 0.9, 0.99};
int ns[] = {2, 8, 64};
printf(" f | n=2 n=8 n=64\n");
for (int i = 0; i < 3; i++) {
printf(" %.2f |", fs[i]);
for (int j = 0; j < 3; j++) printf(" %6.2f", amdahl(fs[i], ns[j]));
printf("\n");
}
return 0;
}