What Assembly Actually Is
Assembly Fundamentals
Chapter 1 · What Assembly Actually Is
Every program you've ever run — a browser, a Python script, this very course's own site — eventually becomes a stream of raw binary instructions a CPU executes one at a time. Assembly language is the thinnest possible layer of human readability wrapped around that binary reality. This chapter explains exactly what that layer is, walks through how a text file of assembly actually becomes a running program, and explains why this course teaches all of it using an invented-for-teaching architecture — LC-3 — instead of jumping straight into the real, quirk-laden chips this site already covers or will eventually cover.
The Ladder of Abstraction
Every programming language sits somewhere on a ladder between "what the CPU actually does" and "what's convenient for a human to write." Assembly sits one rung above the very bottom.
| Level | What one line looks like | Relationship to machine code |
|---|---|---|
| Machine code | 0001 001 000 0 00 010 | Is the machine code — raw bits the CPU fetches and decodes directly |
| Assembly | ADD R1, R0, R2 | One assembly instruction → almost always exactly one machine instruction |
| High-level (Python, C, JS) | x = a + b | One line → anywhere from a handful to hundreds of machine instructions |
That near-1:1 correspondence is the entire point of assembly. A high-level language hides the machine so you can think about the problem; assembly deliberately doesn't — it exists specifically so you can see, instruction by instruction, exactly what the CPU is doing. py1-1's own print("hello") compiles down through several layers before it ever reaches a CPU; an LC-3 ADD instruction in this course is, for all practical purposes, the thing the CPU executes.
The Assemble-Link-Run Pipeline
A text file full of mnemonics like ADD and LD doesn't run by itself — a CPU has never heard of the word "ADD." Getting from a .asm source file to a running program passes through several distinct stages:
; source.asm — a human writes this START LD R1, VALUE ADD R1, R1, R1 HALT VALUE .FILL #5
- Assembler — reads the source file and translates each mnemonic into its binary machine-code equivalent, resolving labels like
VALUEinto actual memory addresses. Produces an object file. - Linker — combines one or more object files together, resolving any references that point at a different file, and produces a single, complete executable image.
- Loader — copies that executable image into memory at the address it expects to run from.
- CPU execution — the processor's own fetch-decode-execute cycle (the full subject of
assembly1-2) takes over from there, one instruction at a time.
This chapter only previews that pipeline — assembly1-9 comes back to it in full, explaining exactly how an assembler turns a mnemonic into bits, and exactly what a linker does when a symbol lives in a different file than the one that uses it.
Why Not Just Learn a Real Chip First?
This site already has (or will have) courses on real, historically important processors — the 6502 and Z80 in cpu8bit1, and eventually a modern x86-64 course. So why not just start there?
Because every real chip carries decisions that were forced by its own era, not by any universal principle of computing:
- The 6502's famously tiny register set exists largely because MOS Technology built it to be sold for $25 in 1975 — fewer transistors meant a cheaper, more competitive chip, not a deliberate teaching choice.
- The Z80 was designed as a superset of the Intel 8080, meaning a good portion of its instruction set exists purely to stay backward-compatible with a chip that came before it.
- x86-64 carries over four decades of backward-compatible accretion — instructions, addressing quirks, and legacy modes that exist only because something built in the 1970s and 80s still has to keep working today.
None of that is a flaw in those chips — it's just historical reality, and cpu8bit1 and the future x86-64 course both cover it honestly. But it means that learning a real chip first means learning "what is a register" and "why does this instruction exist for a 1975 manufacturing reason" at the same time, tangled together. This course exists to untangle them — teaching the universal concepts on their own first, so that by the time you reach a real chip, every quirk you encounter reads as a genuine, nameable design trade-off instead of unexplained noise.
Meet the Teaching Architecture: LC-3
This course uses LC-3 ("Little Computer 3"), a 16-bit instruction set architecture designed specifically for teaching computer organization — not an invented toy made up for this course, but a real, widely used academic architecture from Patt & Patel's textbook Introduction to Computing Systems, taught at universities including UT Austin. LC-3 was deliberately designed to be small enough to fully understand in a single course, yet complete enough to write real, working programs on: 16-bit words, 8 general-purpose registers, a clean and compact instruction set, and thorough public documentation.
Every chapter from here forward writes real LC-3 assembly and traces it against LC-3's actual, documented behavior — nothing in this course is simplified beyond what LC-3 itself already simplifies for you.
| Concept previewed here | What it roughly means | Fully explained in |
|---|---|---|
| Fetch-decode-execute cycle | The repeating loop every instruction passes through to actually run | assembly1-2 |
| Memory & addressing modes | How an instruction says where its data lives | assembly1-3 |
| Registers & the register file | LC-3's 8 fast, on-chip storage slots (R0–R7) | assembly1-4 |
| Instructions & opcodes | The actual operations LC-3 can perform, and their binary encoding | assembly1-5 |
| Condition codes & branching | How a program makes decisions (if/else, loops) | assembly1-6 |
| Subroutines & the stack | Calling and returning from reusable blocks of code | assembly1-7 |
| I/O & system calls | How a program talks to a keyboard, screen, or operating system | assembly1-8 |
| Assemblers & linkers | How this chapter's own pipeline actually works, in full | assembly1-9 |
Hands-On Exercises
Given the three lines below, identify which abstraction level each one belongs to (machine code, assembly, or a high-level language), and explain in your own words why the assembly line has a much closer, more predictable relationship to the machine-code line than the high-level line does: x = a + b, ADD R1, R0, R2, 0001 001 000 0 00 010.
A program is split across two source files, main.asm and helper.asm. main.asm calls a label defined only inside helper.asm. Using this chapter's four-stage assemble-link-run pipeline, name the specific stage where that cross-file label reference actually gets resolved, and explain what would go wrong if that stage were skipped.
Using this chapter's own reasoning in "Why Not Just Learn a Real Chip First?", name two specific historical quirks (one from the 6502, one from the Z80) that this course's use of LC-3 lets you avoid dealing with until cpu8bit1 — and explain why postponing them helps rather than just delays the learning.
Chapter 1 Quick Reference
- Machine code → assembly → high-level languages is a ladder of abstraction; assembly sits one rung above raw binary, nearly 1:1 with it
- The pipeline: assembler (source → object file) → linker (object files → one executable) → loader (executable → memory) → CPU execution
- Real chips (6502, Z80, x86-64) carry era-specific historical baggage — cost constraints, backward compatibility, decades of accretion
- LC-3 — a real, documented 16-bit teaching ISA (Patt & Patel), used to strip that baggage away so the universal concepts come first
- This course's own roadmap: fetch-decode-execute → memory/addressing → registers → instructions → branching → subroutines/stack → I/O → assemblers/linkers
- The teaching architecture is simplified in era, not in rigor — everything taught here is LC-3's real, documented behavior