Machine code and assembly — what actually runs
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 bits for the opcode.
- 4 registers → bits to name each register.
Why ? With bits you can label distinct things, so to label things you need .
Step 3 — Lay out one 9-bit instruction ADD R1, R2, 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.

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
.pyto 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
ADDR1into 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?
What is assembly language?
Does standard Python compile to native assembly like C?
What does an assembler do beyond opcode→bits lookup?
What does a compiler do vs an interpreter?
What two parts make up a machine instruction?
How many bits to select one of k items?
Bits to encode an opcode with 16 possible operations?
What are the steps of the CPU's main loop?
What is the Program Counter (PC)?
How does a loop exist at the machine level?
What is an ISA?
Why does the same byte mean different things on different CPUs?
Why might an immediate constant be limited in size?
What ultimately touches the CPU, compiled or interpreted?
Connections
- Binary and Number Systems — opcodes/operands are just binary numbers; 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 Flow —
if/loops compile down to conditional jumps editing the PC.
Concept Map
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 cheezon mein se ek choose karni hai to 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.