1.1.12How Computers Work

Machine code and assembly — what actually runs

2,597 words12 min readdifficulty · medium2 backlinks

WHAT is machine code?

WHY two names for the same thing? Binary 10110000 01100001 is impossible for humans to read. Assembly gives it a mnemonic (MOV AL, 0x61). Same instruction, different representation — like writing the number "thirteen" vs "13" vs "1101".


HOW an instruction is built (derive it from scratch)

A CPU can't read English. So we must encode "add register 2 and register 3, store in register 1" as bits. Let's design such an encoding to see there is no magic.

Step 1 — What must the bits tell the CPU?

  • WHICH operation? (add / sub / load / jump…) → call this the opcode.
  • WHICH data to use? → the operands (registers, or a constant, or a memory address).

Why this step? Every instruction is just "do X to Y" — opcode = X, operands = Y.

Step 2 — Allocate bits. Suppose our toy CPU has:

  • 8 possible operations → we need log28=3\lceil \log_2 8 \rceil = 3 bits for the opcode.
  • 4 registers → log24=2\lceil \log_2 4 \rceil = 2 bits to name each register.

Why log2\log_2? With nn bits you can label 2n2^n distinct things, so to label kk things you need nlog2kn \ge \log_2 k.

Step 3 — Lay out one 9-bit instruction ADD R1, R2, R3:

010opcode=ADD  01dest R1  10src R2  11src R3\underbrace{010}_{\text{opcode=ADD}}\;\underbrace{01}_{\text{dest }R1}\;\underbrace{10}_{\text{src }R2}\;\underbrace{11}_{\text{src }R3}

That single binary word 010 01 10 11 is the machine code. The assembler produced it from the text ADD R1, R2, R3. The CPU reads it and the wiring does the addition.

Figure — Machine code and assembly — what actually runs

HOW the CPU actually runs it — the fetch–decode–execute cycle

Why a PC? Programs are just lists in memory. Something must remember "where are we?" — that's the PC. A jump instruction simply writes a new value into the PC, which is how if/loops/functions exist at the bottom level.


The translation ladder (WHY assembly sits where it does)

C source                  Python source
   │  compiler               │  interpreter (CPython)
   ▼                         ▼
Assembly (MOV, ADD, JMP)   Python bytecode  ──► VM loop runs each
   │  assembler             (runs on a virtual machine, not the CPU directly)
   ▼                              │
Machine code (raw bytes)   ◄──────┘ (the VM itself is machine code)
   │  CPU hardware
   ▼
Electrons / logic gates   ← physics
  • Compiler (C, Rust, etc.): high-level language → assembly, ahead of time (does the hard creative work: loops, types, optimization).
  • Interpreter (standard/CPython): does not turn your Python into native assembly. It first compiles your .py to portable bytecode, then a loop inside the interpreter (the virtual machine) executes each bytecode op. The interpreter program is itself machine code that the CPU runs.
  • Assembler: assembly → machine code. It is not just an opcode lookup — it also does label resolution (turning ADDR1 into a real address), relocation (so code can be placed anywhere in memory), macro expansion, and directive handling.
  • CPU: machine code → actions (hardware).

Worked examples


Common mistakes (steel-manned)


Recall Feynman: explain to a 12-year-old

Imagine a robot that only understands numbered commands written on cards: "1 = pick up", "2 = move left", "3 = drop". You write a story for it as a stack of cards with numbers. The numbers are machine code. Because numbers are hard to remember, you also keep a cheat-sheet that says "PICKUP = 1, LEFT = 2" — writing your story with those words is assembly. The robot has a finger pointing at the current card (the Program Counter); it reads a card, does it, then moves its finger to the next card — unless a card says "jump back to card 2", which is how it can repeat things. With C, a translator turns your whole story into number-cards before the robot starts. With Python, there's a helper robot (the interpreter) that reads your story one line at a time and presses the right buttons for you — that helper robot is itself made of number-cards.


Active recall

