Quiz yourself.
Ten random questions from the full pool. Try to answer in your head, then reveal. Mark each one honestly — your score is yours alone.
Drill the deck.
All 146 questions. Tap to flip. Mark known to skip on next pass.
From transistors
to out-of-order
machines.
The canonical superscalar textbook, distilled into 11 chapters of interview-ready concepts and 146 Q&A. Built for ASIC, CPU, and computer architecture roles.
What this is
A focused, interview-grade companion to Modern Processor Design: Fundamentals of Superscalar Processors. Each chapter follows the same shape: core idea → why it matters → how it actually works → common pitfalls → interview Q&A. No fluff, no padding — just what hiring managers at NVIDIA, Apple, AMD, Intel, Qualcomm, Cirrus Logic, and Tenstorrent actually probe for.
Three ways to use it
- Read mode — chapter by chapter, like a book. Toggle Single / All Chapters.
- Quiz mode — 10 random questions, score yourself, repeat.
- Flash mode — flip through the full deck, mark known to skip.
Switch modes from the sidebar at any time. Search across all 146 questions with the search bar. Your progress persists locally — no account, no login, no telemetry.
Chapter map
Processor Design
ISA, dynamic-static interface, performance equation, scalar to superscalar.
Pipelined Processors
Pipelining, hazards, balancing stages, MIPS R2000 case study.
Memory & I/O
Caches, virtual memory, TLBs, memory hierarchy, DMA.
Superscalar Organization
Parallel/diversified/dynamic pipelines, fetch through retire.
Superscalar Techniques
Branch prediction, register renaming, Tomasulo, ROB.
PowerPC 620
End-to-end superscalar case study with real measurements.
Intel P6
The microarchitecture behind Pentium Pro/II/III. uops, RAT, ROB.
Survey of Designs
Speed demons vs brainiacs. Alpha, MIPS, x86-64, SPARC.
Branch Prediction
Two-level, gshare, perceptron, hybrid, target prediction.
Value Prediction
Value locality, instruction reuse, beyond data flow.
Multithreading & CMP
SMT, MESI, memory consistency models.
Cheat Sheet + Rapid-Fire
One-page reference + 35 night-before questions.
Banu Rohit Vutukuri
MS EE grad targeting ASIC/RTL/PD roles. Built this guide while interview-prepping.
How to use this guide.
A few patterns that make this stick faster than re-reading chapters at 2 AM.
The 3-pass method
Pass 1 — Skim (30 min/chapter). Read just the lede, section headers, and the Q&A questions (not the answers). Goal: build a mental map of the territory. Spot the gaps in what you can already explain confidently.
Pass 2 — Deep read (1–2 hrs/chapter). Read the body. Work through formulas with pencil and paper. Redraw the diagrams from memory. Try every Q&A before opening it — even badly. Wrong answers teach more than read answers.
Pass 3 — Active recall (20 min/chapter). Close the page. Explain the chapter aloud in two minutes. The places you stumble are exactly where an interviewer will probe.
Recognition feels like learning but isn't. Production — explaining out loud, drawing from memory, working a problem from scratch — is what builds the recall an interviewer is testing. Pass 3 is the one most people skip. It's the one that matters.
Interview prep priority
One week to prep? Work in this order:
- Ch 2 (Pipelining) — universal. Asked in 90% of CPU/ASIC interviews.
- Ch 5 (Superscalar Techniques) — the vocabulary of out-of-order: Tomasulo, renaming, ROB.
- Ch 3 (Memory) — caches and TLBs. Cache organization questions are nearly guaranteed.
- Ch 9 (Branch Prediction) — gshare, TAGE. Frequent for performance-track roles.
- Ch 4 (Superscalar Organization) — front-end through retire. Glue for Ch 5.
- Ch 11 (Multithreading) — MESI, memory consistency. Common at AMD, Apple, Arm.
- Ch 1, 7, 10 — round things out. Ch 7 (P6) for x86-specific roles.
Levels of depth
Bachelor's targeting first internship: Ch 1–3 plus the basics of Ch 5. The rest is bonus.
Master's targeting full-time ASIC/PD/RTL/architecture: every chapter. Push hard on Ch 5, 7, 9, 11.
Senior or PhD interview prep: read the source for Ch 8–11. This guide gives scaffolding; the textbook gives the depth you'll need to defend ideas.
What this guide is not
- Not a replacement for hands-on RTL. Reading does not replace writing a 5-stage pipeline in Verilog and debugging it on a board.
- Not a math refresher. Cache associativity arithmetic, AMAT, Amdahl's law — practice these on paper.
- Not exhaustive. The Survey chapter (Ch 8) covers a sliver of what the book contains. For deep ISA-specific knowledge, go to the source.
Processor Design.
Before you can build a fast processor, you need a vocabulary: ISA, microarchitecture, the performance equation, and the difference between scalar and superscalar. Get this chapter wrong and the rest of the book reads like noise.
The three levels: ISA, implementation, realization
The single most-confused distinction in this entire field. Three layers, top to bottom:
- Architecture (ISA) — the contract. Instructions, registers, addressing modes, memory model. x86, ARMv8, RISC-V are ISAs. Software-visible. Changes glacially.
- Implementation (microarchitecture) — how you build hardware that obeys the contract. Pipelining, caches, OoO, branch prediction. The Apple M3 and Intel Lunar Lake implement different microarchitectures of overlapping ISAs.
- Realization — the physical chip. Process node, layout, packaging, voltage. Where physical-design and circuit engineers live.
ISA is the recipe. Microarchitecture is the kitchen workflow. Realization is the actual stove. Different kitchens can cook the same recipe — that's why a 2025 x86 chip still runs 1980s code.
The dynamic-static interface
The DSI separates what the compiler figures out (static) from what the hardware figures out (dynamic). Every architectural decision can be classified as moving work above or below this line.
- VLIW pushes nearly everything above the DSI. The compiler schedules everything, finds parallelism, avoids hazards. Hardware is dumb and fast.
- Out-of-order superscalar pushes everything below. Hardware finds parallelism dynamically. Compiler can be naive.
- RISC sits in the middle. Simple instructions both compiler and hardware can handle.
The trade-off: hardware that does scheduling sees runtime information (cache misses, branch outcomes) the compiler never will. But it pays in transistors, power, and verification complexity. This trade-off is the central tension of the entire field.
The performance equation
Memorize this. Every optimization in the book maps to one term.
Three knobs, that's it:
- Reduce instruction count — better ISA, better compiler, more powerful instructions.
- Reduce CPI (cycles per instruction) — pipelining, superscalar, better prediction, larger caches.
- Reduce clock period (= raise frequency) — deeper pipelines, smaller process node, better circuit design.
These knobs interact. Deepening the pipeline raises frequency but increases CPI (longer mispredict penalty, more bubbles). The whole second half of the book is about navigating this tension — Pentium 4 is the cautionary tale.
Scalar to superscalar
A scalar processor issues at most one instruction per cycle. Best CPI = 1.
A superscalar processor issues several per cycle. Best CPI < 1, equivalently IPC > 1.
The book introduces six flavors of parallelism processors exploit:
- Pipelining — overlap stages of different instructions. (Ch 2)
- Superpipelining — go deeper to clock faster.
- Superscalar — multiple parallel pipelines. (Ch 4–5)
- VLIW — compiler-scheduled wide instructions.
- Vector / SIMD — single instruction operates on a vector of data.
- Multithreading / multiprocessing — multiple threads or cores. (Ch 11)
The limits of ILP
Studies cited in the chapter (Wall, Lam, Smith, others) measured ILP available in real programs with infinite hardware and perfect prediction. Result: 4–7 instructions per cycle on average for integer code, higher for FP/scientific. With realistic hardware, sustained IPC is 1.5–3 in modern wide superscalars.
This is why the industry hit a wall around 2004 and pivoted to multicore. Squeezing more ILP from a single thread runs into hard data-dependence and control-dependence limits — limits no transistor budget can buy past.
Interview Q&A
Chapter 1 · 10 questionsWhat's the difference between architecture, microarchitecture, and realization?
Architecture (ISA) is the software-visible contract — instructions, registers, memory model. Microarchitecture is the hardware implementation — pipelines, caches, branch predictors, ROB. Realization is the physical chip — process node, layout, voltage.
Apple's M-series and Qualcomm's Snapdragon both implement ARMv8 ISA but use entirely different microarchitectures and realizations. This separation is what lets binaries work across decades of chips.
State the processor performance equation and explain each term.
CPU Time = Instruction Count × CPI × Clock Period
Instruction Count depends on ISA and compiler. CPI depends on microarchitecture — pipelining, hazards, cache misses, mispredictions. Clock Period depends on circuit design and process technology.
Likely follow-up: "What happens if you double pipeline depth?" Frequency rises (period drops), but every mispredict costs more cycles, so CPI goes up. The product may not improve. That's the Pentium 4 lesson.
What is the dynamic-static interface (DSI) and why does it matter?
The DSI separates compile-time work from run-time work. It's the design choice of "who finds the parallelism — compiler or hardware?"
VLIW pushes scheduling onto the compiler — works for predictable kernels (DSP), struggles with cache-miss-heavy code. OoO superscalar puts scheduling in hardware — handles dynamic events gracefully but burns transistors. Where you draw the DSI shapes the entire design.
Why did the industry move from frequency to multicore around 2004?
Three walls hit simultaneously:
- Power wall: dynamic power scales with V²f. Past ~3–4 GHz, heat density becomes unmanageable in air-cooled systems.
- ILP wall: sustained ILP in real programs caps around 4–7 even with perfect speculation. More issue width yields diminishing returns.
- Memory wall: DRAM speeds didn't scale with CPU speeds. More frequency just meant more cycles waiting on memory.
Multicore sidestepped all three by exploiting thread-level parallelism instead.
What's the difference between IPC and CPI? When would you use each?
They're reciprocals: IPC = 1/CPI. CPI is more natural for scalar processors where CPI ≥ 1. IPC is more natural for superscalar where IPC > 1 is the goal. Industry talks about IPC because "our IPC is 3.5" is more intuitive than "our CPI is 0.286."
State Amdahl's Law. Why does it matter?
If a fraction f of execution is sped up by factor s, total speedup is 1 / ((1-f) + f/s). The crucial insight: as s → ∞, speedup is bounded by 1/(1-f). The serial portion fundamentally caps parallel speedup.
Why it matters: it's why 64 cores don't make most desktop workloads 64× faster. Serial fractions (file I/O, single-threaded loops, lock contention) dominate real-world gains.
RISC vs CISC — does the distinction still matter in 2025?
Less than it used to. Modern x86 (CISC) chips internally decode complex instructions into RISC-like uops. Modern ARM (RISC) has accumulated complex instructions for crypto, NEON, etc.
What still matters: encoding density (CISC variable-length hurts fetch bandwidth and complicates decode) and memory operand model (CISC mem-to-mem ops vs RISC load-store). These cause real microarchitectural differences even today — see the M-series's effortless 8-wide decode versus x86's complex multi-decoder front-end.
What's the upper bound on ILP for typical programs?
With infinite resources, perfect alias analysis, and perfect prediction: ILP of 4–7 for integer programs, 10–20+ for numeric/FP. With realistic hardware, sustained IPC is 1.5–3 in modern wide superscalars. This empirical limit is the reason multicore exists.
How is benchmark performance measured fairly? SPECint vs SPECfp?
SPEC defines workload suites. SPECint covers integer-heavy applications (compilers, compression, AI search). SPECfp covers floating-point and scientific code (fluid dynamics, molecular modeling). Both report a geometric mean of speedups vs a reference machine.
Important nuance: SPECint stresses branch prediction and cache; SPECfp stresses memory bandwidth and FPU throughput. A processor optimized for one may not look great at the other.
If you could only optimize one term in the performance equation, which would you pick?
Trap question — the right answer is "it depends on the baseline." If your CPI is already 0.5 (very wide superscalar), squeezing more ILP costs huge transistors for tiny gain. If your clock is already process-limited, going deeper hurts CPI.
A confident interview answer: "I'd profile first. If branch mispredictions dominate, attack CPI via better prediction. If the workload is memory-bound, invest in cache hierarchy. If it's compute-bound and CPI is near 1, only then consider frequency." Show you think in trade-offs, not absolutes.
Pipelined Processors.
If you remember nothing else from this book, remember this chapter. Pipelining is the single most-asked-about topic in CPU/ASIC interviews. Hazards, forwarding, stalls, balancing — be fluent.
The core idea
An unpipelined processor handles one instruction at a time, end to end. A pipelined processor breaks the work into stages and overlaps them — like an assembly line. Throughput improves by roughly the depth of the pipeline. Latency per instruction stays the same; throughput goes way up.
Pipelining idealism
Textbook pipelining assumes:
- Identical computations: every instruction does the same work in each stage.
- Independent computations: no instruction depends on another.
- Uniform sub-computations: stages take exactly the same time.
Real instructions violate all three. The chapter is about how to make pipelining work despite this.
Balancing pipeline stages
The clock period equals the slowest stage plus pipeline-register overhead. Stages of 2, 3, 1, 4, 2 ns force a clock ≥4 ns — the 1 ns stage wastes 75% of its potential. Balanced stages give the highest frequency.
Speedup from pipelining (ideal) = T_unpipelined / (T_max_stage + T_register)
For N balanced stages: speedup approaches N as long as the pipeline stays full.
The three types of hazards
Hazards prevent the next instruction from issuing in the next cycle.
1. Structural hazards
Two instructions need the same hardware in the same cycle. Example: single memory port → IF and MEM stages collide. Fix: Harvard architecture (separate I-cache and D-cache), or duplicate the resource.
2. Data hazards
An instruction needs a value not yet written by an earlier one. Three subtypes — universal vocabulary:
- RAW (Read After Write): "true" dependence.
add R1,R2,R3 ; sub R4,R1,R5— sub needs R1. - WAR (Write After Read): "anti" dependence. Only matters when instructions execute out of order.
- WAW (Write After Write): "output" dependence. Also an OoO issue.
In a strict in-order pipeline, only RAW exists. WAR and WAW emerge with OoO — register renaming (Ch 5) eliminates them.
3. Control hazards
Branches. The pipeline doesn't know whether to fetch fall-through or target until the branch resolves — typically several stages downstream.
Forwarding (bypassing): the data-hazard fix
Instead of stalling, route the ALU output of an in-flight instruction directly to the input of a younger one — no waiting for register-file write-back.
Forwarding doesn't fix everything. Load-use hazard: a load's value isn't available until end of MEM, but the dependent instruction in EX needs it the cycle before. Even with forwarding, you need exactly one bubble.
Resolving control hazards
- Stall until resolved. Simple, slow.
- Predict not-taken. Squash if wrong. Cheap, ~50% accurate.
- Delayed branch. The instruction after the branch always executes. Compiler fills the slot. RISC-V keeps it as legacy; MIPS used it heavily.
- Branch prediction. Speculate using history. Modern designs hit >95%. Covered in Ch 5 and Ch 9.
Unifying instruction types
Different instructions naturally need different stages. A load uses MEM; an add doesn't. A store writes memory but not a register.
The MIPS solution: every instruction passes through every stage. The add walks through MEM as a no-op. The store has a dummy WB. Wastes a little energy but keeps pipeline control simple — no structural conflicts on the writeback port.
"Why does an ADD instruction go through the MEM stage?" Because forcing all instructions through all stages keeps stage occupancy uniform, eliminates structural hazards on the writeback port, and simplifies control. The cost (a few unused cycles per instruction) is small compared to the simplicity gained.
Going deeper: superpipelining
Going deeper raises the clock but increases penalties. The master formula:
Branch mispredict penalty grows from ~3 cycles (5-stage) to ~15 cycles (20-stage). With 15% branches at 5% mispredict rate: 0.15 × 0.05 × (15-3) = 0.09 added to CPI. Frequency goes up 2–3×; CPI gets noticeably worse. Pentium 4 (NetBurst, 20+ stages) was the textbook example of going too deep — mispredictions and stalls ate the frequency advantage.
Interview Q&A
Chapter 2 · 12 questionsWhy does pipelining improve throughput but not latency?
Per-instruction latency is determined by total time through every stage. Pipelining doesn't reduce that — it actually adds a tiny bit (pipeline-register delay). Throughput rises because once the pipeline is full, you complete one instruction per cycle instead of one every N cycles. Classic latency-vs-bandwidth trade.
Walk through the classic 5-stage MIPS pipeline.
IF — fetch from I-cache using PC. ID — decode opcode, read register file, sign-extend immediate. EX — ALU op, or address compute for loads/stores, or branch comparison. MEM — D-cache access for loads/stores; pass-through for ALU ops. WB — write result to register file.
Bonus: register file is read in ID and written in WB. A back-to-back dependence requires forwarding from EX/MEM/WB back to ID-input or EX-input.
Explain RAW, WAR, and WAW. Which exist in an in-order pipeline?
RAW — true data dependence. Consumer reads what producer wrote. WAR — younger instruction writes a register an older one still needs to read. WAW — two writes to the same register; program order must be preserved.
Strict in-order: only RAW exists. Instructions execute in program order, so the read happens before any later write. WAR and WAW emerge with OoO; register renaming makes them disappear.
What's the load-use hazard, and why can't forwarding alone eliminate it?
A load's value is available only at the end of MEM. The instruction immediately after the load is in EX during the load's MEM cycle and needs the data at the start of EX. Forwarding can deliver from MEM-output to EX-input only one cycle later — so you need exactly one bubble between a load and its dependent consumer.
Compilers schedule unrelated instructions into that delay slot when possible.
What's the cost of a branch misprediction in a 5-stage vs 20-stage pipeline?
The penalty equals the number of stages between fetch and branch resolution. In a 5-stage where branches resolve in EX (stage 3), penalty is 2 cycles of squashed instructions. In a 20-stage deep pipeline where branches may not resolve until stage 15+, penalty can be 15–20 cycles.
This is why deep pipelines need very accurate prediction. Pentium 4 had ~20-cycle mispredict penalty — its 95%+ predictor was essential, not optional.
Why do we balance pipeline stages? What happens if we don't?
The clock period must accommodate the slowest stage. If one stage takes 4 ns and others take 2 ns, the clock must be ≥4 ns and the fast stages sit idle for half the cycle.
Splitting the 4 ns stage into two 2 ns stages lets you clock at 2 ns. Pipeline depth goes up by one and frequency doubles for that critical path. Real designs spend significant effort identifying critical paths and re-balancing.
What is forwarding (bypassing)? Where does it physically live?
Forwarding routes the result of an in-flight instruction directly from a later stage's output back to an earlier stage's input, bypassing the register file. Physically: muxes in front of ALU operand inputs, with control logic detecting when a younger instruction's source register matches an in-flight instruction's destination.
Common forwarding paths in a 5-stage MIPS: EX/MEM → EX-input, MEM/WB → EX-input. Each path is a 32- or 64-bit bus plus comparator logic.
Describe a structural hazard and how Harvard architecture eliminates one.
Structural hazard: two stages contend for the same hardware. Classic case: unified memory with one port — IF and MEM collide every cycle a load or store is in flight.
Harvard splits memory (or first-level cache) into separate instruction and data caches with separate ports. IF and MEM access different physical memories simultaneously. Modern CPUs use Harvard at L1 and unified at L2+.
What's a delayed branch? Why was it used in MIPS, and why did it fall out of favor?
A delayed branch defers its effect by one instruction. The "delay slot" — the instruction immediately after the branch — always executes regardless of outcome. The compiler tries to fill it with useful work.
It worked in early MIPS because the pipeline was shallow and one slot was enough. As pipelines deepened, you'd need 5, 10, 20 delay slots — impractical. Modern ISAs (ARMv8, RISC-V) abandoned delay slots for branch prediction.
How does pipelining affect power?
Each pipeline register adds clocked flip-flops. More stages = more flops switching every cycle = more dynamic power (P ∝ C·V²·f). Plus, deeper pipelining usually pairs with higher frequency, multiplying power further.
Speculation overhead compounds it — squashed instructions still consumed energy in the front-end. Pentium 4's deep pipeline plus speculation was famously power-hungry. Core deliberately shortened the pipeline to recover power efficiency.
Compute CPI for an in-order pipeline with forwarding and a branch predictor.
CPI = CPI_ideal + stalls_per_inst
For an in-order MIPS with forwarding: CPI_ideal = 1. Add: load-use stalls (load_freq × dep_consumer_rate × 1), branch mispredicts (branch_freq × mispredict_rate × penalty), cache misses (miss_rate × miss_penalty), structural hazards.
Example: 30% loads, 50% have a dependent next inst, 20% branches at 90% accuracy with 2-cycle penalty: CPI = 1 + (0.3 × 0.5 × 1) + (0.2 × 0.1 × 2) = 1 + 0.15 + 0.04 = 1.19.
One feature to add to a basic 5-stage pipeline — what would it be?
An honest answer: depends on the workload bottleneck. The most universally impactful single feature is a branch predictor (a basic 2-bit BHT). Most workloads have ~20% branches; even a simple predictor brings accuracy from ~50% (always-not-taken) to ~85%+, which translates to substantial CPI improvement at 2-cycle mispredict penalty.
Bonus points: "After branch prediction, I'd add a non-blocking cache so loads don't stall the whole pipe on misses." Show layered thinking.
Memory & I/O.
A modern CPU spends most of its time waiting on memory. Understanding caches, virtual memory, TLBs, and DMA isn't optional for any architecture, ASIC, or systems interview.
Latency vs bandwidth
Two distinct metrics, often confused. Latency is the time for a single request to complete, in nanoseconds. Bandwidth is the rate of throughput, in GB/s. They're not interchangeable — a wide DRAM bus has high bandwidth but the same latency as a narrow one.
Patterson said it best: "Money buys bandwidth. Latency is hard." You can almost always parallelize for bandwidth (more channels, wider buses). Latency is bounded by physics — signal propagation, RAS-CAS sequencing in DRAM, cache lookup time.
The memory hierarchy
Each level is bigger and slower than the one above. Typical modern desktop in 2025:
| Level | Size | Latency | Bandwidth |
|---|---|---|---|
| Registers | ~1 KB | 0 cycles | — |
| L1 Cache | 32–192 KB | 3–5 cycles | ~1 TB/s |
| L2 Cache | 256 KB – 16 MB | 10–20 cycles | ~500 GB/s |
| L3 Cache | 4–256 MB | 30–80 cycles | ~200 GB/s |
| Main DRAM | 8–256 GB | 100–300 cycles | 50–200 GB/s |
| NVMe SSD | 0.5–8 TB | ~50,000 cycles | 5–14 GB/s |
Locality: why caches work
Temporal locality: if you accessed a location, you'll access it again soon. (Loop variables, stack frames.)
Spatial locality: if you accessed a location, you'll access nearby locations soon. (Arrays, instruction streams.)
Caches exploit temporal locality by keeping recent data; spatial locality by fetching whole cache lines (typically 64 bytes), not single bytes.
Cache organization
A cache stores blocks of data tagged with which memory address they came from. Three flavors:
Direct-mapped
Each memory block maps to exactly one cache line. Lookup is fast — one tag compare. But two blocks competing for the same line cause conflict misses even with the cache half-empty.
Fully associative
Any block can go anywhere. No conflict misses. But every lookup compares all tags in parallel — expensive in power and area. Used for small structures (TLBs, victim caches).
Set-associative (the practical choice)
Cache divided into sets of N "ways." A block can go into any of N lines in its assigned set. Modern L1 caches: 4–8 way. L2: 8–16 way. L3: 16–32 way. Sweet spot of flexibility vs lookup cost.
For a cache with 2^B bytes per block, 2^S sets, W ways:
[ tag | index (S bits) | block offset (B bits) ]
Total cache size = W × 2^S × 2^B bytes
The 3 C's of cache misses
- Compulsory (cold): first access to a block. Unavoidable in pure form; reduced by prefetching.
- Capacity: working set bigger than cache. Reduced by larger caches.
- Conflict: blocks evict each other due to limited associativity. Reduced by higher associativity or victim caches.
Some texts add Coherence misses as a 4th C in multiprocessor systems — a block was invalidated by another core's write.
Write policies
Write-through: writes hit cache AND memory immediately. Simple, slow, used in small L1s with write buffers to hide DRAM latency.
Write-back: writes only update cache; a "dirty bit" marks modified lines. On eviction, dirty lines are written to memory. Most modern caches use write-back.
Write-allocate (vs no-write-allocate): on a write miss, do you bring the line into cache? Write-allocate pairs with write-back. No-write-allocate pairs with write-through.
AMAT: average memory access time
This generalizes recursively: if L2 has its own miss rate, L1's miss penalty is L2's AMAT, and so on. The master formula for evaluating any cache change.
Virtual memory
Each process sees its own virtual address space. The OS plus hardware (MMU) translate virtual to physical addresses on every access. Three big benefits:
- Isolation: processes can't read each other's memory.
- Larger-than-RAM programs: unused pages live on disk, paged in on demand.
- Relocation: the OS can place a process anywhere in physical memory.
Page tables and the TLB
The page table stores virtual-to-physical mappings. Walking it on every access would be ruinous (multi-level page tables = multiple memory accesses). The TLB (Translation Lookaside Buffer) is a small, fast cache of recent translations.
Typical TLBs: 64–2048 entries, fully associative or high-associativity, separate I-TLB and D-TLB plus a unified L2 TLB. A TLB miss triggers a page-table walk — hardware-walked on x86, software-walked on classic MIPS.
A TLB miss can require 4–5 memory accesses to walk a 4-level page table on x86. If those misses themselves miss in cache, you're talking hundreds of cycles for a single load. This is why huge pages (2 MB, 1 GB) exist — bigger pages mean fewer translations, less TLB pressure.
I/O systems
Three ways the CPU communicates with I/O devices:
- Programmed I/O (polling): CPU loops reading device status. Wastes cycles.
- Interrupt-driven: device raises an interrupt when ready. CPU does other work meanwhile.
- DMA (Direct Memory Access): a separate controller transfers data between device and memory. CPU sets up the transfer; one interrupt fires at completion. Essential for high-bandwidth devices (NVMe, 10/25/100 GbE NICs, GPUs).
Interview Q&A
Chapter 3 · 12 questionsWalk through a load instruction's full path through the memory hierarchy.
1. Compute virtual address in EX. 2. Look up TLB with virtual page number. On TLB miss, walk page table. 3. Use the resulting physical address to index L1 D-cache (or use VIPT — see next). 4. On L1 hit, return data; on miss, send request to L2. 5. On L2 miss, send to L3 (if present), then memory controller, then DRAM. 6. Data returns up the hierarchy, filling each level on the way back. 7. Forwarding paths deliver the value to the dependent instruction's EX input.
What's VIPT and why is it used for L1?
VIPT = Virtually Indexed, Physically Tagged. The cache index uses bits from the virtual address (so TLB lookup and cache index lookup happen in parallel — saves time), but the tag comparison uses the physical address (so aliasing is correct).
This works only if the index bits are entirely within the page offset (which is invariant under translation). Constraint: cache_size / associativity ≤ page_size. For 4 KB pages, an 8-way 32 KB cache fits (each way = 4 KB = page size).
Compare write-back vs write-through. When would you use each?
Write-through: writes go to cache and next level on every store. Simpler coherence, but huge bandwidth on the lower level. Used in small L1 caches paired with write buffers, or where simplicity dominates.
Write-back: only updates cache; modified lines tagged dirty, written back on eviction. Massively reduces lower-level bandwidth. Cost: complex coherence and the dirty-bit machinery. Modern L1/L2/L3 are essentially all write-back.
95% L1 hit rate but the app is still slow. What's likely happening?
5% miss rate sounds great, but miss penalty matters more. If L1 hit time is 4 cycles and L2 hit time is 15 cycles: AMAT = 0.95×4 + 0.05×15 = 4.55. That's 14% slower than perfect L1 — significant.
If those 5% cascade to DRAM (~250 cycles): AMAT = 4 + 0.05×250 = 16.5. That's 4× slower. Check L2/L3 hit rates and TLB miss rate too. The 95% number is meaningless without the miss-penalty distribution.
Explain the TLB. What happens on a TLB miss?
TLB caches recent virtual-to-physical page translations. Typically 64–2048 entries, often split ITLB/DTLB plus an L2 TLB. Hit: 1 cycle.
Miss: the MMU walks the page table. On x86 this is hardware-walked (4–5 memory accesses for a 4-level table); on MIPS/RISC-V it can be software-walked (the OS handles it). The walked PTE is inserted into the TLB. If the page isn't in physical memory at all, that's a page fault — a much heavier OS-level event.
Why are huge pages (2 MB or 1 GB) useful?
A 4 KB TLB entry covers 4 KB. A 2 MB huge page covers 2 MB with the same single TLB entry — 512× more reach. For workloads with huge working sets (databases, JVMs, HPC), huge pages dramatically cut TLB miss rates.
Cost: internal fragmentation (tiny allocations consume 2 MB), more complex memory management, harder page sharing between processes. Linux's transparent huge pages enables them automatically when beneficial.
What's a victim cache?
A small fully-associative buffer (typically 4–16 lines) holding recently evicted lines from a low-associativity cache. On a miss, check the victim cache before the next level. If found, swap the line back.
Effect: gives a direct-mapped cache much of the conflict-miss benefit of a higher-associativity cache, with minimal area overhead. Norm Jouppi's original 1990 paper. Less common today since L1 caches are 8-way associative anyway.
Compare programmed I/O, interrupt-driven, and DMA.
Programmed I/O: CPU repeatedly reads a status register. Wastes cycles, simple, predictable for low-bandwidth.
Interrupt-driven: device raises IRQ when data is ready. CPU does other work in between. Better utilization, but interrupt overhead can dominate at very high event rates.
DMA: dedicated controller copies data between device and memory without CPU. CPU sets up descriptor; DMA does the transfer; one interrupt at completion. Essential for high-bandwidth devices.
What is cache coherence, and why is it needed in multicore?
Each core has its own L1. If core 0 caches address X, then core 1 writes X, core 0's cache must reflect that change before reading X again. Cache coherence enforces this — typically MESI (Modified, Exclusive, Shared, Invalid) or extensions like MOESI.
The hardware tracks each line's state and uses bus snooping or directory protocols to invalidate or update other caches' copies. Without coherence, parallel programs see stale data and break. (Detailed in Ch 11.)
If you doubled L1 size, what happens to hit rate, hit time, and overall performance?
Hit rate increases (capacity misses drop, conflict misses may drop). But hit time increases — bigger arrays have longer access paths, more decoders, larger tag arrays. May force a slower clock or an extra access cycle.
Empirically, doubling L1 from 32 KB to 64 KB might give +1–2% hit rate but +1 cycle access time. For L1-hit-bound code, this can be a net loss. Modern designers pick L1 size carefully balanced against critical path. This is why L1 hovered at 32 KB for two decades despite Moore's Law — though Apple has since pushed to 192 KB by clever timing.
Inclusive vs exclusive vs non-inclusive cache hierarchies?
Inclusive: L2 contains every line in L1. Simplifies coherence (only need to snoop L2). Wastes capacity (L1 contents duplicated). Used by Intel for many years.
Exclusive: a line is in L1 OR L2, never both. Maximum effective capacity. Coherence is harder. Used by AMD historically.
Non-inclusive (NINE): no enforced relationship. Lines may be in both, neither, or one. Compromise. Used by modern Intel after Skylake-X for L2/L3.
What's prefetching, and what are the trade-offs?
Prefetching speculatively fetches cache lines before they're requested. Hardware prefetchers detect patterns (sequential, strided, indirect). Software prefetching uses explicit instructions inserted by the compiler.
Wins: hides latency for predictable patterns. Costs: cache pollution (prefetched lines evict useful ones), bandwidth waste (mispredicted prefetches consume bandwidth), and power. Aggressive prefetchers help streaming benchmarks but can hurt random-access workloads. Tuning the predictor's confidence threshold is where most engineering happens.
Superscalar Organization.
Pipelining gets you to CPI=1. To go below 1 you need parallel pipelines that issue, execute, and complete multiple instructions per cycle. This chapter is the architectural skeleton.
Why scalar pipelines hit a wall
Three structural limits:
- Upper bound on throughput: CPI cannot go below 1. Even a perfect 5-stage retires at most one instruction per cycle.
- Inefficient unification: a single pipeline forces every instruction through every stage. A simple add waits behind a slow divide.
- Rigid pipeline: instructions must execute in strict program order. A stalled instruction blocks all younger ones, even if they're ready.
From scalar to superscalar — three steps
Step 1: parallel pipelines
Replicate the pipeline. Now you have two (or more) instructions flowing in lock-step through duplicated IF/ID/EX/MEM/WB stages. Best case: CPI = 0.5 with two pipes.
Cost: a fetch unit that delivers multiple instructions per cycle, a register file with multiple read/write ports, and forwarding networks that scale quadratically.
Step 2: diversified pipelines
Different instruction types have different needs. Split EX into specialized functional units: integer ALU, FP unit, branch unit, load/store unit. Now an FP add and an integer add execute simultaneously without contending.
Step 3: dynamic pipelines
Allow instructions to execute out of order. An instruction whose operands are ready can execute even if older instructions stall. Requires:
- Buffers between stages so older stalls don't block younger instructions.
- A scheduling mechanism (reservation stations or issue queue).
- A retirement mechanism that re-imposes program order — the reorder buffer — for correct exception handling and precise interrupts.
The generic superscalar pipeline
The book's canonical wide pipeline, found (with variations) in nearly every modern CPU:
Fetch
Pull N instructions per cycle from I-cache. Aligned-fetch hardware deals with branches that don't lie on cache-line boundaries. Branch prediction guides fetch direction.
Decode
Translate raw bytes into internal control signals. For x86, this is where complex instructions become uops. For RISC ISAs, decode is simpler (fixed-length, regular formats).
Rename
Map architectural registers (16 or 32) to physical registers (100–500+). Eliminates WAR and WAW hazards. Detailed in Ch 5.
Dispatch
Send renamed instructions to issue queues / reservation stations and allocate ROB entries. After dispatch, instructions wait for their operands.
Issue
Wake up instructions whose operands are ready and select which to send to functional units this cycle. The issue logic is one of the most timing-critical paths in a wide CPU.
Execute
Functional units do the work. Multi-cycle ops (multiply, divide, FP) may be pipelined or non-pipelined.
Complete
Result is written to PRF or broadcast to waiting reservation stations. The ROB entry is marked complete.
Retire (commit)
In strict program order, the ROB head commits: architectural state updates, the physical register becomes the architectural one, the entry is freed. This is what makes exceptions precise.
The two critical resources
Issue width (front-end): how many instructions can be fetched, decoded, renamed, and dispatched per cycle. Apple M3/M4 ~8-wide; Intel Lunar Lake ~6-wide; AMD Zen 5 ~6-wide.
Window size (back-end): how many in-flight instructions the OoO core tracks. ROB sizes range from ~200 (older designs) to 600+ (Apple M3, Intel Lunar Lake). Bigger window = more ILP exposed, but bigger CAMs and more area/power.
"Wider is always better." No. Issue width has diminishing returns because real programs only have ~4–7 ILP. Beyond a point, you're paying full transistor and power cost for issue slots that mostly stay empty. This is why nobody ships 16-wide cores — the curve flattens.
Interview Q&A
Chapter 4 · 10 questionsDifference between in-order and out-of-order superscalar?
In-order superscalar issues multiple instructions per cycle but maintains strict program order at every stage. If instruction N stalls, N+1 cannot pass it. Examples: ARM Cortex-A53, original Pentium.
Out-of-order superscalar can execute instructions in any order their dependencies allow. A stalled load doesn't block independent younger instructions. Hardware re-imposes program order at retirement to keep exceptions precise. Far higher IPC but vastly more complex. Examples: Intel Core, Apple M-series, AMD Zen.
Why do we need a reorder buffer?
Three reasons all rooted in correctness:
- Precise exceptions: when instruction N takes an exception, instructions N+1, N+2 already executed must be undone. The ROB tracks them in program order so we can squash everything younger than N.
- Speculation recovery: on a branch misprediction, all instructions younger than the branch must be squashed. The ROB defines "younger."
- In-order retirement: stores must commit to memory in program order; register writes must happen in program order. The ROB enforces this.
Difference between dispatch and issue?
Different texts use these differently — be ready to clarify. The book's convention (and a common one):
Dispatch: moving an instruction from the in-order front-end into the OoO back-end (allocating a ROB entry, putting it in a reservation station / issue queue). Done in program order.
Issue: sending a ready instruction from the issue queue to a functional unit. Done out of order, based on operand readiness.
Some texts (and Intel docs) use "issue" for what the book calls "dispatch." Always ask the interviewer their convention.
Why do superscalar processors need register renaming?
WAR and WAW hazards aren't real data dependences — they're name dependences caused by the small number of architectural registers. add R1,...; add R1,... creates a fake WAW because both write R1, even though the values are unrelated.
Renaming maps each write to a fresh physical register. The two adds now write different physical registers — no false dependence. This unlocks parallelism hidden by ISA register pressure. (Mechanism in Ch 5.)
Front-end vs back-end?
Front-end: in-order pipeline stages from fetch through dispatch. Concerned with delivering instructions: branch prediction, fetch, decode, rename. Throughput here caps the whole CPU.
Back-end: out-of-order portion — issue queue, functional units, ROB, retirement. Concerned with executing and committing instructions correctly. The back-end can have more capacity than the front-end (an 8-wide back-end can execute 8 uops/cycle even if the front-end only delivers 6, as long as the issue queue is non-empty).
What does "issue width" mean and what limits it?
The maximum number of instructions that can pass through a critical pipeline stage per cycle (decode, rename, issue-to-EX). Often quoted as the dispatch or rename width.
Limits: register file ports scale roughly as O(width²) in area (each ALU needs 2 read ports + 1 write; forwarding network grows similarly). Wakeup-select logic in the issue queue is timing-critical and grows with width and queue size. Issue port utilization drops with width because real programs lack the ILP. So 6–8 wide is the sweet spot in 2025.
Typical ROB sizes in modern CPUs?
2024–2025 designs: Intel Skylake ~224, Apple M1 ~630, Apple M3/M4 ~600+, AMD Zen 4 ~320, Zen 5 ~448. The ROB defines the "instruction window" — how far ahead the CPU can look for independent work to overlap with stalls.
A larger ROB hides longer-latency events (DRAM misses ~300 cycles). But the ROB is a CAM-like structure with significant area, power, and timing-critical paths for allocation and retirement. Doubling ROB size doesn't double IPC — diminishing returns again.
Sketch how a 4-wide superscalar fetches when the I-cache line is 64 B and a branch is mid-line.
Suppose 4-byte instructions, so a cache line holds 16 instructions. The fetch unit reads the whole line, then uses the PC's low bits to extract a contiguous group of up to 4 instructions starting at the PC. If a branch is encountered before the 4th, fetch stops at the branch and the predicted target's first instructions come next cycle (potentially from a different cache line — needing a second fetch port or a 1-cycle stall).
Modern designs use a branch target buffer (BTB) to predict the target during fetch, and may employ a uop cache to deliver decoded sequences regardless of branch location.
What's a uop cache, and why did Intel add one to Sandy Bridge?
A uop cache stores already-decoded micro-operations indexed by instruction address. On a hit, fetch+decode is bypassed entirely — uops flow directly into rename. Saves both latency and power (decoders are expensive in x86 due to variable-length CISC instructions).
For tight loops that fit in the uop cache (~1500 entries on Sandy Bridge, 4K+ on later cores), the front-end runs much faster and the legacy decoders can be clock-gated. Same idea Pentium 4's trace cache attempted, but more conservatively scoped — and it actually worked.
Relationship between issue width, ROB size, and PRF size?
They have to be balanced. ROB size sets the instruction window. PRF size must hold all in-flight architectural and speculative writes — typically ROB_size + arch_reg_count. Issue width sets how many can move per cycle, but if the queue/ROB is undersized you stall front-end before the window fills.
A poorly balanced design (8-wide front-end with a 64-entry ROB) leaves the back-end starved. Modern designs target 60–80 ROB entries per issue slot as a rough heuristic.
Superscalar Techniques.
The mechanisms that make wide out-of-order execution actually work. Branch prediction, register renaming, Tomasulo, ROB, load-store ordering. The heart of the book and the heart of every architecture interview.
Branch prediction basics
Why branches are catastrophic in deep superscalar pipelines: a misprediction discards everything fetched after the branch — potentially 50–100+ uops in a wide deep CPU. Even 95% accuracy can cost 1–2 IPC if the penalty is 15 cycles.
Static prediction
Decision is made at compile time or by simple rule. Examples: always-not-taken, backward-taken/forward-not-taken (BTFNT — exploits loops), profile-guided. Cheap, ~70–80% accuracy.
One-bit BHT
One bit per branch storing the last outcome. Predicts "what happened last time." Fails badly on alternating patterns (T,N,T,N) — gets every branch wrong.
Two-bit saturating counter
Four states: Strongly NT (00), Weakly NT (01), Weakly T (10), Strongly T (11). Predict T if MSB=1; update by ±1 (saturating). Tolerates one anomaly without flipping. ~85–90% accuracy alone.
Two-level predictors (BHT + PHT)
Maintain a global or per-branch history register (last N outcomes as a bitstring). Use the history (often XOR'd with the PC — that's gshare) to index a Pattern History Table of 2-bit counters. Captures correlation between branches. ~95%+ accuracy. Detailed in Ch 9.
Branch misprediction recovery
When a branch resolves and was mispredicted:
- Squash all instructions younger than the branch in fetch, decode, rename, ROB.
- Restore architectural state — recover the rename map (using checkpoints or a recovery walk).
- Redirect fetch to the correct PC.
Modern designs checkpoint the rename map at every branch (tens of checkpoints in flight). Recovery is then 1–2 cycles. Without checkpoints, you'd have to walk the ROB backwards undoing renames — much slower.
Register renaming — the mechanism
The architectural register file (16–32 registers per ISA) is mapped to a physical register file (PRF) of 100–500+ registers. The mapping lives in a rename table (RAT — Register Alias Table).
Two PRF designs:
- Unified PRF (Intel since Sandy Bridge, modern AMD, Apple): all values live in the PRF. The RAT stores pointers. No data movement on retire — just commit the PRF entry as architectural.
- ARF + ROB-based forwarding (older P6, original Tomasulo): values live in ROB until retirement, then move to ARF. Simpler conceptually, more data movement.
Tomasulo's algorithm
The 1967 IBM 360/91 algorithm that essentially defined out-of-order execution. Three innovations:
- Reservation stations at each functional unit, each holding an instruction and tags for its operands.
- Common Data Bus (CDB): any FU broadcasts results with a tag; all RSes snoop and capture matching operands.
- Register renaming via tagging: each RS has a tag identifying its result. Younger consumers capture the tag from the RAT and wait for that tag on the CDB.
The original Tomasulo had no ROB — instructions retired (wrote registers) when they completed, which made exceptions imprecise. Modern designs add the ROB to fix that.
Reservation stations vs issue queue
Distributed RSes: each functional unit has its own queue. Wakeup is local. Used by AMD K7/K8 and PowerPC 620.
Centralized issue queue: one shared queue, instructions select target FU at issue. Used by P6, modern Intel, modern AMD. More flexible but bigger CAM and harder timing.
Memory data flow — load-store ordering
Loads and stores are the trickiest part of OoO. Memory is a shared, addressable structure; you can't simply rename addresses the way you rename registers (you don't know the address until execution).
Store-to-load forwarding
If a young load reads from an address that an older un-retired store wrote, the load must get the store's value (not the stale value in cache). The store buffer / load-store queue (LSQ) tracks in-flight stores; loads compare their address against pending stores and forward when matching.
Memory disambiguation
Can a load with unknown address pass an older store with unknown address? Conservative: no, wait for the store address. Aggressive: speculate "no alias" and re-execute on misspeculation. Intel's memory dependence predictor learned which load-store pairs alias; modern designs continue this.
In a relaxed memory model (ARM, RISC-V), loads can be reordered with respect to other loads as long as program-order data dependences hold. Getting this wrong in microarchitecture causes silent data corruption in multithreaded code. Memory ordering verification is one of the hardest tasks in CPU validation.
Interview Q&A
Chapter 5 · 14 questionsWalk through register renaming with a concrete example.
Consider: add R1,R2,R3 ; add R4,R1,R5 ; add R1,R6,R7. Architecturally, the third add overwrites R1 — a WAW with the first.
Rename allocates fresh physical registers for each destination: first add → P10, second → P11, third → P12. Source operands look up in the RAT (current arch-to-phys map).
Result: add P10,P2,P3 ; add P11,P10,P5 ; add P12,P6,P7. No false dependence — first and third can execute in parallel. The RAT now maps R1→P12.
On retirement, P10, P11, P12 are committed in order. P10 is freed when a later instruction overwrites R1 architecturally (when the third add retires) — the freelist mechanism.
Explain Tomasulo's algorithm in 30 seconds.
An instruction is dispatched to a reservation station, which holds either operand values or tags identifying the producer. When all operands arrive, the instruction issues to its FU. The FU computes and broadcasts the result on the Common Data Bus along with the producer tag. All RSes and the register file snoop the CDB; any waiting RS with a matching tag captures the value as one of its operands. This decouples execution from program order while still respecting data dependences.
How does a 2-bit saturating counter work, and why is it better than 1 bit?
States: 00 (Strongly NT), 01 (Weakly NT), 10 (Weakly T), 11 (Strongly T). Predict taken if MSB=1. On taken outcome, increment (saturating at 11). On not-taken, decrement (saturating at 00).
Benefit over 1 bit: tolerance to one anomaly. A loop that's taken 99 times and not-taken once stays in state 11 throughout — only momentarily dipping to 10. A 1-bit predictor would mispredict the iteration AFTER the not-taken one too. Empirically: 2-bit is ~85–90% on SPEC, 1-bit is ~70–80%.
What's gshare, and why does it work?
gshare uses XOR of the global branch history register and the branch PC to index a table of 2-bit counters. The XOR distributes correlated patterns across the table while still using PC bits to differentiate static branches.
Why it works: many branches correlate with recent control flow ("if (a) {} else { if (b) {} }" — the second branch's behavior depends on the first). Pure per-branch (PAg) misses this. Pure global (gselect/gshare) captures it. gshare's XOR is a cheap way to combine global history with PC for index aliasing reduction.
Walk through what happens to in-flight instructions on a branch misprediction.
1. Branch resolves in EX, comparing actual outcome to predicted. 2. Mismatch detected → squash signal asserted. 3. All ROB entries younger than the branch are marked invalid (or the tail pointer is rewound). 4. The rename map is restored from a checkpoint taken at the branch — every later rename is undone in one cycle. 5. Issue queue / RSes flush younger instructions. 6. Fetch is redirected to the correct target PC. 7. Pipeline refills — the misprediction penalty.
Older designs without rename checkpoints walked the ROB backward undoing the RAT, which took many cycles. Modern designs allocate a rename-map checkpoint at every branch.
What is store-to-load forwarding? Why is it tricky?
When a load executes, it must check whether any older un-retired store wrote the same address. If so, the load gets the store's value (which isn't yet in cache). The load-store queue holds pending stores with their addresses and data; loads do an address CAM against pending stores.
Tricky because: (1) addresses may not be known yet when the load executes (must conservatively wait, or speculate); (2) partial overlaps — a 4-byte load that overlaps a 2-byte and a 1-byte store needs partial-data assembly; (3) misaligned cases; (4) on misspeculation, a load that received forwarded data must be replayed. Intel's memory dependence predictor learns which loads can safely speculate past unknown stores.
WAW, WAR, and RAW after renaming?
RAW remains — it's a true value dependence and renaming can't remove it. The consumer must wait for the producer to compute the value.
WAR and WAW vanish. After renaming, the second writer gets a fresh physical register, so there's no shared register name between older and newer writes. The older read targeted the original physical register, which is preserved in the freelist until safely retired.
Why are modern designs unified PRF instead of ROB-based?
In ROB-based designs (early P6, original Tomasulo with reorder buffer), values live in the ROB entry until retirement, then are copied to the architectural register file. This means data is moved on retirement (consumes a write port and energy) and consumers must read either ARF or ROB depending on the producer's state — complex.
Unified PRF: values live in physical registers throughout. The RAT maps architectural names to physical registers. On retirement, no data moves — the physical register is simply marked as "the architectural one for now." The previous physical register for that architectural name is freed. Cleaner, less power, but PRF must be larger. Now standard.
What's a hybrid (tournament) branch predictor?
Two predictors run in parallel — typically a local (per-branch history) and a global (gshare-style) predictor. A meta-predictor (also a 2-bit counter table) chooses which to trust for each branch based on which has been right more often recently.
Why: different branches favor different predictors. Loop branches with stable per-branch patterns favor local history; branches that correlate with recent control flow favor global. The tournament gets the best of both. Alpha 21264 popularized this; modern designs use even more sophisticated combinations (TAGE, perceptron). Detailed in Ch 9.
What's the memory dependence predictor (MDP)?
Predicts whether a load will alias with an older un-known-address store. If predicted no-alias: load executes early, speculatively. If alias is later detected: load is squashed and re-executed. If predicted alias: load waits for the store address.
Intel's "store sets" implementation (Chrysos & Emer, 1998) learned which loads tend to alias which stores. Without MDP, conservative ordering forces every load to wait — kills IPC. With MDP, loads execute aggressively and only the rare misspeculations replay.
How does the issue queue (or reservation station) wake up instructions?
Each entry has tag(s) identifying the producer(s) of its operand(s). When a result is broadcast on the result bus, the entry's CAM compares its source tags against the broadcast tag; a match captures the result and clears the corresponding "ready" bit. When all sources are ready, the entry asserts a request to the selector.
The wakeup-select loop is one of the tightest critical paths in a modern OoO core — broadcast → tag compare → operand capture → ready signal → selector → grant must all happen in one cycle (or two, in a pipelined scheduler). This is why issue queues are typically small (32–100 entries).
What is a "speculative" instruction, and what happens if speculation is wrong?
An instruction is speculative if it's executing past an unresolved branch or other speculation point. Its result is committed to architectural state only if speculation is verified correct (the branch resolves as predicted, no exception, no memory misorder, etc.).
If speculation is wrong, the instruction and all younger ones are squashed: ROB entries cleared, rename map rolled back, issue queue flushed, store buffer entries cancelled. Side effects on caches and prefetchers usually persist — this is why Spectre attacks work, since speculative loads leak cache state. Architectural state is unaffected.
What are precise exceptions, and why do they require the ROB?
An exception is "precise" if, when signaled, all instructions before it have completed and none after have any effect — exactly as if the program had executed in strict order. Required for sane OS interrupt handling, debugging, and virtual memory page faults.
Out-of-order execution naturally executes instructions in any order. The ROB enforces in-order retirement: an exception is only delivered when the offending instruction reaches the ROB head. Younger instructions in the ROB (which may have already executed) are squashed. The architectural state matches the precise-exception requirement.
How does load forwarding handle a partial-width store?
If a 4-byte load reads bytes 0–3 and an older store wrote bytes 1–2, you have a partial overlap. Three policies:
- Forward only on full match: if any partial overlap and not full match, stall the load until the store retires (writes to cache). Simple, often slow.
- Partial forwarding: assemble the load value from store buffer bytes + cache bytes. Complex hardware, faster.
- Replay the load: mark the load to re-execute after the offending store completes.
Modern Intel does limited partial forwarding for aligned cases; misaligned partial overlaps trigger a replay penalty (the famous "store forwarding stall"). Common micro-optimization target in HPC code.
PowerPC 620.
The first commercial 64-bit PowerPC superscalar. The book uses it as an end-to-end case study because its design is clean enough to study and aggressive enough to teach real superscalar trade-offs. Understanding 620 prepares you for any modern OoO microarchitecture.
Why study an older processor?
Modern CPUs are too complex to fully document publicly — Intel won't tell you exactly how their issue queue works. The 620 is fully documented and analyzed, and contains every essential mechanism (branch prediction, register renaming, ROB, OoO issue, multiple FUs) at a scale you can hold in your head. Once you understand 620, every modern out-of-order CPU is just "620 with more of everything."
The 620 at a glance
| Parameter | 620 | Modern Reference |
|---|---|---|
| Issue width | 4-wide | 6–8 wide |
| ISA | PowerPC 64-bit | — |
| Pipeline depth | ~5 main + diversified | 14–20 |
| Execution units | 2 Int, 1 Mul/Div, 1 FP, 1 LSU, 1 Branch | 4–6 ALUs + multiple LSU/FPU |
| ROB / completion buffer | 16 entries | 200–600+ |
| Reservation stations | 6 RSes (one per FU), 2–4 entries each | centralized, 60–100 |
| Rename registers | 8 GPR + 8 FPR + 16 condition reg fields | all in unified PRF, 200+ entries |
| L1 I-cache / D-cache | 32 KB / 32 KB | 32–192 KB / 32–128 KB |
| Branch prediction | 2-bit BHT, 256 entries | TAGE/perceptron, KB-scale |
The pipeline
The 620 is a classic example of diversified, dynamic pipelines. The front-end is in-order, the back-end is OoO, and instructions retire in order via the ROB.
Instruction fetch
The 620 fetches up to 4 instructions per cycle from a 32 KB I-cache. A 256-entry, 2-bit Branch History Table guides prediction; a small BTAC (Branch Target Address Cache) provides the target address quickly so fetch isn't held up by decode-stage target computation.
Branch prediction is checked at decode (where the actual target is computed) and at execute (where the actual outcome is known). Misprediction penalty depends on where the misprediction is caught — closer to fetch is cheaper.
Dispatch
Up to 4 instructions per cycle are dispatched. Dispatch checks:
- Is the appropriate reservation station available?
- Is a ROB entry available?
- Are there enough rename registers?
If any check fails — dispatch stall. The 620's small RSes (2–4 entries each) and small ROB (16 entries) are a frequent source of dispatch stalls in dependence-heavy code, and the book's measurements quantify this.
Issue and execution
Each FU has its own RS. Instructions in an RS monitor the result bus, capturing operand values when their producer broadcasts. When all operands are ready, the instruction is selected by that FU's local arbiter.
This distributed scheduler approach trades flexibility for simplicity. A free integer FU can't "steal" an instruction whose RS is the other integer FU. Modern designs use centralized issue queues to avoid this load imbalance.
Completion and retire
The 620's ROB has 16 entries. Up to 4 instructions can complete (write the ROB) per cycle, and up to 4 can retire (commit to architectural state) per cycle. Stores commit from the ROB to a store queue, which writes the cache asynchronously.
What the measurements show
The chapter's empirical analysis on SPEC95 reveals real-world bottlenecks:
- Average sustained IPC: ~1.2–1.6 — far below the 4-wide peak. Real ILP is limited.
- Dispatch stalls dominate. Small RSes and limited rename registers cause front-end starvation more than execution-unit contention.
- Branch mispredictions cost a lot. Even with 2-bit BHT, ~10% misprediction × 4-cycle penalty hurts.
- Cache effects are substantial. L1 misses to memory cost dozens of cycles; loads dominate the stall profile.
A 4-wide pipeline doesn't mean IPC of 4. Real sustained IPC is dominated by stalls — branch mispredicts, cache misses, dispatch limits — not by execution-unit utilization. This is the reason "Speed Demons vs Brainiacs" (Ch 8) became the central design debate of the 1990s. The 620 was a brainiac that didn't quite earn its complexity.
Interview Q&A
Chapter 6 · 8 questionsWhy was the PowerPC 620 chosen as the textbook case study?
It contains every essential mechanism of a modern OoO superscalar — branch prediction, register renaming, distributed reservation stations, ROB-based retirement, multiple functional units — at a documented, comprehensible scale. The complete microarchitecture and SPEC95 performance data are public. Modern processors hide most internal details, so 620 remains the best end-to-end teaching example.
Distributed reservation stations vs centralized issue queue. Which does 620 use?
620 uses distributed RSes — each FU has its own small queue (2–4 entries), 6 RSes total. Pro: small CAMs, fast wakeup. Con: load imbalance — if one integer RS fills while the other is empty, dispatch stalls even though execution capacity exists.
A centralized issue queue (P6, modern Intel/AMD) is one shared queue; instructions select an FU at issue time. Pro: better utilization, no per-FU starvation. Con: bigger CAM, longer wakeup-select path. Modern designs almost universally use centralized.
If 620 is 4-wide but achieves ~1.5 IPC, where do the missing 2.5 issue slots go?
To stalls. Empirical breakdown roughly:
- ~30–40%: dispatch stalls — small RSes/ROB fill.
- ~20–30%: data-cache misses (loads stall consumers).
- ~10–15%: branch mispredictions.
- ~10%: instruction-cache misses and front-end gaps.
- Rest: serialized dependence chains, structural conflicts.
Notice execution-unit contention is rarely the bottleneck — adding a 5th FU wouldn't help. Wider front-end + bigger window + better predictor would. This guides everything modern CPUs do.
What's a BTAC, and how does it differ from a branch direction predictor?
The direction predictor (BHT, gshare, etc.) predicts whether a branch is taken or not — a 1-bit decision.
The BTAC / BTB stores the target address of taken branches, indexed by the branch PC. Without it, fetch must wait for decode to compute the target — too slow at high frequencies. The BTAC delivers the target during fetch itself, enabling back-to-back taken-branch fetch.
Both are needed in a high-performance front-end.
Role of rename registers in PowerPC 620?
The 620 has 8 GPR rename registers, 8 FPR rename registers, and 16 condition register field rename buffers separate from the architectural file. When an instruction dispatches, a rename register is allocated for its destination; the architectural file is only updated at retirement.
This is the older "ROB + ARF + rename file" style — three separate storage structures. Modern designs collapse this into a unified PRF. The 620's design was conservative for its era; subsequent PowerPCs (G3, G4, G5) moved to unified PRFs.
How are stores handled in the 620's pipeline?
A store dispatches to the LSU's RS. When its address and data are both ready, it executes — meaning the address translates and the data is held in a store queue entry. The store does NOT modify the cache yet; that would be visible to other observers and break precise exception semantics.
Only when the store reaches the ROB head and retires does it commit from the store queue to the cache. This guarantees that stores from a squashed speculation path never leak to memory. Modern designs work the same way, with the LSQ being a more sophisticated structure that also forwards data to younger loads.
Bridge to POWER3 and POWER4?
POWER3 (1998) widened the front-end, added more execution units, and refined branch prediction. POWER4 (2001) was a major leap: dual-core (one of the first multicore desktop chips), much wider pipelines, sophisticated branch predictors, and aggressive memory hierarchy.
The 620 → POWER3 → POWER4 trajectory exemplifies what every CPU family does over generations: more issue width, deeper pipelines for higher clocks, better predictors, larger windows, more cores. Same fundamental machinery as 620, just scaled.
Redesigning the 620 today with no transistor constraints — what would you change first?
An honest interview answer:
- Bigger ROB and PRF. 16 ROB entries is tiny — exposing more ILP is the cheapest IPC win.
- Centralized issue queue instead of distributed RSes — eliminates load imbalance.
- Better branch predictor. A simple BHT was fine for 1995 but TAGE/perceptron buys 5%+ IPC today.
- Non-blocking caches with prefetching. Modern non-blocking design hides much more memory latency than 620's conservative approach.
What I would NOT do first: increase issue width. The 620's bottleneck wasn't EX bandwidth; it was window size. Going from 4-wide to 6-wide without enlarging the window is wasted silicon.
Intel P6.
The microarchitecture behind Pentium Pro, Pentium II, and Pentium III — the design that proved x86 could be high-performance and survived in the DNA of every Intel chip since. P6 introduced uop translation, deep OoO, and a unified ROB that defined Intel's house style for 25 years.
The core insight: translate, don't execute
x86 is a CISC ISA: variable-length instructions, complex addressing modes, memory-to-memory ops. Implementing it directly in OoO hardware is awful — every instruction is a special case.
P6's revolution: decode x86 instructions into simple, fixed-format micro-operations (uops), then run a clean RISC-style OoO core on the uops. The legacy ISA stays compatible; the engine doesn't pay for the legacy.
One x86 instruction becomes 1–4 uops typically. Some complex instructions (string ops, transcendentals) explode into many uops via a microcode ROM. Most simple instructions decode 1-to-1.
Three-phase pipeline
P6 organizes itself conceptually into three pipelines:
1. In-order front-end
- Instruction fetch — 32 B/cycle from L1 I-cache, plus ITLB lookup.
- Branch prediction — 512-entry BTB with 2-bit counters, RAS for returns.
- Decode — three parallel decoders. The "complex decoder" handles instructions producing 1–4 uops; two "simple decoders" handle 1-uop instructions only. The famous "4-1-1 rule": each cycle, decoders can only produce that pattern.
- RAT (Register Alias Table) — renames x86 architectural regs to physical regs (40 entries in P6's ROB).
- Allocator — allocates ROB entry, RS entry, MOB entry per uop.
2. Out-of-order core
- Reservation Station — 20-entry centralized issue queue. Watches for operand readiness via tag matching on the result bus.
- 5 issue ports: Port 0 (ALU/FP/Mul), Port 1 (ALU/Jump), Port 2 (Load), Port 3 (StoreAddr), Port 4 (StoreData).
- Up to 5 uops can issue per cycle, but throughput is bounded by RS size (20) and front-end (3 uops/cycle decode) — sustained ~3 uops/cycle.
3. In-order retirement
- 40-entry ROB tracks uops in program order.
- Up to 3 uops retire per cycle. Retirement updates the architectural state (RRF — Retirement Register File) and commits stores from the MOB.
The Memory Order Buffer (MOB)
P6's load/store handling lives in the MOB, which has two parts:
- Load buffer (LB): tracks in-flight loads. Each load CAMs older stores in the SB; if it finds an alias, the older store's data is forwarded.
- Store buffer (SB): tracks stores from execute through retirement. Stores commit to cache only at retirement.
P6 was conservative about load speculation: if a load's address matched an older store with unknown address, the load waited. Later Intel designs added a memory dependence predictor to allow speculation past unknown stores.
Why P6 beat Pentium
The original Pentium was in-order, dual-issue (U/V pipes). Pentium Pro (P6, 1995) was OoO, 3-issue uop-based. On real workloads:
- Compiler-friendly RISC code helped Pentium's U/V pairing — but it required careful scheduling.
- Real-world (not hand-tuned) x86 code mixed instructions chaotically; OoO P6 extracted parallelism the compiler couldn't.
- P6 hid memory latency much better via OoO + non-blocking caches.
P6's design lived through Pentium II/III, and its key ideas (uop translation, OoO with ROB, RAT, RS) directly evolved into Banias → Core → Nehalem → Sandy Bridge → modern Intel cores. The "Pentium 4 NetBurst" detour was a deep-pipeline experiment that failed; Intel returned to P6's lineage with Core in 2006.
Every Intel chip since 2006 (Core through Lunar Lake) is essentially a much-bigger, much-wider, much-faster P6. The mechanisms — uop translation, RAT, ROB, centralized RS — are unchanged in principle. Once you understand P6, you understand the family.
Interview Q&A
Chapter 7 · 10 questionsWhy does Intel decode x86 instructions into uops?
x86 is irregular: variable-length, complex addressing modes, memory-to-memory ops. Implementing OoO scheduling directly on x86 instructions would mean every instruction is a special case — register port pressure, dependency analysis, exception handling all become combinatorial nightmares.
uops are fixed-format, RISC-like, with a uniform "register-register" structure (memory operands become explicit load+op or op+store sequences). The OoO core is now ISA-agnostic; it just schedules a stream of simple uops. This separation has let Intel keep the same OoO engine architecture across decades while x86 itself accumulated extensions.
What does "4-1-1 decode" mean?
P6 had three parallel decoders: D0 (complex, can produce up to 4 uops), D1 and D2 (simple, 1 uop each). The constraint per cycle: D0 can take any instruction; D1/D2 only single-uop instructions. So if an x86 instruction sequence is "complex, complex, simple," only the first complex goes through D0 and the second complex stalls D1, breaking the sequence.
The "4-1-1" name is the per-decoder uop budget per cycle. Compilers tried to schedule x86 sequences to match this pattern. Modern Intel decoders relaxed this with multiple complex decoders and a uop cache that bypasses the legacy decoders entirely.
P6's ROB-based design vs modern unified PRF (Sandy Bridge+)?
P6: ROB entries hold uop status AND result data. Architectural state lives in a separate retirement register file (RRF). The RAT maps architectural regs → ROB entries. On retirement, data moves from ROB to RRF — a real data movement.
Sandy Bridge (and modern Intel/AMD): one large physical register file. ROB entries hold metadata only — pointers to PRF entries. RAT maps architectural regs → PRF indices. On retirement, no data moves; the PRF entry simply becomes "the architectural one" and the previous architectural PRF entry is freed.
Unified PRF saves energy (no data copy on retire) and read ports (consumers always read PRF). It needs a bigger PRF to hold all in-flight values, but transistor budgets allow this since 2010.
Why is P6's reservation station "centralized" while PowerPC 620's is "distributed"?
P6 has one 20-entry RS that can dispatch uops to any of 5 issue ports. 620 has separate small RSes attached to each FU.
Centralized: better utilization (an idle ALU port can grab a ready integer uop regardless of which RS entry it's in). But the wakeup CAM is bigger (more entries × more sources to compare) and the select logic is more complex (must arbitrate across all ports).
Distributed: simpler local hardware. But uneven RS occupancy causes dispatch stalls. P6's choice traded hardware complexity for utilization, which proved correct as silicon budgets grew.
What were the issue ports in P6?
P6 had 5 ports. Port 0: ALU, integer multiplier, FP unit, divider. Port 1: ALU, branch unit, integer shifter. Port 2: load AGU. Port 3: store address AGU. Port 4: store data.
Insight: a store needs both Port 3 AND Port 4 (address compute and data movement) — two slots for what looks like one operation. A load only needs Port 2. Modern Intel has more ports (8–12) and finer specialization, but the same idea — uops are typed by the ports they can use.
What's the Memory Order Buffer (MOB), and why is it separate from the ROB?
The MOB tracks loads and stores specifically — their addresses, data, and ordering. The ROB tracks all uops for retirement and exceptions. They're separate because memory ordering needs different bookkeeping (address CAM, store-load forwarding) that other uops don't.
Load buffer entries CAM against store buffer entries on every load. The store buffer holds data until retirement. Both are sized to match the ROB — every in-flight load/store has a corresponding MOB entry.
Why did Intel return to P6's design lineage after the Pentium 4 (NetBurst) era?
NetBurst (P4) bet on extreme pipeline depth (~31 stages effective) and very high frequencies (4+ GHz target). The bet failed because:
- Branch misprediction penalty became enormous (~20 cycles).
- Power scaled badly — leakage at high frequencies hit the wall around 3.8 GHz.
- The trace cache had cold-start and capacity issues.
- Per-cycle work (IPC) dropped because so much logic was bypassed for speed.
The Banias / Pentium-M team (originally building laptop chips) had been quietly evolving P6 with better branch prediction and prefetching. Core (2006) productized this. The 13–14 stage P6-derived pipeline at ~3 GHz beat the 31-stage P4 at 3.8 GHz on real workloads — and used half the power. P6's lineage continues today.
What's uop fusion (macro-op fusion / micro-op fusion)?
Macro-op fusion: two adjacent x86 instructions are fused into one uop. Classic case: cmp r,r ; jcc target becomes one fused uop that does the compare and conditional jump. Saves a uop in the front-end and the OoO window.
Micro-op fusion: what would be two uops (e.g., load + ALU op) are kept as one uop in the front-end and ROB but issued as two to ports. Saves ROB entries and decode bandwidth.
Both were introduced in Pentium-M / Core era and are now ubiquitous. They effectively widen the pipeline at the front-end and ROB without increasing structural width — a clever trick.
What happens when an x86 string instruction (like REP MOVSB) is decoded?
It's microcoded. The decoder recognizes the opcode and triggers the microcode ROM, which generates a sequence of simpler uops (load, store, decrement counter, conditional branch). This sequence may be hundreds of uops for long strings.
Modern x86 has "fast string" optimizations: REP MOVSB on recent CPUs is detected and replaced by a hardware-accelerated bulk copy with magic — bypassing the per-byte microcode loop. Without it, REP MOVSB would be slower than a hand-coded loop. With it, REP MOVSB is often the fastest memcpy implementation.
What IPC would you expect from a P6-class core on a typical workload?
P6 itself: ~1.5–2.0 sustained IPC on SPEC, peak 3 (decode-limited). Modern P6-derived cores (Skylake-and-later): 2.5–4 IPC typical, peak 5–6 internally. The bottleneck has shifted: P6 was decode-limited; modern chips are usually limited by branch mispredictions and memory latency rather than decode bandwidth (uop cache solved that).
You'd answer in an interview: "It depends heavily on workload. For SPEC-style, ~3. For a memory-bound HPC benchmark, often <1. For tight loops in cache, 4+." Show you know IPC isn't a single number — it's a workload property.
Survey of Designs.
A tour of the major superscalar processors — how Alpha, MIPS, PA-RISC, x86, x86-64, PowerPC, SPARC each made different trade-offs along the same fundamental design space. The chapter's headline framing — "Speed Demons vs Brainiacs" — defines the central debate of the 1990s and still illuminates modern design choices.
Speed Demons vs Brainiacs
Two opposing philosophies emerged in 1990s superscalar design:
Speed Demons (clock speed wins)
Shallow IPC, very deep pipeline, aggressive frequency. Simpler per-stage logic = shorter critical path = higher clock. Examples: DEC Alpha 21164/21264, Intel Pentium 4.
Strategy: max frequency, accept lower IPC. Best for code with high ILP and good branch behavior.
Brainiacs (IPC wins)
Wider issue, more complex out-of-order, larger windows. Lower clock but higher IPC. Examples: HP PA-8x00, IBM POWER, Intel P6 (and modern Intel/AMD/Apple).
Strategy: extract every ounce of ILP, accept lower clock. Best for irregular workloads with unpredictable memory access.
Brainiacs, mostly. The power wall and memory wall capped frequency growth around 2004. Once you can't go faster, you must do more per cycle. Modern Apple M3 and Intel Lunar Lake are deeply brainiac: 6–8-wide, ROB of 600+, modest 3–4 GHz clocks. The P4 NetBurst era is remembered as the last serious speed-demon attempt.
Major families surveyed
DEC / Compaq Alpha (1992–2001)
The legendary clean-slate RISC ISA. Alpha 21064 (1992) was the first commercial 64-bit RISC superscalar. Alpha 21164 (1995) added on-die L2. Alpha 21264 (1998) was a brilliant OoO design with the "tournament predictor" that became standard. The 21364 added an integrated memory controller — years before Intel/AMD did the same.
Alpha was technically excellent but commercially doomed by HP/Compaq mismanagement and Itanium politics. Its engineers (Joel Emer, Glenn Hinton, Jim Keller) seeded later teams at Intel, AMD, Apple, and Tesla.
HP PA-RISC
Started simple (PA-7000) and got progressively more aggressive (PA-8000 was a wide OoO brainiac with 56-instruction window — huge for 1996). Eventually merged with Intel's Itanium. Influential ISA but ended in the EPIC dead end.
MIPS
The textbook RISC, optimized for compiler-driven scheduling. R2000 was scalar in-order; R10000 was the OoO superscalar generation. MIPS dominated workstations and embedded; pivoted to embedded-only in the 2000s, eventually licensed to Imagination/Wave/MIPS Inc. The ISA influenced RISC-V deeply.
IBM POWER / PowerPC
POWER1 → POWER2 (RS/6000) — wide issue, high-end workstation. PowerPC (joint with Apple/Motorola) brought the ISA to desktop. POWER4 (2001) was the first commercial multi-core. POWER continues today as IBM's high-end server CPU and is technically among the most aggressive designs (POWER10: 8-wide, deep OoO).
Intel x86 (8086 lineage)
The textbook covers the i486 (in-order, pipelined) → Pentium (in-order superscalar U/V pipes) → Pentium Pro (P6, OoO via uops) → Pentium 4 (NetBurst, deep speed-demon). Modern history (Core, Skylake, Lunar Lake) postdates the book but follows the same trajectory: P6 evolution.
x86-64 (AMD's contribution)
AMD extended x86 to 64 bits with Athlon 64 / Opteron in 2003. Doubled register count to 16 GPRs (huge IPC win), added 8 more SIMD registers. Intel adopted (initially reluctantly) as "Intel 64." This decision shaped the next 20 years of computing.
SPARC
Sun's RISC. Distinctive features: register windows (rotating banks of registers for fast function calls), tagged arithmetic. UltraSPARC went OoO. Sun's SPARC efforts ended; Fujitsu's SPARC64 line continues for HPC.
Common trends across families
Strip away the ISA differences and the underlying microarchitecture trajectory is identical:
- 1980s: Scalar, pipelined. ~1 IPC peak, ~0.6–0.8 sustained.
- Early 1990s: In-order superscalar. 2-issue, ~1.3 IPC sustained.
- Mid 1990s: Out-of-order with small windows (16–40 ROB). 4-issue, ~1.5–2 IPC.
- 2000s: Bigger windows (100+ ROB), better predictors, multicore.
- 2010s+: Very wide (6–8), huge windows (300+ ROB), SMT, integrated graphics/AI.
Convergent evolution. The constraints (ILP wall, memory wall, power wall) push every team toward similar answers.
Interview Q&A
Chapter 8 · 8 questionsDifference between "speed demon" and "brainiac"?
Speed demons aim for extreme clock frequency through deep, simple pipelines (Pentium 4, Alpha 21164). Brainiacs aim for high IPC through wide issue and aggressive OoO (P6, Apple M-series). Both can win on different workloads; the modern industry has converged on brainiac because power and memory walls cap frequency.
Why did Alpha die despite being technically excellent?
Compaq acquired Digital, then HP acquired Compaq. HP had bet on Itanium (jointly developed with Intel). Killing Alpha was politically necessary to commit to Itanium. By the time Itanium failed (it failed badly), Alpha's team and momentum were gone.
Lesson: technical merit doesn't guarantee survival. Business decisions, ecosystem, and strategic alignment matter more. Alpha's engineers ended up driving advances at Intel (Hinton on Core/Nehalem), AMD (Keller on K8/Zen), and Apple (Keller and others on M-series).
What was special about the Alpha 21264's branch predictor?
It introduced the tournament predictor: a local-history predictor and a global-history predictor running in parallel, with a meta-predictor (also a 2-bit counter table) choosing which to trust per branch. Got the best of both prediction styles.
This design directly inspired later predictors. Modern TAGE and perceptron predictors are descendants of this idea — combining multiple prediction sources adaptively.
What did x86-64 add architecturally beyond 64-bit?
Several huge wins beyond just wider addresses:
- Doubled GPR count (8 → 16). x86's 8 GPRs were a massive register-pressure bottleneck. 16 reduced register spilling and improved IPC noticeably.
- Doubled XMM register count (8 → 16). Big win for vectorized code.
- Cleaned up addressing modes for the new RIP-relative mode and removed some legacy.
- NX bit (no-execute) for security.
The register doubling alone bought ~10–20% IPC on integer code. AMD's most strategically important architectural decision.
Why is there convergent evolution in CPU design across companies?
Same constraints push toward same answers. The ILP wall caps useful issue width around 6–8. The memory wall makes large windows necessary. The power wall caps frequency around 4–5 GHz. Within those bounds, the optimal design is approximately known: wide front-end, large window, sophisticated branch predictor, multi-level cache, SMT, multicore.
You see Apple, AMD, Intel, Qualcomm, ARM, IBM all converge on similar block diagrams not because they copy each other but because the math points there. Differentiation now comes from execution quality, integration (AI accelerators, GPU), and process node — not architectural breakthroughs.
When does VLIW (or EPIC) make sense vs OoO superscalar?
VLIW wins when:
- The workload is regular and predictable (DSP, signal processing, GPU shaders).
- The compiler can fully analyze the program's parallelism.
- You want low power (no OoO bookkeeping).
- You're willing to recompile when the chip changes.
OoO wins for general-purpose code with cache misses, branches, and dynamic library calls — events the compiler can't predict. This is why VLIW thrived in DSPs (TI C6x) and GPUs (NVIDIA pre-Fermi was VLIW-like) but failed for general CPUs (Itanium).
What did POWER4 (2001) introduce?
It was the first mainstream dual-core high-performance chip. Two complete OoO cores on one die, with shared L2. This validated multicore as a practical design before the rest of the industry committed (Intel Pentium D came in 2005, AMD Opteron dual-core in 2005). POWER4 also pushed deep OoO with 200+ in-flight instructions, foreshadowing modern chips.
Speed-demon vs brainiac in 1995 vs 2025?
1995 honest answer: speed-demon was a defensible bet. Process scaling was still delivering frequency gains; deep pipelines won linear improvements. Many top engineers picked it (Alpha, P4).
2025: brainiac, no question. The frequency curve flattened around 2004. Power scales badly. Modern workloads (browsers, VMs, ML inference, JIT-compiled code) have unpredictable memory access patterns that punish deep pipelines. Wide OoO with great prediction and large windows is the proven winner.
The interesting modern wrinkle: heterogeneous designs (Apple's E-cores, Intel's E-cores) bring back tiny in-order cores for power-efficient throughput on background tasks. Speed-demon vs brainiac is now a per-core decision in a multi-core SoC, not a single-chip philosophy.
Advanced Branch Prediction.
Wide superscalar pipelines die without accurate branch prediction. This chapter goes from 2-bit counters to TAGE and perceptron — the predictors actually shipping in modern CPUs. If you're interviewing for any performance role, this is the densest chapter for impact.
Why predictors got more sophisticated
Three forces:
- Pipelines got deeper. 5-stage misprediction = 2 cycles. 20-stage = 15+ cycles. Each percent of accuracy is worth more.
- Pipelines got wider. A 4-wide pipeline squashes 4× as many in-flight instructions on a misprediction as a 1-wide.
- Memory got slower (relatively). A misprediction that triggers a cache-miss redirect becomes catastrophic — modern penalties can be 100+ cycles when memory is involved.
Bumping accuracy from 95% to 97% sounds tiny, but it can mean ~30% fewer mispredictions — at 15-cycle penalty over 20% branch frequency, that's serious IPC.
Two-level predictors (Yeh & Patt, 1992)
The breakthrough: branch history correlates. The outcome of branch X depends not just on X's own past, but on the recent path the program took to reach X.
Mechanism: a Branch History Register (BHR) shifts in the outcome of every branch as a 1-bit value. The BHR (last N branch outcomes) plus the branch PC index a Pattern History Table (PHT) of 2-bit saturating counters.
Variants by how BHR and PC combine:
- PAg — Per-branch BHR, global PHT. Captures patterns within a single branch.
- GAg — Global BHR, global PHT. Captures correlation between branches.
- gselect — Concatenate global BHR with PC bits to index PHT.
- gshare (McFarling, 1993) — XOR global BHR with PC bits to index PHT. Reduces aliasing more than gselect at no extra cost.
Concatenation wastes index bits when the BHR is short — many table entries unused. XOR mixes BHR and PC bits across the entire table, distributing predictions evenly. Same hardware cost (just XOR gates) but better utilization of the PHT.
Hybrid (tournament) predictors
Different branches favor different predictors. The Alpha 21264 introduced the tournament predictor: run two predictors in parallel, plus a meta-predictor (also 2-bit counters) that learns which is better for each branch.
21264's instance: a local-history predictor (per-branch BHR + PHT) AND a gshare-style global predictor, with a 2-bit meta-counter per branch choosing between them. Better than either alone.
TAGE (Tagged Geometric)
The most successful modern predictor design (Seznec, 2006 onwards). Idea: use multiple PHTs indexed with progressively longer histories, and pick the longest history that has a tagged match.
Mechanism:
- A base predictor (bimodal) for branches with no useful history.
- Several tagged tables (typically 4–12), each indexed with a different history length — geometrically increasing (e.g., 5, 13, 35, 92, 250 bits).
- Each tagged entry stores a small tag plus a counter and a "useful" bit.
- Prediction: query all tables in parallel, select the table with longest matching history.
Why it works: short branch histories solve some problems; long histories solve others. TAGE picks the right length per branch dynamically. Variants (TAGE-SC-L, ITTAGE) win every branch prediction championship and are widely believed to be deployed in modern CPUs.
Perceptron predictors
Jiménez & Lin (2001). Use a tiny perceptron (single-layer neural net) per branch. Each perceptron has weights for each bit of the global history; prediction is the sign of the dot product of weights and history.
Strength: scales to long histories (perceptron weights grow linearly, not exponentially as in PHTs). Excellent at capturing linearly separable patterns.
Weakness: can't capture XOR-like patterns (linearly inseparable). Hardware uses simple integer adders; latency was historically a concern but pipelined perceptrons solve that.
AMD shipped perceptron predictors in Bulldozer (2011) and refined through Zen. Modern Zen 4/5 reportedly uses a TAGE+perceptron hybrid.
Branch target prediction
Direction (taken/not-taken) is one half. Target prediction — where will a taken branch jump to — is the other half. For unconditional branches, jumps, indirect branches, and returns, the target matters more than the direction.
- BTB (Branch Target Buffer): caches the target of recently-taken branches, indexed by branch PC. A hit delivers the target during fetch — no waiting for decode.
- RAS (Return Address Stack): when a call instruction is detected, push the return PC onto a small stack (~16 entries). When a return is detected, pop. Returns are nearly 100% predicted correctly with this — they go where the matching call was.
- Indirect branch predictor: indirect calls (function pointers, virtual methods) need actual target prediction, not just taken/not-taken. Use a small history-indexed target cache; key for OO and VM-heavy code.
Trace cache (and uop cache)
Pentium 4's trace cache stored decoded uops in dynamic execution order, following predicted branches. A taken-branch fetch could deliver uops from across multiple basic blocks in one cycle.
Issues: cold-start penalty (predictor took time to build traces), aliasing, capacity sensitivity. P4 abandoned it; Intel returned with the simpler uop cache (Sandy Bridge+) which is just a cache of decoded uops indexed by static PC — same energy savings without trace-construction cost.
Interview Q&A
Chapter 9 · 13 questionsWhy isn't a 2-bit BHT enough for modern CPUs?
2-bit BHT captures only per-branch local history. It can't see correlations between branches. For example, "if (x > 0) { ... } if (x > 0) { ... }" — the second branch is perfectly correlated with the first, but a per-branch predictor treats them independently. Two-level (gshare) or TAGE captures this.
Also: deep pipelines penalize each percentage point of accuracy heavily. A 92% predictor was fine in 1995 (5-stage). At 20-stage with 4-wide issue, you need ≥97% to keep IPC up.
Walk through gshare lookup and update.
Lookup: 1) Read the global BHR (e.g., last 12 branch outcomes). 2) XOR with the lower bits of the branch PC. 3) Use the result to index the PHT. 4) Read the 2-bit counter; predict taken if MSB=1.
Update (after branch resolves): 1) Index the PHT the same way (XOR of original BHR with PC). 2) Increment the counter on taken, decrement on not-taken (saturating). 3) Shift the actual outcome into the BHR.
Note: the BHR used at update is the BHR at fetch time, not after — must be saved alongside the in-flight branch.
Why is XOR (gshare) better than concatenation (gselect)?
Concatenation uses BHR bits as high-order index bits and PC bits as low-order (or vice versa). For short BHRs, this clusters all entries from the same branch in nearby table slots — wasting capacity.
XOR mixes BHR and PC bits uniformly across the whole table. Same hardware cost, better aliasing distribution. Empirically gshare wins by 1–2% accuracy at the same table size.
Explain TAGE in one paragraph.
TAGE uses multiple tagged PHT-like tables, each indexed with a progressively longer global-history length (geometric series: e.g., 5, 13, 35, 92, 250 bits). On lookup, all tables are queried in parallel; the table with the longest history that has a matching tag provides the prediction. A bimodal base predictor handles branches with no useful history. Updates allocate new entries when current predictors are wrong, and a "useful" bit prevents thrashing. TAGE picks per-branch the optimal history length, which is why it consistently beats single-history predictors.
Why are perceptron predictors useful when we have TAGE?
Perceptrons scale storage linearly with history length (weights × history bits) instead of exponentially (PHT entries = 2^history). For very long histories (256+ bits), perceptron is cheaper.
Modern designs are often hybrid: TAGE for branches with short-to-medium correlations, perceptron for branches with very long-history dependence. AMD's Zen series is rumored to use this combo.
Limitation: perceptrons can't learn non-linearly-separable patterns (XOR of two history bits). TAGE handles those naturally via tagged exact-match.
What's a Return Address Stack (RAS)?
A small hardware stack (8–32 entries) that mirrors the software call stack. On every recognized CALL, push the return PC. On every RETURN, pop and use as the predicted target. Indirect branches that are returns hit ~99% accuracy this way.
Failure mode: deep recursion overflows the RAS, causing return mispredictions in the deeply-nested calls. Mitigation: increase RAS size (modern CPUs use 32+) and use a wraparound counter so overflow doesn't corrupt the most-recent entries.
How is an indirect branch (e.g., virtual method call) predicted?
For indirect branches, the issue isn't taken/not-taken — they're always taken. The issue is the target. A simple BTB just remembers the last target; this fails for polymorphic indirect branches that jump to different methods on different calls.
Modern indirect branch predictors index a target table with a hash of the PC and recent branch history (analogous to gshare for targets). This captures that "last time path A led here, the target was X; path B led to target Y." Indirect-branch-heavy code (interpreters, virtual machines) benefits enormously.
What's a BTB miss, and what happens when one occurs?
BTB miss: the branch's target isn't cached. Fetch can't redirect immediately — it must wait until decode (where the target offset is computed for direct branches) or even execute (for indirect branches). This stalls fetch for several cycles.
BTB miss penalty is smaller than direction misprediction but still significant. Cold code (first-time-executed paths) and indirect branches with new targets are common BTB-miss culprits. Larger BTBs help; multi-level BTBs (small fast L1 BTB + larger slower L2 BTB) are standard in modern CPUs.
Why did the Pentium 4 trace cache fail?
Three issues:
- Cold-start: a freshly entered region must first be decoded normally, then traces built. Workloads with poor instruction locality (call-heavy, branch-heavy) never warmed the trace cache.
- Capacity stress: traces are large (decoded uops with prediction state). The trace cache was effectively a much smaller "I-cache" measured in static instructions covered.
- Misprediction nuking: a single misprediction within a trace invalidated the rest. Trace lifetime was short.
The simpler uop cache (Sandy Bridge+) keeps the energy savings of "skip the decoders" while eliminating the trace-construction cost — it just caches decoded uops indexed by static PC, like a regular cache.
How is the BHR rolled back on misprediction?
The BHR is speculatively updated as branches predict (so dependent predictions can use the latest history). On misprediction, the speculative BHR is rolled back to the version saved at the mispredicted branch's fetch. Without this, all subsequent predictions corrupt their history.
Implementation: a small "rename" of the BHR. Each in-flight branch carries a snapshot of the BHR. On misprediction, the predictor restores that snapshot. Same idea as RAT checkpointing — small storage cost, eliminates rollback walks.
What's a partial-tag aliasing predictor?
Some predictor entries store only a partial tag (a few bits of PC), accepting that occasional aliasing happens. The tag matches "probably this branch." Partial tags save area at small accuracy cost.
TAGE uses partial tags. Pure tag-less predictors (gshare) accept all aliasing; pure full-tag predictors (BTB) waste storage. Partial tags are the engineering compromise — used widely.
What does "branch prediction overhead" mean for a chip's area and power?
A modern branch predictor can occupy 10–20% of a core's area when you count BTB, BHRs, PHTs, RAS, indirect predictor, and the supporting logic. It's typically the second-largest non-cache structure after the OoO scheduler/RF.
Power is also significant — every fetch cycle queries multiple structures. CPUs gate parts of the predictor when running predictable code (tight loops with one branch direction), but you can't shut it off.
The classic engineering question: "Should we add a 32 KB L2 BTB or a 32 KB more L1 cache?" Answer depends entirely on workload. Branch-heavy code (interpreters, browsers) benefits from BTB; straight-line numerical code benefits from cache.
Why does branch prediction sometimes leak data (Spectre)?
Mispredicted speculative loads still execute and bring data into the cache before the misprediction is detected. An attacker trains the predictor to mispredict in a way that causes a sensitive load to execute speculatively; the load's address depends on secret data; the cache state after the squash leaks the secret via timing analysis.
The architectural state was protected (speculation was undone). The microarchitectural state (cache) was not. Spectre and its variants exploit this gap. Mitigations: speculation barriers (LFENCE), retpolines (replace indirect branches with non-speculatable returns), and microarchitectural defenses (sanitize speculative loads). All cost performance.
Value Prediction.
Branch prediction breaks control dependences. Value prediction breaks data dependences — predict the result of an instruction before it computes, let consumers run early, validate later. Mostly research; small fragments live on in modern CPUs.
The data flow limit
Every instruction can execute as soon as its operands are ready. The longest chain of dependent instructions in a program window is the data flow limit — the minimum cycles even an infinite-resource OoO machine needs.
Example: a = b + c ; d = a * 2 ; e = d - 1. Three serial dependences. Even infinite ALUs can't reduce this to fewer than 3 cycles. The chain is the wall.
Value locality: the empirical observation
Lipasti, Wilkerson, & Shen (1996) measured something striking: a large fraction of dynamic instruction results repeat. Specifically:
- ~40–50% of loads return a value seen on a recent execution of that same load.
- Many ALU instructions produce the same result repeatedly.
- Some patterns are stride-predictable (counter increments).
This is value locality — analogous to spatial/temporal locality but for values rather than addresses. If values repeat, you can predict them.
Two ways to exploit value locality
Value prediction (speculative)
Predict an instruction's result before it computes. Consumers receive the predicted value and execute speculatively. When the instruction actually completes, compare. On match: free speedup. On mismatch: squash and re-execute.
Instruction reuse (non-speculative)
Cache the inputs and result of recently-executed instructions. If the same instruction with the same inputs comes around, skip execution and use the cached result. No squash needed — it's not speculative.
Predictor types
Four canonical schemes from the original literature:
- Last-value predictor: remember the most recent value of each instruction. Predict the same. Trivial; works for invariant values.
- Stride predictor: remember last value AND last stride. Predict
last + stride. Captures counters, address arithmetic, induction variables. - Two-level (context-based): table of past value sequences indexed by the recent value-history of that instruction. Captures repeating patterns ABCABCABC.
- Hybrid: a metapredictor chooses between the above per instruction (analogous to tournament branch predictor).
The verification cost
Speculative value prediction needs squash machinery on misprediction. The misspeculation penalty (squash all dependent instructions, restore state, re-execute) typically requires several cycles.
Math: predictor value, hit rate h, average dependent chain length L, squash penalty p. Speedup ≈ h × L; cost ≈ (1-h) × p. To win net: h × L > (1-h) × p. Need high accuracy (90%+) on long-chain instructions.
The verification overhead and squash recovery for general-purpose instructions costs more than it saves on real workloads. The accuracy isn't quite good enough for the chain lengths you can usefully predict. Combined with the security implications (Spectre-like issues), full value prediction has stayed in research papers.
What did make it into modern CPUs
Three derivative techniques are real:
- Move elimination / zero idiom recognition. Modern CPUs detect
xor rax,raxandmov rax,rbxat rename and execute them in zero cycles by manipulating the rename table directly. No FU usage, no latency. - Address prediction for prefetching. Stride-based load prefetchers are essentially value predictors for load addresses.
- Memory dependence prediction. Already discussed in Ch 5 — predicts whether a load aliases an unknown-address store, related to value prediction in spirit.
Interview Q&A
Chapter 10 · 10 questionsWhat is the data flow limit, and how does value prediction try to break it?
The data flow limit is the minimum cycles needed for the longest chain of true (RAW) dependencies in a program window. Even infinite hardware can't beat it because each instruction must wait for its predecessor's actual result.
Value prediction speculates the predecessor's result before it computes, allowing consumers to execute in parallel. If the prediction is right, the chain is broken. If wrong, the consumer is squashed and re-executed. Net win requires high prediction accuracy.
What's value locality?
The empirical observation that a large fraction of instruction results recur during execution. Loop induction variables, loaded addresses, constant-but-non-immediate values, and frequently-called function results all exhibit value locality.
Lipasti et al.'s 1996 study found ~50% of dynamic load values repeated within a small window. Different from temporal/spatial locality, which describe address access patterns; value locality describes the values themselves.
Compare value prediction and instruction reuse.
Value prediction is speculative — predict result, execute consumers, verify, squash on miss. Cheaper per attempt; expensive on misprediction.
Instruction reuse is non-speculative — cache (instruction, inputs, output) tuples; on a re-encounter with same inputs, skip the FU and use the cached output. Slower to look up, but no squash penalty.
Reuse is conservative; prediction is aggressive. Both target value locality from different angles.
Why don't modern CPUs ship general-purpose value prediction?
Multiple compounding issues:
- Accuracy is below the required threshold. 80–90% sounds great, but the squash cost on the 10–20% misses overwhelms the speedup.
- Power. Predictor tables and verification logic burn energy on every instruction.
- Verification complexity. Squashing only the consumers (not all younger instructions) is hard to do correctly.
- Marginal gains. Modern wide OoO already achieves close to 80% of single-thread theoretical limit. The remaining 20% is hard to extract via prediction.
- Spectre-class concerns. Value prediction adds new speculation surfaces.
What is move elimination, and how does it relate to value prediction?
Move elimination is a real shipped technique: at rename, the renamer recognizes that mov rax, rbx just creates an alias. Instead of dispatching it to an ALU, the renamer simply makes the architectural register rax point to the same physical register as rbx. Zero-latency, zero-FU.
Same idea for zero-idioms: xor rax,rax always produces 0, so the renamer points rax to a "zero" physical register without any FU work. Effectively a 100%-accurate "value predictor" for trivially predictable cases. Modern Intel and AMD both do this.
How does a stride predictor work?
A predictor table indexed by instruction PC, each entry storing the most recent value and the stride (current_value - previous_value). Prediction = current_value + stride.
Excellent for loop induction variables (i++, addresses incrementing by sizeof(elem)). Not great for irregular values. The "two values + a state machine deciding when stride is reliable" version (confidence counter) is the practical one.
Hardware stride prefetchers (essentially stride predictors for load addresses) are real and shipping in every modern CPU.
What is the verification cost of value prediction?
Two mechanisms must coexist: the predictor delivers a value to consumers; the actual instruction still executes; on completion, compare predicted vs actual.
On match: nothing extra needed (the prediction was a "shortcut"; the real value was the same). On mismatch: squash the dependent chain (only the consumers, not all younger code), restore state, re-execute consumers with the correct value.
Selective squash is hard — you need to track which instructions consumed the predicted value (transitively!) to know which to squash. This is the major implementation hurdle; most proposals simplify by squashing all instructions younger than the mispredicted producer (overkill but easier).
What workloads might benefit most from value prediction?
Long-dependent-chain code with high value locality: pointer-chasing in linked structures (high address locality), cryptographic key schedules (highly regular patterns), interpreter dispatch (repeating sequences), some sparse linear algebra. These have low ILP from data flow alone but predictable values.
Workloads where value prediction would NOT help: random-access code (no value locality), branchy code (mispredictions dominate latency anyway), already-fast straight-line loops (already at IPC limit).
What's instruction reuse, and why is it safer than prediction?
Instruction reuse caches (instruction PC, source operand values, result) tuples. On a re-encounter where source values match, the cached result is used directly — bypassing execution.
Safer because there's no speculation: if the source values match, the result is provably the same (assuming the instruction is deterministic). No squash needed. The cost is the cache lookup itself, which competes with FU latency for trivial instructions; reuse only wins for instructions where the cache lookup is faster than execute (multiplies, loads, divides).
Why is this chapter still in the textbook if value prediction never shipped?
Three reasons:
- Conceptual completeness. The framework — speculation breaking dependence walls — is correct and elegant.
- Influence on shipped derivatives. Move elimination, zero-idiom recognition, stride prefetchers, MDP — all are descendants.
- It might come back. As workloads change (ML inference, JIT-heavy code), the cost-benefit may shift. Recent academic interest in selective value prediction for specific instruction classes is renewed.
Knowing the framework prepares you for whatever shipping descendant exists.
Multithreading & CMP.
When single-thread ILP runs out, the answer is more threads. This chapter covers the spectrum of multithreading (FGMT, CGMT, SMT) and the multicore plumbing that makes them work — cache coherence (MESI/MOESI), memory consistency models, and synchronization primitives.
Why multithreading at all
A modern OoO CPU spends a huge fraction of its cycles waiting on long-latency events — cache misses to DRAM (~250 cycles), branch mispredictions (~15 cycles), divides, etc. During these stalls, dozens of execution slots sit idle.
Multithreading fills idle slots with work from a different thread. Same hardware, more useful work, higher utilization. Core insight: thread-level parallelism (TLP) hides latency that ILP can't.
The three flavors
Coarse-Grained Multithreading (CGMT, "switch on event")
Switch threads only on long stalls (L2 miss, page fault). One thread runs at full speed until it stalls, then another thread takes over. Hardware: replicated PC and registers per thread; one set active at a time.
Pros: simple. Pro: minimal impact on single-thread performance. Con: thread switch has latency (drain pipeline). Con: can't hide short stalls.
Fine-Grained Multithreading (FGMT, "round-robin")
Switch threads every cycle (or every few cycles). Each cycle's instruction comes from a different thread. Hardware: replicated state per thread; thread selector at fetch.
Pros: hides all stalls including short ones. Pro: simpler pipeline (can be in-order; thread interleaving handles dependences). Con: each thread runs slower than a dedicated machine. Used by Sun Niagara (UltraSPARC T1/T2), GPU shader cores, network processors.
Simultaneous Multithreading (SMT)
In an OoO superscalar, fill multiple issue slots from multiple threads in the same cycle. Two threads might issue 4 + 2 uops in the same cycle, using 6 of 8 issue slots. Tullsen et al., 1995.
Hardware: replicated front-end per thread (PC, fetch, decode, RAT) but shared back-end (issue queue, ROB, FUs, caches). Each ROB entry tagged with thread ID for retirement.
Pros: highest utilization of OoO core. Pro: small area overhead (~5–15%). Con: threads compete for shared resources; one thread's working set can pollute the other's cache. Used by Intel Hyper-Threading, IBM POWER (4-way SMT, 8-way SMT in POWER8+), AMD Zen.
Cache coherence: MESI
Multiple cores, each with private L1. If two cores cache the same line and one writes, others must see the change. Cache coherence enforces "the system behaves as if there's a single, consistent memory."
The classic protocol: MESI (Modified, Exclusive, Shared, Invalid):
- Modified (M): this cache has the only copy; it's been written; memory is stale.
- Exclusive (E): this cache has the only copy; it's clean (matches memory).
- Shared (S): multiple caches have copies; clean.
- Invalid (I): not in this cache.
MOESI adds an Owned state — like Modified but allows other caches to have Shared copies. Owner is responsible for memory update. AMD favored MOESI; Intel's variants are essentially MESIF (with a Forward state for cache-to-cache transfer).
Snooping vs directory
Snooping: every cache watches a shared bus. On a memory transaction, all caches check their tags and respond. Works great for ~8 cores. Doesn't scale because the bus becomes a bottleneck.
Directory: a central (or distributed) directory tracks which caches hold each line. Coherence transactions are point-to-point (cache → directory → relevant caches). Scales to hundreds of cores. More complex; higher latency for the common case.
Modern many-core chips (Xeon, EPYC, Apple M-series with 10+ cores) use directory-based or hybrid protocols. Intel's "ring bus" and "mesh" interconnects are essentially directory-flavored.
Memory consistency models
Coherence ensures per-location ordering across cores. Consistency ensures cross-location ordering — when can two cores observe different memory operations in different orders?
Sequential consistency (SC)
Strongest: every core observes all memory operations in the same total order, and that order respects program order within each core. Easy to reason about; hard to make fast (every store waits for completion).
Total Store Order (TSO) — x86
Stores can be buffered (a younger load can complete before an older store from the same thread is visible). All cores observe the same store order, but loads can pass earlier stores. Closer to SC than relaxed; allows store-buffer optimizations.
Relaxed (ARM, RISC-V, POWER)
Loads and stores can be reordered freely as long as program-order data dependences are respected. Maximum hardware freedom. Programmers must explicitly insert memory barriers (DMB, fence) where ordering matters.
Code that works on x86 (TSO) often breaks on ARM/RISC-V (relaxed) because reorderings the relaxed model permits would never happen on x86. Java's volatile, C++'s std::atomic with explicit ordering, and Linux kernel barriers exist to bridge this gap. Anyone doing concurrent code without acquire/release barriers is gambling.
Atomic primitives
Two main flavors:
- CAS (Compare-And-Swap): atomic "if memory is X, set it to Y." x86
cmpxchg, ARM CASA. Simple but suffers from ABA problem. - LL/SC (Load-Linked / Store-Conditional): two-instruction primitive. LL marks an address; SC succeeds only if no other core wrote that address since the LL. ARM LDREX/STREX, RISC-V LR/SC, classical PowerPC.
LL/SC is more flexible (any operation between LL and SC). CAS is simpler. Both implement higher-level primitives (mutex, semaphore, lock-free queues).
big.LITTLE / heterogeneous multicore
Modern SoCs (Apple M-series, Intel 12th-gen+, ARM big.LITTLE) include multiple core types:
- Performance cores: wide OoO, high frequency. Run latency-critical work.
- Efficient cores: narrower OoO or in-order, lower frequency. Run background work, parallelizable throughput tasks.
OS schedulers (macOS, Windows 11 ThreadDirector, Linux EAS) decide which work goes where based on power state and predicted demand. The model: latency-sensitive code on P-cores, throughput-sensitive code on E-cores. Saves significant power; complicates scheduling.
Interview Q&A
Chapter 11 · 14 questionsDifference between FGMT, CGMT, and SMT?
CGMT: switch threads on long stalls (e.g., L2 miss). Simple, hides only big stalls. FGMT: switch every cycle. Hides all stalls, but each thread runs slower; usually paired with in-order pipelines (Niagara, GPUs). SMT: issue from multiple threads simultaneously in an OoO core. Best utilization of wide OoO; small area overhead. Used by Intel HT, IBM POWER, AMD Zen.
Spectrum from coarsest (CGMT) to finest (SMT). Modern high-perf desktops/servers favor SMT because it composes naturally with already-deep OoO.
What's the area cost of adding 2-way SMT to an OoO core?
Roughly 5–15% of core area, depending on what's replicated. You must duplicate per-thread state: PC, ITLB, RAT (rename map), branch predictor history (sometimes shared with thread-tagged tables), and architectural register file (or just PRF entries reserved per thread).
Shared: issue queue, ROB, FUs, L1 caches (with thread tag bits per line), DTLB. The big win: same FUs serve both threads, so issue-port utilization rises substantially without doubling silicon.
Walk through MESI on a write to a shared line.
Initial: line is in S in caches A and B (both clean). A wants to write.
- A issues a "bus upgrade" or "RFO" (read-for-ownership) on the bus.
- B's cache snoops; sees the request for a line it has in S; transitions S → I (invalidate).
- A's cache transitions S → M; performs the write locally.
- Memory is now stale (only A has the current value); A's line is in M.
- If C later reads that line, it issues a bus read; A snoops, supplies the data, transitions M → S; C goes I → S; memory may also be updated (write-back depends on protocol).
What problem does the Owned (O) state solve in MOESI?
In MESI, when a Modified line is read by another cache, the protocol must write back to memory and transition to S. That extra writeback is wasted work if the line is read again before being evicted.
MOESI's Owned state lets the original M-state cache transition to O instead of writing back: the line stays dirty in the O cache; other caches go to S; the O cache "owns" the responsibility of writing back on eviction. Saves a memory transaction. AMD historically favored MOESI for this reason.
Compare snooping vs directory cache coherence.
Snooping: shared broadcast medium; all caches see all transactions. Latency is fast for the common case (no extra hop). Doesn't scale past ~16 cores because bus bandwidth saturates.
Directory: central or distributed directory tracks which caches share each line. Each transaction is point-to-point. Scales to hundreds of cores. Adds a hop (directory lookup) on the common case. Modern many-core chips (Xeon mesh, EPYC Infinity Fabric, large-core Apple M3 Max/Pro) use directory or hybrid.
Define sequential consistency, TSO, and weak (relaxed) consistency.
Sequential consistency (SC): all memory ops appear in a single total order respecting per-thread program order. Strongest, simplest to reason about, slowest in hardware.
TSO (x86): stores may be buffered; younger loads can complete before older same-thread stores are globally visible. Otherwise SC. Allows store-buffer optimizations.
Weak (ARM, RISC-V, POWER): nearly any reordering is allowed except where program-order data dependences exist. Hardware has maximum freedom; software must use barriers (DMB, fence) for ordering.
Why is x86's TSO easier to program than ARM's relaxed model?
On x86, two stores from one thread are observed in program order by all other threads (no store-store reordering across cores). Locks built with two stores work as expected.
On ARM, two stores can be observed in different orders by different cores unless an explicit DMB (data memory barrier) intervenes. Many naive lock implementations break. Compiler atomic intrinsics (std::atomic) emit the right barriers; hand-rolled assembly often doesn't.
This is why porting concurrent code from x86 to ARM frequently surfaces latent bugs that "always worked" on Intel.
What's the ABA problem in CAS?
Thread T1 reads value A from a memory location and prepares to CAS A→B. Meanwhile T2 changes A→C→A (intermediate value irrelevant). T1's CAS succeeds because it sees A — but the world changed in between.
For lock-free stack/queue using pointers, this is catastrophic: the pointer might be reused for a new node and T1's CAS unwittingly links the stale node into the structure.
Mitigations: tagged pointers (encode an ABA counter alongside the pointer), hazard pointers, epoch-based reclamation, or use LL/SC instead (which detects intervening writes natively).
Compare CAS and LL/SC.
CAS: single instruction "if mem == expected, set mem = new." Simple, but vulnerable to ABA.
LL/SC: LL reads and marks the address; SC succeeds only if no other write to that address has happened since LL. Allows arbitrary computation between LL and SC. ABA-free because SC fails on any intervening write.
Both implement primitives like atomic increment, compare-and-set, and fetch-and-add. ARM, RISC-V use LL/SC. x86 uses CAS (cmpxchg). C++11 atomics abstract over both.
What's false sharing, and why does it kill multithreaded performance?
Two cores writing different variables that happen to live in the same cache line. Each write causes coherence ping-pong: line moves from M in core A → I in A, M in B → I in B, M in A — back and forth, every iteration.
Even though the threads aren't logically sharing data, the cache line is shared. Performance can drop 10–100× compared to having the variables in separate cache lines. Common culprit: per-thread counters laid out as a packed array.
Fix: pad data structures to 64-byte boundaries (cache line size). C++17 has alignas(std::hardware_destructive_interference_size); Linux kernel uses ____cacheline_aligned.
Explain a memory barrier (fence) and when one is needed.
A memory barrier orders memory operations: instructions before the barrier must complete (be globally visible) before instructions after the barrier execute or become visible.
Needed whenever the algorithm relies on ordering that the consistency model doesn't guarantee. Classic case: producer-consumer flag pattern. Producer: write data, set flag. Consumer: spin on flag, read data. On a relaxed-memory ISA, without a barrier between data-write and flag-set, the consumer can see flag set before data — broken. ARM needs DMB; x86 needs MFENCE (or implicit ordering from locked instructions).
How does Apple's M-series handle heterogeneous cores?
M-series chips have P-cores (performance — wide OoO ~8-wide, ~3.5 GHz, large ROB) and E-cores (efficiency — narrower OoO, ~2 GHz, smaller ROB). Both implement the same ISA (ARMv8) so binaries run on either.
macOS scheduler classifies threads by Quality-of-Service (QoS) and dispatches accordingly. Latency-sensitive UI threads → P-cores. Background indexing → E-cores. The cores share unified memory and a coherent fabric. The whole thing yields excellent perf/Watt because most workloads have a mix of latency- and throughput-sensitive work.
Why does Intel call its SMT "Hyper-Threading"?
Marketing. Hyper-Threading is just Intel's brand for 2-way SMT. The first Intel HT was Pentium 4 (2002); abandoned briefly during NetBurst era; returned in Nehalem (2008) and has shipped continuously since.
Some recent client chips (Lunar Lake P-cores) have removed HT — Intel found that for client workloads, the area saved by removing SMT is better used on the OoO engine itself. Server chips (Xeon) keep HT because server workloads (high-thread-count, latency-tolerant) benefit more.
What's the cost trade-off of SMT vs adding more cores?
SMT adds a second thread context to an existing core for ~10% area; gets ~20–30% throughput improvement on parallel workloads. Excellent ratio.
Adding a second physical core: ~100% area; ~80–95% throughput improvement (some shared resources scale less than linearly). Lower ratio per area, but each thread runs at full single-thread speed.
SMT wins on workloads that benefit from thread-level parallelism but don't need full performance per thread (servers, batch processing). Multi-core wins for latency-sensitive parallel workloads. Modern chips do both. The interesting design choice in 2025 is whether to add SMT to a P-core or just add another E-core — Intel and AMD now disagree on this.
Interview Cheat Sheet.
The condensed reference. Print it, fold it, take it. If you can explain everything on this page in your own words, you can pass nearly any computer-architecture interview round.
Performance equation
- Reduce IC: better ISA, better compiler, fused ops
- Reduce CPI: pipelining, OoO, prediction, caching
- Reduce period: deeper pipeline, smaller process, better circuits
- They interact — deeper pipe raises freq but increases CPI per mispredict
Pipeline (5-stage MIPS)
Hazards
| Type | Cause | Fix |
|---|---|---|
| Structural | Resource contention | Duplicate (Harvard cache) |
| Data: RAW | True dep — read-after-write | Forwarding; stall (load-use) |
| Data: WAR | Anti — write after read | Renaming (only OoO) |
| Data: WAW | Output — same dest | Renaming (only OoO) |
| Control | Branches | Prediction; delayed slot |
Cache fundamentals
- 3 C's: Compulsory, Capacity, Conflict (+Coherence in MP)
- Direct-mapped: 1 way; cheap; conflict-prone
- Set-associative: N ways; sweet spot (modern: L1 8-way, L2 16-way, L3 16-32 way)
- Fully associative: small structures (TLB, victim caches)
- Write-back + write-allocate: standard for L1/L2/L3
- VIPT: L1 trick; index bits within page offset → translate in parallel
Virtual memory
TLB miss = 4–5 memory accesses (x86) ≈ very expensive. Huge pages (2 MB / 1 GB) reduce TLB pressure dramatically.
Branch prediction
| Predictor | Accuracy | Used in |
|---|---|---|
| Always not-taken | ~50% | None modern |
| 1-bit BHT | ~80% | Early simple cores |
| 2-bit BHT | ~85% | i486-era |
| gshare | ~92–95% | Pentium III, early Athlon |
| Tournament | ~95–96% | Alpha 21264 |
| TAGE / perceptron | ~97–98%+ | Modern Intel/AMD/Apple |
Out-of-order pipeline
Renaming (modern unified PRF)
- RAT maps architectural reg → physical reg (PRF index)
- Each write allocates a fresh PRF entry; old mapping kept until retire
- Eliminates WAR and WAW; RAW remains (it's a real dep)
- Modern PRF size: 200–500+ entries
Reservation station / Issue queue
- Holds dispatched but not-yet-issued instructions
- Each entry: opcode, src tags or values, dest tag, ready bits
- Wakeup: CAM compares broadcast result tag against waiting entries
- Select: picks N ready entries per cycle to dispatch to FUs
- Wakeup-select is the tightest critical path in modern OoO design
ROB (Reorder Buffer)
- Tracks all in-flight instructions in program order
- Allocated at dispatch, freed at retire
- Enables: precise exceptions, mispredict squash, in-order retirement
- Modern sizes: 200 (older) to 600+ (Apple M3, Intel Lunar Lake)
Memory ordering
| Model | ISAs | Reorderings allowed |
|---|---|---|
| Sequential consistency | (theoretical) | None |
| TSO | x86, SPARC | Load before older store (same thread) |
| Relaxed | ARM, RISC-V, POWER | Most reorderings except prog-order data deps |
Cache coherence: MESI
Multithreading flavors
| Type | Switch | Pipeline | Example |
|---|---|---|---|
| CGMT | On stall | OoO | (rare) |
| FGMT | Every cycle | In-order | Sun Niagara, GPU |
| SMT | Every cycle (interleaved) | OoO | Intel HT, IBM POWER, AMD Zen |
Modern CPU benchmarks (2024–2025)
| CPU | Width | ROB | Clock |
|---|---|---|---|
| Apple M3 P-core | ~8 | ~600 | ~4.0 GHz |
| Intel Lunar Lake P-core | ~6 | ~576 | ~5.1 GHz |
| AMD Zen 5 | ~6 | 448 | ~5.7 GHz |
| IBM POWER10 | ~8 | large | ~3.7 GHz |
The walls (why multicore exists)
- Power wall: P ∝ V²f; ~3–5 GHz cap on air-cooled chips
- ILP wall: sustained ~4–7 ILP in real code, less past 6-wide
- Memory wall: DRAM didn't keep up with CPU; latency in cycles grows
Solution: TLP + multicore + heterogeneous cores + bigger caches + hardware accelerators.
Rapid-fire Q&A.
35 short, sharp questions for the night before. If you can answer in 30 seconds without faltering, you're ready.
The 35
Whole book · 35 questions1. Define ISA in one sentence.
The software-visible contract — instruction formats, registers, addressing modes, memory model — that hardware must implement.
2. State the performance equation.
CPU Time = Instruction Count × CPI × Clock Period.
3. Three reasons a deep pipeline can hurt performance.
Higher branch mispredict penalty; more clock-skew/register overhead per stage; lower IPC because of forwarding gaps and structural conflicts in deeper microarchitecture.
4. RAW, WAR, WAW — which exists in an in-order pipeline?
Only RAW. WAR and WAW are name dependences that emerge with out-of-order execution.
5. What is forwarding?
Routing the output of an in-flight instruction directly to the input of a younger one, bypassing the register file.
6. Why does load-use hazard need a stall even with forwarding?
The load's value is available only at end of MEM, but the consumer needs it at start of EX one cycle earlier. Forwarding can't time-travel.
7. What is AMAT?
Average Memory Access Time = hit_time + miss_rate × miss_penalty. Recursive across cache levels.
8. Difference between direct-mapped and 4-way set associative?
Direct-mapped: each block has exactly one slot. 4-way: each block has 4 candidate slots in its set; less conflict miss.
9. What's a TLB?
A small fast cache of virtual-to-physical address translations. Hit: 1 cycle. Miss: page-table walk.
10. What is VIPT and why use it?
Virtually Indexed, Physically Tagged. Allows TLB lookup and cache index lookup in parallel — saves one cycle on L1 hits.
11. Why do superscalar CPUs need register renaming?
To eliminate WAR and WAW false dependences caused by limited architectural register count.
12. What does a ROB do?
Tracks in-flight instructions in program order. Enables in-order retirement, precise exceptions, and speculation rollback.
13. What's a reservation station / issue queue?
A buffer holding dispatched-but-not-yet-issued instructions, waiting for their operands. Wakeup via tag broadcast on result bus.
14. Tomasulo in one sentence.
Out-of-order execution with reservation stations, common data bus broadcast, and tag-based renaming.
15. What's a 2-bit saturating counter?
4 states (Strong NT, Weak NT, Weak T, Strong T). Predict T if MSB=1. Update by ±1 on actual outcome (saturating).
16. What is gshare?
XOR global branch history with branch PC, use as PHT index. Captures cross-branch correlation cheaply.
17. Why is TAGE state-of-the-art?
Multiple tagged tables with geometrically-increasing history lengths; picks the longest matching history per branch dynamically.
18. What's a BTB?
Branch Target Buffer. Caches taken-branch targets, indexed by branch PC, so fetch can redirect immediately without waiting for decode.
19. What's a Return Address Stack?
A small hardware stack mirroring software call/return — push on call, pop on return. Returns predict ~99%.
20. Why did Pentium 4's trace cache fail?
Cold-start penalty, capacity stress, and traces invalidated by mispredictions. Replaced by the simpler uop cache.
21. What's a uop?
A simple, fixed-format internal operation that x86 instructions decode into. Lets the OoO engine schedule a clean RISC-like stream regardless of x86 complexity.
22. What was 4-1-1 decode in P6?
Per cycle: one complex decoder produces up to 4 uops, two simple decoders produce 1 uop each. Sequence "complex, complex, simple" stalls because only one complex decoder exists.
23. Speed Demons vs Brainiacs?
Speed demons go for max clock with deep simple pipelines (P4, Alpha 21164). Brainiacs go for max IPC with wide complex OoO (P6, modern Apple/Intel/AMD). Brainiacs won.
24. What did x86-64 add architecturally?
64-bit addresses, doubled GPR count (8→16), doubled XMM (8→16), NX bit. Register doubling alone bought ~10–20% IPC.
25. What's the data flow limit?
The minimum cycles needed for the longest chain of true dependences. Even infinite hardware can't beat it without speculation.
26. What is value locality?
The empirical observation that instruction results recur frequently. Loads, induction variables, etc. exhibit this pattern.
27. Why didn't general-purpose value prediction ship?
Verification overhead and squash recovery cost more than the speedup. Move elimination and prefetchers shipped instead.
28. SMT vs FGMT vs CGMT?
SMT: simultaneous, multi-thread issue per cycle in OoO core. FGMT: round-robin every cycle (in-order). CGMT: switch on stalls.
29. MESI states?
Modified (own copy, dirty), Exclusive (own copy, clean), Shared (multiple copies, clean), Invalid (not present).
30. What's the difference between snooping and directory coherence?
Snooping: shared bus, all caches see all transactions. Directory: central tracker, point-to-point coherence messages. Directory scales further.
31. x86 TSO vs ARM relaxed — practical difference?
x86 preserves nearly all store-store and store-load orderings without barriers. ARM allows nearly all reorderings; programmers must use DMB or atomics with explicit ordering.
32. CAS vs LL/SC?
CAS: single instruction, vulnerable to ABA. LL/SC: pair of instructions; SC fails on intervening write, ABA-immune.
33. What's false sharing?
Two cores writing different variables in the same cache line, causing coherence ping-pong despite no logical sharing. Fix: pad to cache-line boundaries.
34. Why does Spectre work?
Speculative loads alter cache state before the speculation is squashed. Architectural state is rolled back; cache state isn't. Timing-based side channel leaks the secret.
35. Three walls that drove multicore?
Power wall (frequency-scaling power becomes infeasible), ILP wall (sustained single-thread parallelism caps at ~4–7), memory wall (DRAM latency in cycles keeps growing).