CPU + Assembly Internals: A Deep-Dive Tutorial
📘 The Complete Tutorial
Introduction: The CPU Is the Brain
Every program you write—whether in Python, C, or Java—ultimately becomes a stream of machine instructions that the CPU executes. Understanding the CPU's internals is like understanding the engine of a car: you can drive without knowing, but you can't optimize, debug, or build high-performance systems without it.
This tutorial takes you from transistors to threads.
Module 01: CPU Architecture
A modern CPU is a complex system-on-chip, but at its core it contains:
| Component | Function |
|---|---|
| Registers | Ultra-fast storage (64 bytes total on x86-64) |
| ALU | Arithmetic and logic operations |
| Control Unit | Fetches, decodes, and schedules instructions |
| Cache | L1/L2/L3 memory hierarchy |
| Branch Predictor | Guesses which way if statements go |
| Pipeline | Overlaps instruction execution |
| MMU | Memory Management Unit (virtual → physical) |
Von Neumann Architecture: Code and data share the same memory bus. Harvard Architecture (used in some embedded systems) separates them.
Module 02: Registers
x86-64 has 16 general-purpose 64-bit registers:
| Register | Purpose | Callee-Saved? |
|---|---|---|
%rax | Return value, accumulator | No |
%rbx | General purpose | Yes |
%rcx | 4th argument, loop counter | No |
%rdx | 3rd argument, I/O | No |
%rsi | 2nd argument, source index | No |
%rdi | 1st argument, destination index | No |
%rbp | Base pointer (frame) | Yes |
%rsp | Stack pointer | Yes |
%r8-%r15 | Additional arguments | r12-r15: Yes |
Special registers:
%rip— Instruction Pointer (next instruction to execute)%rflags— Status flags (zero, carry, sign, overflow)
Module 03: Instruction Pointer (RIP)
%rip holds the address of the next instruction to execute. It advances automatically:
1mov $42, %eax # RIP advances past this instruction 2add $1, %eax # RIP advances again
Control flow changes RIP directly:
1jmp label # RIP = address of label 2call func # RIP = func, push return address 3ret # RIP = popped return address
Module 04: Stack Pointer (RSP)
%rsp points to the top of the stack (lowest address, since the stack grows down):
1push %rax # RSP -= 8; [RSP] = RAX 2pop %rax # RAX = [RSP]; RSP += 8
Stack frame setup (prologue):
1push %rbp # Save old base pointer 2mov %rsp, %rbp # Set new base pointer 3sub $32, %rsp # Allocate 32 bytes for locals
Teardown (epilogue):
1mov %rbp, %rsp # Restore stack pointer 2pop %rbp # Restore base pointer 3ret # Return to caller
Module 05: Flags Register (RFLAGS)
The flags register contains boolean status bits:
| Flag | Name | Set When... |
|---|---|---|
| ZF | Zero Flag | Result is zero |
| CF | Carry Flag | Unsigned overflow |
| SF | Sign Flag | Result is negative |
| OF | Overflow Flag | Signed overflow |
| PF | Parity Flag | Even number of 1 bits |
| IF | Interrupt Flag | Interrupts enabled |
Usage:
1cmp %rax, %rbx # Compare: subtract RAX from RBX, set flags 2je equal # Jump if ZF=1 (equal) 3jg greater # Jump if SF=OF and ZF=0 (signed greater)
Module 06: Arithmetic Logic Unit (ALU)
The ALU performs all arithmetic and logical operations:
- Arithmetic:
add,sub,mul,div,inc,dec - Logical:
and,or,xor,not,shl,shr,sar - Comparison:
cmp(subtraction without storing result)
Example: Bit manipulation
1mov $0b1010, %al 2and $0b1100, %al # AL = 0b1000 (bitwise AND) 3xor %eax, %eax # EAX = 0 (faster than mov $0, %eax)
Module 07: Control Unit
The Control Unit (CU) is the CPU's conductor:
- Fetch: Read instruction from memory (via cache) into instruction register
- Decode: Determine operation and operands
- Execute: Send commands to ALU, FPU, or memory unit
- Write-back: Store results in registers or memory
Modern CPUs have a front-end (fetch/decode) and back-end (execute/write-back) separated by a reorder buffer.
Module 08: CPU Cache
Cache is fast, small memory close to the CPU core. Without cache, every memory access would take 100+ cycles (RAM latency).
Principle of Locality:
- Temporal: Recently used data is likely used again
- Spatial: Nearby data is likely used next
Module 09: L1 / L2 / L3 Cache Hierarchy
| Level | Size | Latency | Shared? | Location |
|---|---|---|---|---|
| L1d | 32-64 KB | ~4 cycles | Per core | Data |
| L1i | 32-64 KB | ~4 cycles | Per core | Instructions |
| L2 | 256-512 KB | ~12 cycles | Per core | Unified |
| L3 | 8-64 MB | ~40 cycles | Shared | Unified |
Cache line: 64 bytes (the minimum transfer unit). If you access one byte, the CPU fetches the entire 64-byte line.
Module 10: Branch Prediction
CPUs execute instructions speculatively. When they hit a branch (if, loop, switch), they guess the outcome:
| Predictor | Accuracy | Description |
|---|---|---|
| Static | ~65% | Always predict not-taken |
| 1-bit | ~80% | Remember last outcome |
| 2-bit saturating | ~90% | Need 2 wrong predictions to flip |
| Tournament/GShare | ~95%+ | Hybrid, history-based |
Misprediction penalty: 15-20 cycles (pipeline flush).
Optimization tip: Make the common case the fall-through (not-taken) branch.
Module 11: Instruction Pipeline
A pipeline overlaps instruction execution:
Cycle: 1 2 3 4 5 6 7
Inst1: F | D | E | W |
Inst2: F | D | E | W |
Inst3: F | D | E | W |
Stages: Fetch → Decode → Execute → Memory → Writeback
Hazards:
- Structural: Two instructions need the same unit
- Data: Instruction needs result of previous instruction
- Control: Branch changes which instruction comes next
Module 12: Out-of-Order Execution (OoOE)
Modern CPUs don't execute instructions in program order. They:
- Fetch instructions in order
- Decode into micro-operations (µops)
- Issue µops to execution ports when dependencies are ready
- Retire (commit) results in program order
Benefit: Independent instructions run in parallel, hiding latency.
Example:
1mov $10, %rax # Cycle 1 2imul $20, %rax # Cycle 2-4 (3-cycle latency) 3mov $5, %rbx # Can execute in Cycle 2 (independent!) 4add %rbx, %rcx # Can execute in Cycle 3
Module 13: SIMD (Single Instruction, Multiple Data)
SIMD processes multiple data elements with one instruction:
| Extension | Register Size | Data Types |
|---|---|---|
| SSE | 128-bit (xmm) | 4×float, 2×double, 16×byte |
| AVX | 256-bit (ymm) | 8×float, 4×double, 32×byte |
| AVX-512 | 512-bit (zmm) | 16×float, 8×double, 64×byte |
1; Add 4 floats at once 2movaps (%rsi), %xmm0 # Load 4 floats 3addps (%rdi), %xmm0 # Add 4 floats in parallel 4movaps %xmm0, (%rdx) # Store result
Auto-vectorization: Compilers (gcc -O3 -mavx2) can convert loops to SIMD automatically.
Module 14: x86-64 Assembly
x86-64 has two syntaxes: AT&T (default on Linux) and Intel (default on Windows).
AT&T syntax:
1movq $42, %rax # Source, Destination 2movl %eax, (%rbx) # Register indirect 3addq $8, %rsp # Immediate
Intel syntax:
1mov rax, 42 # Destination, Source 2mov [rbx], eax 3add rsp, 8
Key AT&T prefixes:
%before registers$before immediates- Suffixes:
b(byte),w(word),l(long),q(quad)
Module 15: Function Calling Convention
The System V AMD64 ABI (Linux/macOS) defines how functions call each other:
Argument passing (left to right):
1st: %rdi 2nd: %rsi 3rd: %rdx 4th: %rcx 5th: %r8 6th: %r9
Return value: %rax (and %rdx for 128-bit)
Caller-saved: %rax, %rcx, %rdx, %rsi, %rdi, %r8-%r11
Callee-saved: %rbx, %rbp, %r12-%r15, %rsp
Stack alignment: RSP % 16 == 0 before call (8-byte return address makes it 16-byte aligned after call).
Module 16: System-Call Instruction
System calls transition from user mode to kernel mode:
| Register | Purpose |
|---|---|
%rax | Syscall number |
%rdi | 1st argument |
%rsi | 2nd argument |
%rdx | 3rd argument |
%r10 | 4th argument |
%r8 | 5th argument |
%r9 | 6th argument |
1; sys_write(fd=1, buf=msg, count=len) 2mov $1, %rax # syscall: write 3mov $1, %rdi # fd: stdout 4lea msg, %rsi # buffer 5mov $13, %rdx # length 6syscall # Enter kernel
Common syscalls: read(0), write(1), open(2), close(3), exit(60), mmap(9).
Module 17: Interrupts
An interrupt is a signal that pauses the CPU to handle an event:
| Type | Triggered By | Example |
|---|---|---|
| Hardware | External device | Keyboard, timer, network packet |
| Software | int instruction | int 0x80 (legacy Linux syscall) |
| Exception | CPU error | Divide by zero, page fault, segfault |
Interrupt Descriptor Table (IDT): Maps interrupt numbers to handler addresses.
Process:
- CPU finishes current instruction
- Pushes flags, CS, RIP onto stack
- Loads handler address from IDT
- Executes handler
iretrestores state and resumes
Module 18: Context Switching
Context switching is how the OS multiplexes CPU time across processes/threads.
What gets saved:
- General-purpose registers
- RIP (instruction pointer)
- RSP (stack pointer)
- RFLAGS
- CR3 (page table base — virtual memory)
Process:
- Timer interrupt fires (or thread yields)
- CPU enters kernel mode
- Save current context to PCB (Process Control Block)
- Select next process (scheduler)
- Load next process's context
iretto user mode — new process runs!
Cost: 1-10 microseconds. Frequent switching hurts performance.
🛠 Complete Practice Code
Below are four complete, runnable projects. Save them, compile, and experiment.
Project 1: Assembly Calculator
A command-line calculator in x86-64 assembly using syscalls.
Project 2: Function-Call Tracer
A C program that traces its own stack frames using inline assembly.
Project 3: Context-Switch Demonstration
A user-space coroutine library demonstrating manual context switching.
Project 4: CPU-Cache Benchmark
Measures L1, L2, L3 cache and main memory latency by accessing arrays of varying sizes.
Let me generate all the code files for you:
Here is the complete tutorial for COURSE 2 — CPU + Assembly Internals, followed by all four practice projects with full source code.
CPU + Assembly Internals: Complete Tutorial
🎯 Meta Title (56 chars)
CPU & Assembly Internals: From Registers to Execution
📝 Meta Description (155 chars)
Master CPU architecture, registers, cache, pipelines, SIMD, x86-64 assembly, calling conventions, interrupts, and context switching with hands-on projects and code.
🔑 SEO Keywords
CPU architecture, x86-64 assembly, CPU cache L1 L2 L3, instruction pipeline, branch prediction, SIMD instructions, calling convention, system call, context switching, out-of-order execution, ALU control unit, assembly programming, CPU registers, interrupt handling, cache benchmark
📘 The Complete Tutorial
Introduction: The CPU Is the Brain
Every program you write—whether in Python, C, or Java—ultimately becomes a stream of machine instructions that the CPU executes. Understanding the CPU's internals is like understanding the engine of a car: you can drive without knowing, but you can't optimize, debug, or build high-performance systems without it.
This tutorial takes you from transistors to threads.
Module 01: CPU Architecture
A modern CPU is a complex system-on-chip, but at its core it contains:
| Component | Function |
|---|---|
| Registers | Ultra-fast storage (64 bytes total on x86-64) |
| ALU | Arithmetic and logic operations |
| Control Unit | Fetches, decodes, and schedules instructions |
| Cache | L1/L2/L3 memory hierarchy |
| Branch Predictor | Guesses which way if statements go |
| Pipeline | Overlaps instruction execution |
| MMU | Memory Management Unit (virtual → physical) |
Von Neumann Architecture: Code and data share the same memory bus. Harvard Architecture (used in some embedded systems) separates them.
Module 02: Registers
x86-64 has 16 general-purpose 64-bit registers:
| Register | Purpose | Callee-Saved? |
|---|---|---|
%rax | Return value, accumulator | No |
%rbx | General purpose | Yes |
%rcx | 4th argument, loop counter | No |
%rdx | 3rd argument, I/O | No |
%rsi | 2nd argument, source index | No |
%rdi | 1st argument, destination index | No |
%rbp | Base pointer (frame) | Yes |
%rsp | Stack pointer | Yes |
%r8-%r15 | Additional arguments | r12-r15: Yes |
Special registers:
%rip— Instruction Pointer (next instruction to execute)%rflags— Status flags (zero, carry, sign, overflow)
Module 03: Instruction Pointer (RIP)
%rip holds the address of the next instruction to execute. It advances automatically:
1mov $42, %eax # RIP advances past this instruction 2add $1, %eax # RIP advances again
Control flow changes RIP directly:
1jmp label # RIP = address of label 2call func # RIP = func, push return address 3ret # RIP = popped return address
Module 04: Stack Pointer (RSP)
%rsp points to the top of the stack (lowest address, since the stack grows down):
1push %rax # RSP -= 8; [RSP] = RAX 2pop %rax # RAX = [RSP]; RSP += 8
Stack frame setup (prologue):
1push %rbp # Save old base pointer 2mov %rsp, %rbp # Set new base pointer 3sub $32, %rsp # Allocate 32 bytes for locals
Teardown (epilogue):
1mov %rbp, %rsp # Restore stack pointer 2pop %rbp # Restore base pointer 3ret # Return to caller
Module 05: Flags Register (RFLAGS)
The flags register contains boolean status bits:
| Flag | Name | Set When... |
|---|---|---|
| ZF | Zero Flag | Result is zero |
| CF | Carry Flag | Unsigned overflow |
| SF | Sign Flag | Result is negative |
| OF | Overflow Flag | Signed overflow |
| PF | Parity Flag | Even number of 1 bits |
| IF | Interrupt Flag | Interrupts enabled |
Usage:
1cmp %rax, %rbx # Compare: subtract RAX from RBX, set flags 2je equal # Jump if ZF=1 (equal) 3jg greater # Jump if SF=OF and ZF=0 (signed greater)
Module 06: Arithmetic Logic Unit (ALU)
The ALU performs all arithmetic and logical operations:
- Arithmetic:
add,sub,mul,div,inc,dec - Logical:
and,or,xor,not,shl,shr,sar - Comparison:
cmp(subtraction without storing result)
Example: Bit manipulation
1mov $0b1010, %al 2and $0b1100, %al # AL = 0b1000 (bitwise AND) 3xor %eax, %eax # EAX = 0 (faster than mov $0, %eax)
Module 07: Control Unit
The Control Unit (CU) is the CPU's conductor:
- Fetch: Read instruction from memory (via cache) into instruction register
- Decode: Determine operation and operands
- Execute: Send commands to ALU, FPU, or memory unit
- Write-back: Store results in registers or memory
Modern CPUs have a front-end (fetch/decode) and back-end (execute/write-back) separated by a reorder buffer.
Module 08: CPU Cache
Cache is fast, small memory close to the CPU core. Without cache, every memory access would take 100+ cycles (RAM latency).
Principle of Locality:
- Temporal: Recently used data is likely used again
- Spatial: Nearby data is likely used next
Module 09: L1 / L2 / L3 Cache Hierarchy
| Level | Size | Latency | Shared? | Location |
|---|---|---|---|---|
| L1d | 32-64 KB | ~4 cycles | Per core | Data |
| L1i | 32-64 KB | ~4 cycles | Per core | Instructions |
| L2 | 256-512 KB | ~12 cycles | Per core | Unified |
| L3 | 8-64 MB | ~40 cycles | Shared | Unified |
Cache line: 64 bytes (the minimum transfer unit). If you access one byte, the CPU fetches the entire 64-byte line.
Module 10: Branch Prediction
CPUs execute instructions speculatively. When they hit a branch (if, loop, switch), they guess the outcome:
| Predictor | Accuracy | Description |
|---|---|---|
| Static | ~65% | Always predict not-taken |
| 1-bit | ~80% | Remember last outcome |
| 2-bit saturating | ~90% | Need 2 wrong predictions to flip |
| Tournament/GShare | ~95%+ | Hybrid, history-based |
Misprediction penalty: 15-20 cycles (pipeline flush).
Optimization tip: Make the common case the fall-through (not-taken) branch.
Module 11: Instruction Pipeline
A pipeline overlaps instruction execution:
Cycle: 1 2 3 4 5 6 7
Inst1: F | D | E | W |
Inst2: F | D | E | W |
Inst3: F | D | E | W |
Stages: Fetch → Decode → Execute → Memory → Writeback
Hazards:
- Structural: Two instructions need the same unit
- Data: Instruction needs result of previous instruction
- Control: Branch changes which instruction comes next
Module 12: Out-of-Order Execution (OoOE)
Modern CPUs don't execute instructions in program order. They:
- Fetch instructions in order
- Decode into micro-operations (µops)
- Issue µops to execution ports when dependencies are ready
- Retire (commit) results in program order
Benefit: Independent instructions run in parallel, hiding latency.
Example:
1mov $10, %rax # Cycle 1 2imul $20, %rax # Cycle 2-4 (3-cycle latency) 3mov $5, %rbx # Can execute in Cycle 2 (independent!) 4add %rbx, %rcx # Can execute in Cycle 3
Module 13: SIMD (Single Instruction, Multiple Data)
SIMD processes multiple data elements with one instruction:
| Extension | Register Size | Data Types |
|---|---|---|
| SSE | 128-bit (xmm) | 4×float, 2×double, 16×byte |
| AVX | 256-bit (ymm) | 8×float, 4×double, 32×byte |
| AVX-512 | 512-bit (zmm) | 16×float, 8×double, 64×byte |
1; Add 4 floats at once 2movaps (%rsi), %xmm0 # Load 4 floats 3addps (%rdi), %xmm0 # Add 4 floats in parallel 4movaps %xmm0, (%rdx) # Store result
Auto-vectorization: Compilers (gcc -O3 -mavx2) can convert loops to SIMD automatically.
Module 14: x86-64 Assembly
x86-64 has two syntaxes: AT&T (default on Linux) and Intel (default on Windows).
AT&T syntax:
1movq $42, %rax # Source, Destination 2movl %eax, (%rbx) # Register indirect 3addq $8, %rsp # Immediate
Intel syntax:
1mov rax, 42 # Destination, Source 2mov [rbx], eax 3add rsp, 8
Key AT&T prefixes:
%before registers$before immediates- Suffixes:
b(byte),w(word),l(long),q(quad)
Module 15: Function Calling Convention
The System V AMD64 ABI (Linux/macOS) defines how functions call each other:
Argument passing (left to right):
1st: %rdi 2nd: %rsi 3rd: %rdx 4th: %rcx 5th: %r8 6th: %r9
Return value: %rax (and %rdx for 128-bit)
Caller-saved: %rax, %rcx, %rdx, %rsi, %rdi, %r8-%r11
Callee-saved: %rbx, %rbp, %r12-%r15, %rsp
Stack alignment: RSP % 16 == 0 before call (8-byte return address makes it 16-byte aligned after call).
Module 16: System-Call Instruction
System calls transition from user mode to kernel mode:
| Register | Purpose |
|---|---|
%rax | Syscall number |
%rdi | 1st argument |
%rsi | 2nd argument |
%rdx | 3rd argument |
%r10 | 4th argument |
%r8 | 5th argument |
%r9 | 6th argument |
1; sys_write(fd=1, buf=msg, count=len) 2mov $1, %rax # syscall: write 3mov $1, %rdi # fd: stdout 4lea msg, %rsi # buffer 5mov $13, %rdx # length 6syscall # Enter kernel
Common syscalls: read(0), write(1), open(2), close(3), exit(60), mmap(9).
Module 17: Interrupts
An interrupt is a signal that pauses the CPU to handle an event:
| Type | Triggered By | Example |
|---|---|---|
| Hardware | External device | Keyboard, timer, network packet |
| Software | int instruction | int 0x80 (legacy Linux syscall) |
| Exception | CPU error | Divide by zero, page fault, segfault |
Interrupt Descriptor Table (IDT): Maps interrupt numbers to handler addresses.
Process:
- CPU finishes current instruction
- Pushes flags, CS, RIP onto stack
- Loads handler address from IDT
- Executes handler
iretrestores state and resumes
Module 18: Context Switching
Context switching is how the OS multiplexes CPU time across processes/threads.
What gets saved:
- General-purpose registers
- RIP (instruction pointer)
- RSP (stack pointer)
- RFLAGS
- CR3 (page table base — virtual memory)
Process:
- Timer interrupt fires (or thread yields)
- CPU enters kernel mode
- Save current context to PCB (Process Control Block)
- Select next process (scheduler)
- Load next process's context
iretto user mode — new process runs!
Cost: 1-10 microseconds. Frequent switching hurts performance.
🛠 Complete Practice Code
Below are four complete, runnable projects. Save them, compile, and experiment.
Project 1: Assembly Calculator
A command-line calculator in x86-64 assembly using Linux syscalls (read, write, exit). No libc required.
Build: gcc -no-pie -nostartfiles -o calculator calculator.s
What it demonstrates:
- System call interface (
syscallinstruction) - String I/O without libc
- ASCII ↔ integer conversion
- Basic arithmetic in assembly
Download: calculator.s
Project 2: Function-Call Tracer
A C program that traces its own stack frames by reading RBP and walking the linked list of frames using inline assembly.
Build: gcc -no-pie -O0 -o func_tracer func_tracer.c
What it demonstrates:
- Reading CPU registers (
RBP,RSP,RIP) via inline assembly - Stack frame layout:
[RBP] = saved RBP,[RBP+8] = return address - Calling convention (
RDI,RSI,RDX,RCXargument registers) - Nested function call visualization
Download: func_tracer.c
Project 3: Context-Switch Demonstration
A user-space coroutine library showing cooperative multitasking between two threads via manual context switching.
Build: gcc -O2 -o context_switch context_switch.c
What it demonstrates:
- Manual register save/restore
- Stack allocation and switching
setjmp/longjmpfor capturing/restoring execution context- Cooperative yielding between threads
Download: context_switch.c
Project 4: CPU-Cache Benchmark
Measures cache latency and bandwidth across the memory hierarchy (L1 → L2 → L3 → RAM) using pointer-chasing and sequential access patterns.
Build: gcc -O2 -o cache_benchmark cache_benchmark.c -lm
What it demonstrates:
- Pointer-chasing to defeat hardware prefetchers
- Random vs. sequential access patterns
- Latency detection for L1/L2/L3/RAM
- Bandwidth measurement at each cache level
- Stride analysis (cache line effects)
Download: cache_benchmark.c
Quick Start: Makefile & README
Download the Makefile and README.md to build and run all projects:
1make all 2./calculator 3./func_tracer 4./context_switch 5./cache_benchmark
🎓 Summary: The CPU Mindset
| Concept | Key Takeaway |
|---|---|
| Registers | The CPU's workspace — 16 general-purpose, ultra-fast |
| RIP | The program counter — changes on every branch |
| RSP/RBP | Stack management — frames linked via saved RBP |
| Flags | Condition codes drive all control flow |
| ALU | All math and logic happens here |
| Cache | L1 (~4ns) → L2 (~12ns) → L3 (~40ns) → RAM (~100ns) |
| Branch Prediction | Wrong guesses cost 15-20 cycles |
| Pipeline | Overlapped execution — stalls hurt |
| OoOE | Independent instructions run in parallel |
| SIMD | Process 4-16 data elements per instruction |
| Calling Convention | ABI contract — who saves what, where args go |
| Syscalls | User→kernel gateway via syscall instruction |
| Interrupts | External events pause and redirect execution |
| Context Switch | Save all state, load new state, resume — costs µs |
"The CPU doesn't execute your code — it executes the machine instructions your code became. Understanding that translation is what separates programmers from systems engineers."
Master the CPU, and you master performance.