What is machine code?
The binary-encoded instructions a CPU executes directly; each is a number = opcode + operands.
What is assembly language?
A human-readable, ~1-to-1 textual representation of machine code, turned into bytes by an assembler.
Does standard Python compile to native assembly like C?
No. CPython compiles to portable bytecode that an interpreter (virtual machine) then executes; it is not turned into native CPU assembly.
What does an assembler do beyond opcode→bits lookup?
Label/symbol resolution, relocation, macro expansion, and handling directives.
What does a compiler do vs an interpreter?
Compiler: translates source to machine code ahead of time. Interpreter: a pre-built program that reads and executes your source (often via bytecode) at run time.
What two parts make up a machine instruction?
The opcode (which operation) and the operands (which data/registers/constants).
How many bits to select one of k items?
⌈log₂ k⌉ bits.
Bits to encode an opcode with 16 possible operations?
⌈log₂16⌉ = 4 bits.
What are the steps of the CPU's main loop?
Fetch, Decode, Execute, Advance (update PC).
What is the Program Counter (PC)?
A register holding the address of the next instruction to fetch.
How does a loop exist at the machine level?
A conditional jump writes a new (earlier) address into the PC, repeating the cycle.
What is an ISA?
Instruction Set Architecture — the convention defining what each machine-code bit pattern means (e.g. x86, ARM, RISC-V).
Why does the same byte mean different things on different CPUs?
Because the meaning is defined by each CPU's ISA, not universally by the binary itself.
Why might an immediate constant be limited in size?
Its bit field has a fixed width; a value needing more bits than the field can't be encoded.
What ultimately touches the CPU, compiled or interpreted?
Only machine code — either emitted ahead of time (compiler) or via the interpreter program, which is itself machine code.

Connections

  • Binary and Number Systems — opcodes/operands are just binary numbers; log2k\lceil\log_2 k\rceil comes from here.
  • The CPU and Registers — registers are the operands instructions act on.
  • Memory and Addressing — the PC holds a memory address; programs live in memory; relocation fixes addresses.
  • Compilers and Interpreters — produce/execute the machine code that runs; bytecode vs native code.
  • Logic Gates and Boolean Algebra — execute step is physically done by gates.
  • Control Flowif/loops compile down to conditional jumps editing the PC.

Concept Map

translated by compiler or interpreter

assembler converts to

human-readable nickname is

encoded as

contains

contains

needs bits

needs bits

determines layout of

reads and decodes

tracks position with

drives

obeys

High-level code C or Python

Machine code

Assembly language

Raw binary bits

One instruction

Opcode = which operation

Operands = which data

bits = ceil log2 of k

CPU hardware

Program Counter

Fetch decode execute loop

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, computer asal mein bahut "bewakoof" machine hai — woh sirf kuch ginti-laayak simple kaam kar sakta hai: number copy karo, do number add karo, ya kisi dusri jagah jump kar jao. Jo C ya Python tum likhte ho, woh seedha CPU pe nahi chalta. Pehle usko translate karna padta hai chhote-chhote instructions mein, jo binary numbers ki shakal mein hote hain — isko bolte hain machine code. Yehi "actually run" hota hai.

Assembly koi alag jaadui cheez nahi hai — yeh bas machine code ka human-readable naam hai. Jaise ADD R1, R2, R3 likhna asaan hai bajaye 010011011 yaad rakhne ke. Ek instruction ke do hisse hote hain: opcode (kaun sa kaam) aur operands (kis data pe). Kitne bits chahiye? Agar kk cheezon mein se ek choose karni hai to log2k\lceil \log_2 k \rceil bits lagte hain — isiliye 8 operations ke liye sirf 3 bits kaafi hain.

Ek important baat jo log galat samajhte hain: C ko ek compiler pehle se native machine code mein convert kar deta hai. Lekin standard Python (CPython) aisa nahi karta — woh tumhare code ko pehle bytecode mein badalta hai, aur fir ek interpreter (virtual machine) loop us bytecode ko run karta hai. Yeh interpreter khud machine code hai jo CPU pe chal raha hai. Matlab Python seedha assembly mein convert nahi hota. Dono case mein CPU ko sirf machine code hi chhoota hai — bas timing aur "kaun karta hai" ka farak hai. Aur assembler sirf ek lookup table nahi hai: woh labels resolve karta hai (jaise ADDR1 ko real address banana), relocation, macro expansion, aur directives bhi handle karta hai.

CPU ek loop ki tarah chalta hai: Fetch (PC ke address se instruction padho), Decode (opcode/operand alag karo), Execute (kaam karo), phir Advance (PC agle instruction pe). Loop ya if chahiye to CPU sirf PC mein naya address daal deta hai — yehi jump hai. Yeh sab samajhna zaroori hai kyunki tab pata chalta hai code fast/slow kyun hai, debugging kaise karein, aur har CPU ka matlab uska ISA (x86, ARM, RISC-V) decide karta hai — binary universal hai, par uska meaning nahi.

Test yourself — How Computers Work

Connections