Module 1: Number Systems and Data Representation
How bits encode numbers, characters, and real values, and how to convert among binary, decimal, and hexadecimal.
Bits, Bytes, and Positional Notation
- Explain why digital computers use binary.
- Read any positional number system using place values.
- Define bit, byte, and the common size prefixes.
Every photo, song, message, and program inside a computer is ultimately a pattern of bits. A bit (binary digit) is the smallest unit of information: a single value that is either 0 or 1. Computers use two values rather than ten because it is easy and reliable to build hardware with two clearly distinct states, such as a low or high voltage on a wire. A component only has to tell "off" from "on," not ten shades in between, so binary circuits are cheap, fast, and resistant to noise.
Positional notation, the idea behind every base
The decimal system you grew up with is a positional system in base 10: each digit's value depends on its place, and each place is worth ten times the one to its right. The number 3725 means 3 thousands, 7 hundreds, 2 tens, and 5 ones, because the places are powers of ten: 10^3, 10^2, 10^1, 10^0. Binary works exactly the same way, except each place is a power of two.
In base 2, the place values from the right are 1, 2, 4, 8, 16, 32, 64, 128, and so on. To read a binary number, add up the place values wherever there is a 1. Consider the byte 10110100:
| Place value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|---|
| Bit | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
Adding the place values where a 1 appears: 128 + 32 + 16 + 4 = 180. So the binary pattern 10110100 represents the decimal number 180.
Bytes and size prefixes
Bits are grouped into bytes of 8 bits each, the standard unit of storage. One byte can hold 2^8 = 256 different patterns, from 00000000 to 11111111 (0 to 255 as an unsigned number). Larger amounts of memory use prefixes. In the binary sense used for memory, a kilobyte is 2^10 = 1024 bytes, a megabyte is 1024 kilobytes, and a gigabyte is 1024 megabytes. The leftmost bit of a group is called the most significant bit because it carries the largest place value, and the rightmost is the least significant bit.
The key habit to build this module is fluency: given any binary pattern you should be able to find its value by summing place values, and given a number you should be able to find its bits. Everything else in the course rests on this skill.
- Key terms
- Bit
- The smallest unit of information, holding a single 0 or 1.
- Byte
- A group of 8 bits, the standard unit of storage; it holds 256 patterns.
- Positional notation
- A number system where a digit's value depends on its position (place value).
- Base (radix)
- The number of distinct digits a system uses; base 2 uses 0 and 1, base 10 uses 0 through 9.
- Most significant bit
- The leftmost bit, carrying the largest place value.
- Least significant bit
- The rightmost bit, carrying the smallest place value (the ones place).
Converting Between Decimal, Binary, and Hexadecimal
- Convert decimal to binary using repeated division or place values.
- Convert binary to hexadecimal by grouping into nibbles.
- Read and write hexadecimal fluently.
Working with computers means moving between number bases constantly, so this lesson drills the three conversions you will use most: decimal to binary, binary to decimal, and binary to hexadecimal.
Decimal to binary by repeated division
To convert a decimal number to binary, repeatedly divide by 2 and record the remainders. The remainders, read from bottom to top, are the binary digits. Convert 156:
| Division | Quotient | Remainder |
|---|---|---|
| 156 / 2 | 78 | 0 |
| 78 / 2 | 39 | 0 |
| 39 / 2 | 19 | 1 |
| 19 / 2 | 9 | 1 |
| 9 / 2 | 4 | 1 |
| 4 / 2 | 2 | 0 |
| 2 / 2 | 1 | 0 |
| 1 / 2 | 0 | 1 |
Reading the remainders from bottom to top gives 10011100. Check by summing place values: 128 + 16 + 8 + 4 = 156. Correct.
Hexadecimal: base 16
Hexadecimal (base 16) is the programmer's shorthand for binary. It uses sixteen digits: 0 through 9, then the letters A, B, C, D, E, F for the values 10 through 15. Hex is popular because each hex digit maps to exactly four bits, called a nibble. That makes long binary strings short and readable. Hex numbers are usually written with a 0x prefix, as in 0x3F.
| Hex | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Binary | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
Binary to hex by grouping nibbles
To convert binary to hex, split the bits into groups of four starting from the right, then translate each group with the table above. Convert 10011100: group as 1001 1100. The nibble 1001 is 9 and 1100 is C, so 10011100 = 0x9C. To go the other way, expand each hex digit into its four bits. This lesson's skill, moving among all three bases, is the daily vocabulary of systems work.
- Key terms
- Hexadecimal
- Base 16, using digits 0 through 9 and letters A through F for values 10 through 15.
- Nibble
- A group of 4 bits, which is exactly one hexadecimal digit.
- Repeated division method
- Converting decimal to another base by dividing repeatedly and reading the remainders in reverse.
- 0x prefix
- A conventional marker written before a number to signal it is in hexadecimal.
- Radix conversion
- Rewriting the same numeric value in a different base.
- Grouping
- Splitting a binary string into nibbles to translate it directly to hex.
Signed Integers and Two's Complement
- Explain the range problem that signed representation solves.
- Compute a two's complement negative by inverting and adding one.
- Add signed binary numbers and recognize overflow.
An unsigned byte holds 0 to 255, but real programs need negative numbers too. The dominant scheme for signed integers is two's complement, used by virtually every modern CPU because it lets the same addition circuit handle both positive and negative numbers with no special cases.
The idea
In an 8-bit two's complement number, the leftmost bit is the sign bit: 0 means non-negative, 1 means negative. But it is more than a flag - the top bit carries a negative place value. For 8 bits the place values are -128, 64, 32, 16, 8, 4, 2, 1. To read a two's complement number, sum the place values as usual but treat the top place as negative. The pattern 11111111 is -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1. This gives an 8-bit range of -128 to +127.
Negating a number: invert and add one
To find the representation of a negative number, take the positive value in binary, flip every bit (the ones complement), then add 1. Find -20 in 8 bits:
- Write +20:
00010100 - Invert every bit:
11101011 - Add 1:
11101100
So -20 is 11101100. Verify by reading place values: -128 + 64 + 32 + 8 + 4 = -20. Correct. The same invert-and-add-one trick converts a negative back to its positive magnitude, which makes the scheme pleasantly symmetric.
Addition and overflow
The payoff is that signed addition is just ordinary binary addition, ignoring any carry out of the top bit. Add 5 and -3 in 8 bits. Here 5 is 00000101 and -3 is 11111101:
00000101 (5)
+ 11111101 (-3)
-----------
00000010 (2, after dropping the carry out of bit 8)
The result 00000010 is +2, exactly right. Overflow happens when the true result will not fit in the available bits - for example, adding two large positives and getting a negative-looking answer. In 8-bit two's complement, adding 100 and 50 gives 150, which exceeds the +127 limit and wraps to a negative pattern. Detecting this is why hardware tracks an overflow flag, covered later when we build the ALU.
- Key terms
- Two's complement
- The standard signed-integer scheme where the top bit carries a negative place value.
- Sign bit
- The most significant bit, which is 1 for negative numbers in two's complement.
- Ones complement
- The result of inverting every bit of a number.
- Invert and add one
- The procedure to negate a two's complement number: flip all bits, then add 1.
- Overflow
- When an arithmetic result is too large or too small to fit in the available bits.
- Signed range
- For n bits of two's complement, the values from -2^(n-1) to 2^(n-1) - 1.
Representing Text and Real Numbers
- Explain how characters map to numbers with ASCII and Unicode.
- Describe the parts of a floating-point number.
- Explain why some decimals cannot be stored exactly.
Bits do not only stand for whole numbers. With an agreed-upon encoding, the same patterns can represent letters, emoji, and fractional values. The rule is simple: a bit pattern means whatever we all agree it means.
Characters: ASCII and Unicode
ASCII (American Standard Code for Information Interchange) is an early standard that assigns each of 128 common characters a 7-bit number. The capital letter A is 65 (01000001), the digits 0 to 9 are 48 to 57, and a space is 32. A helpful pattern: lowercase letters sit exactly 32 above their uppercase versions, so a is 97. ASCII covers English but not the world's scripts, so modern systems use Unicode, which assigns a number (a code point) to more than a hundred thousand characters across every writing system, plus emoji. The most common way to store Unicode is UTF-8, a variable-length encoding that uses one byte for ASCII characters and up to four bytes for others, so old ASCII text is still valid UTF-8.
Real numbers: floating point
To store values like 3.14 or 0.0005, computers use floating-point representation, standardized as IEEE 754. It stores a number in three fields, much like scientific notation (a sign, a fraction, and an exponent):
| Field | Meaning | 32-bit float size |
|---|---|---|
| Sign | 1 for negative, 0 for positive | 1 bit |
| Exponent | Scales the value by a power of two | 8 bits |
| Mantissa (fraction) | The significant digits | 23 bits |
The value is roughly sign times mantissa times 2 raised to the exponent. Using powers of two lets one format span from tiny to enormous magnitudes, which is why it is called "floating" point: the position of the binary point moves with the exponent.
Why 0.1 is not exact
Just as 1/3 cannot be written exactly in decimal (0.3333...), the decimal 0.1 cannot be written exactly in binary; it repeats forever. A float stores only a finite number of bits, so it keeps a very close approximation, not the exact value. This is why 0.1 + 0.2 can print as 0.30000000000000004 in many languages. The lesson for programmers: never test floating-point values for exact equality; compare whether they are close enough within a small tolerance instead.
- Key terms
- Encoding
- An agreed mapping between bit patterns and the things they represent, such as characters.
- ASCII
- A 7-bit code assigning numbers to 128 common English characters; 'A' is 65.
- Unicode
- A universal standard assigning a code point to characters of every writing system, plus emoji.
- UTF-8
- A variable-length byte encoding of Unicode that is backward compatible with ASCII.
- Floating point
- A representation of real numbers with a sign, exponent, and mantissa, like binary scientific notation.
- Mantissa
- The fractional (significant-digits) part of a floating-point number.
Module 2: Boolean Logic and Gates
The algebra of true and false, the logic gates that implement it, and how truth tables describe any circuit.
Boolean Algebra and Truth Tables
- Evaluate expressions using AND, OR, and NOT.
- Build a truth table for a boolean expression.
- Apply basic boolean identities to simplify logic.
Underneath the arithmetic and the circuits lies a tidy branch of math invented by George Boole in the 1800s and later applied to switching circuits: boolean algebra. It works with just two values, which we can write as 1 (true) and 0 (false), and three fundamental operations.
The three basic operations
- AND (written as a dot or just concatenation) is 1 only when both inputs are 1. Think "both must be true."
- OR (written with a plus sign) is 1 when at least one input is 1. Think "either will do."
- NOT (written with a bar or prime) flips its single input: NOT 1 is 0, NOT 0 is 1.
Truth tables
A truth table lists every possible combination of inputs and the output for each. Because each input is one of two values, a function of n inputs has 2^n rows. Here are the tables for the three basic operations:
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 1 | 1 | 1 | 0 |
Building and reading expressions
You can combine the operations into expressions and evaluate them for each input row. Consider (A AND B) OR (NOT A). When A is 0, NOT A is 1, so the whole expression is 1 regardless of B. When A is 1, the NOT A term is 0, so the result equals A AND B, which is just B. The expression is therefore 1 in every row except A=1, B=0.
A few useful identities
Boolean algebra has simplification rules that let you shrink a circuit. Among the handiest: A AND 1 = A, A OR 0 = A, A AND 0 = 0, A OR 1 = 1, and A AND A = A. There is also the powerful De Morgan's law: NOT (A AND B) equals (NOT A) OR (NOT B), and NOT (A OR B) equals (NOT A) AND (NOT B). These identities are the same tools a compiler or a hardware designer uses to make logic smaller and faster.
- Key terms
- Boolean algebra
- The algebra of two values (true and false) with the operations AND, OR, and NOT.
- AND
- An operation that is true only when both inputs are true.
- OR
- An operation that is true when at least one input is true.
- NOT
- An operation that inverts its single input.
- Truth table
- A table listing the output for every possible combination of inputs.
- De Morgan's law
- NOT(A AND B) = (NOT A) OR (NOT B), and NOT(A OR B) = (NOT A) AND (NOT B).
Logic Gates and Universal Gates
- Identify the standard logic gates and their symbols.
- Explain XOR, NAND, and NOR behavior.
- Explain why NAND and NOR are universal.
A logic gate is a small electronic circuit that implements one boolean operation. Give it input voltages standing for 0s and 1s, and it produces an output voltage for the result. Gates are the physical bricks from which all computation is built. Each has a standard schematic symbol, but you can reason about them entirely through their truth tables.
The core gates
Beyond AND, OR, and NOT (drawn as a triangle with a small circle, the circle meaning "invert"), three combined gates appear constantly:
- XOR (exclusive OR) outputs 1 when its inputs differ. It is the "one or the other but not both" gate, and it is the heart of binary addition.
- NAND is NOT-AND: it outputs the opposite of AND, so it is 0 only when both inputs are 1.
- NOR is NOT-OR: it outputs 1 only when both inputs are 0.
| A | B | XOR | NAND | NOR |
|---|---|---|---|---|
| 0 | 0 | 0 | 1 | 1 |
| 0 | 1 | 1 | 1 | 0 |
| 1 | 0 | 1 | 1 | 0 |
| 1 | 1 | 0 | 0 | 0 |
Fan-in, and why gates are cheap
Real gates are built from a few transistors, tiny electronic switches etched onto a silicon chip. A single modern processor packs billions of them. Because each gate is so small and cheap, designers think nothing of using thousands of gates to build one adder or one small memory. A gate can also take more than two inputs; a 3-input AND, for instance, outputs 1 only when all three inputs are 1. The number of inputs a gate accepts is called its fan-in. Larger fan-in can save gates but tends to make each gate slightly slower, one of the many tradeoffs a hardware designer weighs.
Universal gates
Here is a beautiful fact: NAND alone (or NOR alone) can build every other gate. Because of this they are called universal gates. For example, a NAND with both inputs tied together acts as a NOT (feed the same signal to both inputs, and NAND outputs the inverse). Chaining a NAND then a NOT-from-NAND recreates AND, and De Morgan's law shows how to get OR from NANDs as well. This matters practically: chip factories can build enormous processors from vast fields of just one repeated gate type, which simplifies manufacturing and testing. Every processor you have ever used is, at bottom, a carefully arranged ocean of gates like these, wired from combinational blocks into the datapath you will meet in Module 4.
- Key terms
- Logic gate
- A circuit that implements one boolean operation on input signals.
- XOR
- The exclusive-OR gate, which outputs 1 only when its inputs differ.
- NAND
- A NOT-AND gate; its output is 0 only when both inputs are 1.
- NOR
- A NOT-OR gate; its output is 1 only when both inputs are 0.
- Universal gate
- A gate such as NAND or NOR from which every other gate can be built.
- Inversion bubble
- The small circle on a gate symbol indicating the output is negated.
Module 3: Combinational and Sequential Circuits
Wiring gates into adders and multiplexers, then adding memory with latches, flip-flops, and a clock.
Combinational Building Blocks: Adders, Multiplexers, Decoders
- Distinguish combinational from sequential circuits.
- Explain how a half adder and full adder work.
- Describe what multiplexers and decoders do.
A combinational circuit is one whose outputs depend only on its current inputs, with no memory of the past. Feed it the same inputs and you always get the same outputs, immediately. From gates we can assemble reusable combinational blocks that appear inside every CPU.
Adding bits: the half adder and full adder
Consider adding two single bits. There are four cases, and the result can need two output bits: a sum and a carry.
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
Look closely: the Sum column is exactly A XOR B, and the Carry column is exactly A AND B. A circuit with just those two gates is called a half adder. To add multi-bit numbers you also need to fold in a carry coming from the column to the right, which requires a full adder: it takes three inputs (A, B, and carry-in) and produces a sum and a carry-out. Chain eight full adders side by side, passing each carry-out into the next carry-in, and you have an 8-bit ripple-carry adder that adds whole bytes. This is literally how the arithmetic unit adds.
Choosing and routing: multiplexers and decoders
A multiplexer (mux) is a selector switch. It has several data inputs, a few select lines, and one output; the select lines' binary value picks which input is copied to the output. A mux with two select lines can choose among four inputs. Muxes let the CPU route data along different paths depending on the instruction. A decoder does the reverse kind of job: it takes an n-bit input and activates exactly one of 2^n output lines, the one whose number matches the input. Decoders are how a memory address selects the single storage cell it names. Together, adders, muxes, and decoders are the vocabulary from which the datapath is built.
- Key terms
- Combinational circuit
- A circuit whose outputs depend only on the present inputs, with no stored state.
- Half adder
- A circuit that adds two bits, giving a sum (XOR) and a carry (AND).
- Full adder
- A circuit that adds three bits (A, B, carry-in) to produce a sum and carry-out.
- Ripple-carry adder
- Multiple full adders chained so each carry-out feeds the next carry-in.
- Multiplexer
- A selector that routes one of several inputs to the output based on select lines.
- Decoder
- A circuit that activates one of 2^n outputs based on an n-bit input, as in address selection.
Sequential Circuits: Latches, Flip-Flops, and the Clock
- Explain how feedback gives a circuit memory.
- Distinguish a latch from an edge-triggered flip-flop.
- Describe the role of the clock and registers.
Combinational circuits compute, but they cannot remember. To store a bit - to build memory, counters, and the CPU's registers - we need sequential circuits, whose outputs depend on both the current inputs and the stored past. The trick that makes memory possible is feedback: routing an output back into an input so the circuit can hold a value.
The latch: memory from feedback
Cross-couple two NOR gates (each one's output feeds the other's input) and you get an SR latch, the simplest one-bit memory. It has a Set input that forces the stored bit to 1 and a Reset input that forces it to 0; when both inputs are 0, the latch holds whatever it last stored. That holding behavior is memory. A refined version, the D latch, has a single data input D and an enable line: while enabled, the stored bit follows D; when disabled, it freezes the last value. A latch is level-sensitive - it responds to inputs the entire time it is enabled.
Flip-flops and edge triggering
For orderly, predictable machines we usually want storage that changes only at a single instant, not continuously. A flip-flop is a storage element that captures its input only on a clock edge, the sharp moment the clock signal transitions from 0 to 1 (the rising edge). Between edges it ignores its input entirely. A D flip-flop samples D at each rising edge and holds it until the next one. Edge triggering keeps every part of the machine in lockstep.
The clock and registers
The clock is a signal that oscillates between 0 and 1 at a steady rate, the drumbeat that paces the whole processor. A 3 GHz clock ticks three billion times per second, and on each tick the machine advances one small step. Group several D flip-flops that all share one clock and you get a register, a small block of fast storage that holds a whole word (say 32 or 64 bits) and updates as a unit each clock cycle. Registers are the CPU's working memory - the fastest storage in the entire machine - and in the next module we will watch instructions flow through them.
- Key terms
- Sequential circuit
- A circuit whose output depends on stored past state as well as current inputs.
- Feedback
- Routing an output back to an input, which lets a circuit hold a value.
- Latch
- A level-sensitive one-bit memory that responds to inputs while enabled.
- Flip-flop
- A storage element that captures its input only on a clock edge.
- Clock
- A steadily oscillating signal that paces the steps of a synchronous circuit.
- Register
- A group of flip-flops sharing a clock that stores a whole word of data.
Module 4: The CPU and the Instruction Cycle
The datapath, the fetch-decode-execute cycle, and how assembly relates to machine code.
Inside the CPU: Datapath and Control
- Identify the main parts of a CPU.
- Explain the role of the ALU, registers, and program counter.
- Describe the stored-program (von Neumann) idea.
A central processing unit (CPU) is the part of the computer that carries out instructions. Though modern chips are staggeringly complex, the core design has been stable since the 1940s and rests on a handful of cooperating parts.
The main components
- The arithmetic logic unit (ALU) does the actual computing: add, subtract, AND, OR, compare, and shift. It is built from the adders and gates of the previous modules.
- Registers are the CPU's handful of ultra-fast storage slots for the values it is working on right now.
- The program counter (PC) is a special register holding the memory address of the next instruction to run.
- The instruction register holds the instruction currently being executed.
- The control unit reads the current instruction and sends the signals that steer everyone else, choosing the ALU operation and opening the right data paths.
The wires and muxes connecting the registers to the ALU and back are collectively the datapath, the roads along which data travels. The control unit is the traffic officer directing that flow.
The stored-program idea
The foundational insight, associated with the von Neumann architecture, is that a program's instructions are stored in the same memory as its data, both just numbers. This is powerful because a computer can be repurposed simply by loading different numbers into memory, rather than being rewired for each task. It also means code and data share one path to memory, a bottleneck sometimes called the von Neumann bottleneck, which is a major reason caches (Module 5) matter so much.
Words and the register file
A CPU processes data in fixed-size chunks called words; a 64-bit CPU has 64-bit registers and moves 64 bits at a time. The full set of registers is the register file. Because registers live right next to the ALU, reading and writing them is roughly a hundred times faster than reaching out to main memory, which is why compilers work hard to keep hot values in registers.
- Key terms
- CPU
- The central processing unit, which fetches and executes a program's instructions.
- Arithmetic logic unit (ALU)
- The CPU component that performs arithmetic and logic operations.
- Program counter (PC)
- A register holding the address of the next instruction to execute.
- Control unit
- The part of the CPU that decodes instructions and directs the datapath.
- Datapath
- The registers, ALU, and connecting wires along which data flows inside the CPU.
- Von Neumann architecture
- A design storing instructions and data together in the same memory.
The Fetch-Decode-Execute Cycle
- List the phases of the instruction cycle.
- Trace one instruction through the cycle.
- Explain how the program counter advances and branches.
How does a CPU actually run a program? It repeats a simple loop, billions of times per second, called the instruction cycle or the fetch-decode-execute cycle. Understanding this loop demystifies the whole machine.
The three phases
- Fetch. The control unit reads the instruction stored at the address in the program counter, copying it from memory into the instruction register. The PC is then advanced to point at the following instruction.
- Decode. The control unit examines the instruction's bits to work out what operation it names and which registers or memory it involves. This sets up the ALU and opens the correct datapath switches.
- Execute. The operation is carried out: the ALU computes a result, a value moves between a register and memory, or the program counter is changed to jump elsewhere. Any result is written back to a register or memory.
Then the cycle repeats with the next instruction. Because the fetch step already advanced the PC, execution normally flows straight down through memory, one instruction after another.
Tracing an example
Suppose memory holds a small program and the PC points at an instruction meaning "add register R1 and register R2, store the result in R3." Fetch copies that instruction in and bumps the PC. Decode recognizes it as an add and selects R1 and R2 as the ALU's inputs with add as the operation. Execute sends R1 and R2 into the ALU, which produces their sum, and the result is written into R3. One tick or a few ticks later, the CPU fetches whatever instruction the PC now names.
Changing the flow: branches
Programs are not just straight lines; they loop and make decisions. A branch (or jump) instruction changes the program counter to a new address instead of the next one in sequence, so the next fetch happens somewhere else. A conditional branch only makes that change when a tested condition is true, such as a comparison result. This single mechanism - editing the PC - is how the hardware implements every if-statement and loop you have ever written.
- Key terms
- Instruction cycle
- The repeating fetch-decode-execute loop by which a CPU runs a program.
- Fetch
- Reading the next instruction from memory into the CPU using the program counter.
- Decode
- Interpreting an instruction's bits to determine the operation and operands.
- Execute
- Carrying out the decoded operation and writing back any result.
- Branch
- An instruction that changes the program counter to alter the flow of control.
- Conditional branch
- A branch taken only when a tested condition holds, implementing if and loop logic.
Assembly and Machine Code
- Explain the difference between machine code and assembly.
- Describe the fields of a machine instruction.
- Read a short assembly snippet.
The instructions a CPU fetches are just numbers, called machine code. Each instruction is a binary pattern split into fields the control unit knows how to read. Writing programs directly in binary would be miserable, so we use assembly language, a human-readable shorthand where each machine instruction gets a short mnemonic name. A tool called an assembler translates assembly text into the equivalent machine-code bits.
The shape of an instruction
A machine instruction typically begins with an opcode, the field that names the operation (add, load, jump, and so on). The remaining fields are operands: which registers to use, or an immediate constant, or a memory address. For example, an add instruction might be laid out like this:
| Opcode (operation) | Destination reg | Source reg 1 | Source reg 2 |
|---|---|---|---|
| ADD | R3 | R1 | R2 |
The full set of instructions a particular CPU understands, together with its registers and their encodings, is its instruction set architecture (ISA). Well-known families include x86 (in most laptops and desktops), ARM (in nearly all phones), and the open RISC-V. Code compiled for one ISA does not run on another, which is why software is often distributed in separate builds.
Reading assembly
Here is a short, generic assembly snippet that adds two numbers and stores the result. Comments after the semicolons explain each line:
LOAD R1, x ; copy the value at memory location x into register R1
LOAD R2, y ; copy the value at memory location y into register R2
ADD R3, R1, R2 ; R3 = R1 + R2
STORE R3, z ; copy R3 back out to memory location z
Each line becomes one machine instruction. Notice the rhythm of real hardware: data must be loaded from memory into registers, operated on there, then stored back. This load-compute-store pattern is the reality beneath a single line of high-level code like z = x + y. A compiler is the program that turns high-level languages such as C or Python-compiled code into exactly these kinds of instructions.
- Key terms
- Machine code
- The binary-encoded instructions a CPU fetches and executes directly.
- Assembly language
- A human-readable notation with one mnemonic per machine instruction.
- Assembler
- A tool that translates assembly language into machine code.
- Opcode
- The field of an instruction that names which operation to perform.
- Operand
- An instruction field specifying a register, constant, or address to act on.
- Instruction set architecture (ISA)
- The full set of instructions and registers a particular CPU family defines.
Module 5: Memory Hierarchy, Caching, and the Stack
Why memory is layered, how caches exploit locality, and how the call stack drives procedure calls.
The Memory Hierarchy and Locality
- Explain the tradeoff between memory speed, size, and cost.
- List the levels of the memory hierarchy.
- Define temporal and spatial locality.
An awkward truth shapes every computer: memory that is fast is expensive and small, while memory that is cheap and huge is slow. No single technology is fast, big, and cheap at once. The engineering answer is a memory hierarchy that layers several kinds of storage so the system feels almost as fast as the quickest level while being almost as large as the biggest.
The levels, fastest to slowest
| Level | Rough speed | Rough size |
|---|---|---|
| Registers | Fastest (inside the CPU) | A few kilobytes at most |
| Cache (L1, L2, L3) | Very fast | Kilobytes to tens of megabytes |
| Main memory (RAM) | Moderate | Gigabytes |
| Solid-state / hard disk | Slow | Hundreds of gigabytes to terabytes |
As you move down the list, each level is dramatically larger and cheaper per byte but also much slower to reach. Data you are using is copied up toward the fast levels; data you are not using drifts down to the big, slow levels. Reaching all the way to main memory can cost the CPU the equivalent of dozens or hundreds of wasted instruction slots, which is why the layers above it exist.
Locality: why the hierarchy works
The hierarchy pays off because real programs do not touch memory randomly. They exhibit locality of reference in two forms:
- Temporal locality: if you use a piece of data, you are likely to use it again soon. A loop counter or a frequently called function is a good example.
- Spatial locality: if you use a piece of data, you are likely to use nearby data soon. Marching through an array element by element is the classic case.
Because of locality, keeping recently used and nearby data in the fast levels satisfies the great majority of requests quickly. Writing code that respects locality - for example, processing an array in order rather than jumping around - can make a program several times faster without changing what it computes.
- Key terms
- Memory hierarchy
- The layered arrangement of storage from fast/small/expensive to slow/large/cheap.
- RAM (main memory)
- The computer's large, moderately fast working memory that holds running programs and data.
- Locality of reference
- The tendency of programs to access the same or nearby memory repeatedly.
- Temporal locality
- The likelihood that recently accessed data will be accessed again soon.
- Spatial locality
- The likelihood that data near a recent access will be accessed soon.
- Volatile memory
- Memory such as RAM that loses its contents when power is removed.
How Caches Work
- Explain what a cache stores and why.
- Define cache hit, miss, and cache line.
- Describe direct-mapped placement and eviction.
A cache is a small, fast memory that sits between the CPU and main memory and keeps copies of recently used data, so future accesses can be served quickly. Caches are the single most important reason modern processors run fast, and they work automatically - programmers do not manage them directly, though writing cache-friendly code helps enormously.
Hits and misses
When the CPU needs data, it checks the cache first. If the data is there, that is a cache hit and the access is fast. If it is not, that is a cache miss, and the data must be fetched from the slower level below, costing many extra cycles. The fraction of accesses that hit is the hit rate. Because of locality, real programs achieve hit rates well above 90 percent, which is what makes the whole scheme pay off.
Cache lines
Caches do not fetch a single byte at a time. On a miss they pull in a whole cache line - a contiguous block, commonly 64 bytes - on the bet that neighboring bytes will be needed soon. This is spatial locality made concrete: touch one byte and its neighbors ride along into the fast memory for free. It is also why looping through an array in order is so efficient; each miss brings in many upcoming elements at once.
Placement and eviction
Where can a given block of memory sit in the cache? In the simplest scheme, direct-mapped, each memory block maps to exactly one cache slot, chosen by part of its address. This is fast to check but can cause two hot blocks that map to the same slot to keep knocking each other out. More flexible set-associative caches let a block live in any of several slots within a set, reducing such conflicts. When the cache is full and a new line arrives, an old line must be evicted; a common policy evicts the least recently used line, betting that the longest-unused data is least likely to be needed next. Understanding caches turns puzzling performance differences into predictable ones: two algorithms with the same instruction count can differ severalfold in speed purely because one is friendlier to the cache.
- Key terms
- Cache
- A small, fast memory holding copies of recently used data to speed access.
- Cache hit
- When requested data is found in the cache, giving a fast access.
- Cache miss
- When requested data is not in the cache and must be fetched from a slower level.
- Cache line
- A contiguous block of memory (often 64 bytes) moved into the cache as a unit.
- Direct-mapped cache
- A cache where each memory block maps to exactly one slot.
- Eviction
- Removing a line from a full cache to make room, often the least recently used one.
The Stack and Procedure Calls
- Explain how the call stack supports function calls.
- Describe what a stack frame contains.
- Explain the return address and stack overflow.
When one function calls another, the machine must remember where to come back to and must give the new function room for its own local variables. It does this with the call stack, a region of memory managed as a stack data structure: items are pushed on top and popped off the top, in last-in, first-out (LIFO) order, exactly matching the way function calls nest.
Stack frames
Each active function call gets its own stack frame (also called an activation record) pushed onto the stack. A frame typically holds:
- The return address - where in the caller to resume once this function finishes.
- The function's local variables and its parameters (or space for them).
- Saved values of registers that must be preserved across the call.
A special register, the stack pointer, always marks the current top of the stack. Calling a function pushes a new frame and moves the stack pointer; returning pops the frame and restores the pointer, so the memory is reused by the next call. Because the most recently called function is the first to return, LIFO is precisely the right discipline.
Tracing nested calls
Imagine main calls cook, which calls chop. When chop is running, the stack holds three frames stacked up: main at the bottom, then cook, then chop on top. When chop finishes, its frame is popped and control returns to cook using the return address stored in chop's frame. When cook finishes, its frame is popped and control returns to main. The stack grew and shrank in perfect step with the call nesting.
Stack overflow
The stack has a limited size. If calls nest too deeply - most often through infinite recursion, a function that calls itself without ever reaching a base case - the stack keeps growing until it runs out of room. This is a stack overflow, and it crashes the program. It is the hardware-level reason a runaway recursive function fails, and it is where the famous programming website gets its name. Recognizing that every function call has a real memory cost on the stack is an important step in reasoning about how programs run.
- Key terms
- Call stack
- A region of memory, managed as a stack, that tracks active function calls.
- Stack (LIFO)
- A last-in, first-out structure where items are pushed and popped from the top.
- Stack frame
- The block pushed for one function call, holding its locals, parameters, and return address.
- Return address
- The stored location in the caller where execution resumes after a call returns.
- Stack pointer
- A register that marks the current top of the call stack.
- Stack overflow
- A crash caused by the call stack growing beyond its limit, often from infinite recursion.
Module 6: Input/Output, the Operating System, and Concurrency
How the CPU talks to devices, and how the operating system manages processes, virtual memory, and concurrency.
Input, Output, and Interrupts
- Explain how the CPU communicates with I/O devices.
- Contrast polling with interrupts.
- Describe the role of buses and device controllers.
A CPU that only computed on registers would be useless; it must also talk to the outside world - keyboards, screens, disks, and networks. This is input/output (I/O). Devices are far slower and more varied than the CPU, so several mechanisms coordinate the conversation.
Buses and controllers
Components communicate over shared sets of wires called buses, which carry addresses, data, and control signals between the CPU, memory, and devices. Each device connects through a device controller, a small piece of hardware that manages the device's details and exposes a set of registers the CPU can read and write. In the common memory-mapped I/O scheme, these device registers are assigned ordinary memory addresses, so the CPU talks to a device simply by reading and writing certain addresses, using the same load and store instructions it uses for memory.
Polling versus interrupts
How does the CPU know when a slow device is ready - say, when a key has been pressed? One approach is polling: the CPU repeatedly checks the device's status register in a loop. Polling is simple but wasteful, because the CPU burns time asking "ready yet?" when it could be doing real work. The better approach is the interrupt: the device signals the CPU only when it needs attention. On receiving an interrupt, the CPU pauses its current program, saves its state, and runs a short interrupt handler to service the device, then resumes exactly where it left off. Interrupts let the CPU stay productive and respond promptly to events, which is why they are the foundation of modern I/O.
Moving bulk data
For large transfers, such as reading a file from disk, having the CPU copy every byte would be slow. Instead, direct memory access (DMA) lets a controller move data between a device and memory on its own, interrupting the CPU only once the whole transfer is done. The CPU sets up the transfer, then goes about other work while the DMA controller does the heavy lifting. Together, interrupts and DMA free the fast CPU from waiting on slow devices, which is essential to a responsive system.
- Key terms
- Input/output (I/O)
- Communication between the CPU and external devices such as keyboards, disks, and networks.
- Bus
- A shared set of wires carrying address, data, and control signals between components.
- Device controller
- Hardware that manages a device and exposes registers the CPU can access.
- Polling
- Repeatedly checking a device's status in a loop to see if it is ready.
- Interrupt
- A signal from a device that pauses the CPU to run a handler when attention is needed.
- Direct memory access (DMA)
- A method letting a controller transfer data to or from memory without the CPU copying each byte.
The Operating System: Processes and Scheduling
- Explain what an operating system does.
- Define a process and describe its states.
- Explain time-sharing and context switching.
Raw hardware is hard to use directly and can only do one thing at a time per core. The operating system (OS) is the master program that manages the hardware and provides services to all other programs. It shares the CPU, memory, and devices among many programs, enforces protection so one program cannot corrupt another, and offers a clean interface through system calls - the controlled requests a program makes to ask the OS for a service such as reading a file or getting more memory.
Processes
A process is a running program together with all its state: its code, its data, its call stack, and its share of resources. Your browser, your editor, and your music player are separate processes. The OS keeps a record for each, and a process moves among a few states during its life:
- Running: currently executing on a CPU core.
- Ready: able to run and waiting for a turn on the CPU.
- Blocked (waiting): paused until some event completes, such as a disk read or user input.
Time-sharing and the scheduler
A single core can run only one process at any instant, yet your computer appears to run dozens at once. The illusion comes from time-sharing: the OS lets each ready process run for a brief slice of time (a few milliseconds), then switches to another, cycling so quickly that all seem to progress together. The component that decides who runs next is the scheduler. Its goals include keeping the CPU busy, treating processes fairly, and keeping interactive programs responsive.
Context switching
Switching the CPU from one process to another is a context switch. The OS saves the outgoing process's registers and program counter (its context), then loads the incoming process's saved context so it resumes exactly where it paused. Context switches are not free - they take time and can disturb the cache - so switching too often wastes effort, a real tradeoff the scheduler must balance. This machinery, invisible in daily use, is what lets one CPU serve many programs at once.
- Key terms
- Operating system
- The master program that manages hardware and provides services to other programs.
- System call
- A controlled request from a program asking the OS to perform a privileged service.
- Process
- A running program together with its code, data, stack, and resources.
- Process state
- The current condition of a process, such as running, ready, or blocked.
- Time-sharing
- Rapidly switching the CPU among processes so many appear to run at once.
- Context switch
- Saving one process's state and loading another's to change which process runs.
Virtual Memory
- Explain the purpose of virtual memory.
- Describe how pages map virtual to physical addresses.
- Explain what a page fault is.
If every process used physical memory addresses directly, they would trample each other, and no program could assume where it would be loaded. Virtual memory solves this with a clean illusion: each process gets its own private, contiguous virtual address space, as if it alone owned all of memory, starting at address zero. The hardware and OS quietly translate these virtual addresses to real physical addresses in RAM behind the scenes.
Pages and page tables
Virtual memory is managed in fixed-size chunks called pages (commonly 4 kilobytes). Physical memory is divided into equal-size frames. The OS keeps a page table for each process that maps each virtual page to the physical frame currently holding it. On every memory access, a hardware unit called the memory management unit (MMU) uses the page table to translate the virtual address to a physical one. To keep this fast, a small cache of recent translations, the translation lookaside buffer, avoids consulting the full table every time.
Two big wins
This design brings two enormous benefits. First, isolation and protection: because each process has its own page table, one process simply cannot name another's memory, so a bug or attack in one program cannot corrupt another. Second, using more memory than physically exists: pages not currently needed can be kept on disk in a swap area and brought into RAM only when accessed. A program can therefore behave as if it has more memory than the machine physically holds.
Page faults
When a process accesses a page that is not currently in RAM (it is on disk or not yet loaded), the MMU raises a page fault. The OS handles it by finding the page on disk, loading it into a free frame (evicting another page if memory is full), updating the page table, and then restarting the instruction as if nothing happened. Occasional page faults are normal, but if a system is so short on memory that it spends most of its time swapping pages in and out - a condition called thrashing - performance collapses. Virtual memory, then, is the layer that makes memory safe to share and larger than it really is.
- Key terms
- Virtual memory
- An abstraction giving each process a private address space, translated to physical memory.
- Virtual address space
- The private, contiguous range of addresses a process sees as its own.
- Page
- A fixed-size block (often 4 KB) that is the unit of virtual memory management.
- Page table
- A per-process map from virtual pages to the physical frames holding them.
- Page fault
- An event raised when an accessed page is not currently in physical memory.
- Memory management unit (MMU)
- Hardware that translates virtual addresses to physical addresses using the page table.
Concurrency Basics
- Distinguish concurrency from parallelism.
- Explain threads and the danger of race conditions.
- Describe how locks provide mutual exclusion.
Modern programs often do many things at once: a browser downloads images while you scroll, a server handles thousands of users together. Concurrency is the property of a system that manages multiple tasks that overlap in time. It is closely related to but distinct from parallelism, which is doing multiple tasks at literally the same instant on multiple CPU cores. A single core can be concurrent (rapidly interleaving tasks) without being parallel; multiple cores add true parallelism.
Threads
Where a process is a whole running program, a thread is a single flow of execution within a process. A process can have several threads that all share the same memory and resources but run somewhat independently, each with its own stack and program counter. Threads make it natural to keep a program responsive - one thread handles the interface while another does slow work - and they let a program use several cores at once.
Race conditions
Shared memory is powerful but perilous. A race condition occurs when the outcome of a program depends on the unpredictable timing of threads accessing shared data. Consider two threads that each increment a shared counter. Incrementing is really three steps: read the value, add one, write it back. If both threads read the same old value before either writes, both write the same result, and one increment is silently lost. The final count is wrong, and the bug may appear only occasionally, making it notoriously hard to find.
Locks and mutual exclusion
The classic fix is mutual exclusion: ensure that only one thread at a time can execute the critical section of code that touches the shared data. A lock (or mutex) is a tool a thread must acquire before entering the critical section and release when leaving; while one thread holds the lock, others wait. This serializes access so updates cannot interleave dangerously. Locks must be used carefully, though: if two threads each hold a lock the other needs and neither will release, they wait forever in a deadlock. Concurrency is one of the deepest topics in computing, and these ideas - threads, race conditions, locks, and deadlock - are the vocabulary you will build on. They are also the reason multi-core machines are powerful yet tricky to program correctly.
- Key terms
- Concurrency
- Managing multiple tasks that overlap in time, whether or not they run at the same instant.
- Parallelism
- Executing multiple tasks literally simultaneously on multiple cores.
- Thread
- A single flow of execution within a process, sharing the process's memory.
- Race condition
- A bug where the result depends on the unpredictable timing of threads accessing shared data.
- Mutual exclusion
- Ensuring only one thread at a time runs a critical section of code.
- Lock (mutex)
- A mechanism a thread acquires to enter a critical section and releases when done, making others wait.