Hand Assembly
Part of Programming Fundamentals
Hand assembly is the process of manually translating assembly language mnemonics into machine code bytes without a software assembler — the essential bootstrap skill for computing from scratch.
Why This Matters
Before you have an assembler program, before you have any software at all, how do you get the first program into the computer? You hand-assemble it — you look up each instruction in the CPU’s reference manual, find its opcode byte or bytes, and enter the binary values directly into memory using a front panel, a hex monitor, or a ROM programmer.
This skill is not obsolete even when assemblers exist. Understanding hand assembly means you can read a memory dump and recognize instructions, verify that a compiler or assembler produced correct output, diagnose a crashed program by examining the raw bytes at the instruction pointer, and write tiny critical routines directly when the toolchain is not available.
Every serious low-level programmer should be able to hand-assemble simple programs. It is the closest you can get to speaking the CPU’s native language, and that intimacy with the machine pays dividends throughout a programming career.
What Hand Assembly Requires
You need three things:
1. The CPU’s instruction set reference: A table listing every instruction, its mnemonic, its encoding (opcode byte(s), plus any operand bytes), and how many bytes and clock cycles it takes. This document is your codebook. For a Z80, the essential instructions fit on a few printed pages. For a 6502, a double-sided reference card suffices.
2. A working knowledge of your CPU’s registers and addressing modes: You must know that LD A, (HL) loads register A from the memory address currently in the HL register pair, that the opcode for this instruction is 0x7E, and that it encodes as a single byte. This knowledge comes from reading the reference manual and practicing.
3. A systematic way to track addresses: As you assemble instructions one by one, you must know the address of each instruction in the final program. Keep a running count, noting the address of each instruction as you go.
The Hand Assembly Process
Work through the assembly language source line by line:
Step 1: Read the mnemonic and operands of the current instruction.
Step 2: Find the instruction in the reference table. Note the opcode byte(s).
Step 3: For instructions with operand bytes (immediate values, memory addresses), note the operand byte(s) in the correct order (little-endian or big-endian depending on the CPU).
Step 4: Write down the address of this instruction (your running program counter), then write the assembled bytes in hex.
Step 5: Advance your program counter by the size of this instruction (1, 2, or 3 bytes for most 8-bit CPU instructions).
Step 6: Repeat for the next instruction.
Example: hand-assembling a short Z80 routine to add two numbers:
; Load A with 10, add 5, store result to address 0x2000
; Starting at address 0x0100
Addr Source Opcode Operand
0100 LD A, 10 3E 0A
0102 ADD A, 5 C6 05
0104 LD (0x2000), A 32 00 20
0107 RET C9
Assembled bytes: 3E 0A C6 05 32 00 20 C9
Enter these 8 bytes starting at address 0x0100, set the program counter to 0x0100, and run.
Two-Pass Hand Assembly
When instructions jump forward to labels not yet defined, you face the same problem as the assembler: you do not know the target address when you encounter the jump.
The solution is two passes:
First pass: Go through the entire program, computing the address of every instruction and recording the address of every label. Do not fill in forward reference addresses yet — leave placeholders.
Second pass: Go through again, filling in every jump target address now that all label addresses are known.
For simple programs of a few dozen instructions, experienced assemblers do this in one mental pass by estimating and adjusting. For anything larger, writing out the first pass label table first is necessary.
Calculating Jump Offsets
Some CPUs (6502, Z80 relative jumps) encode short jumps as signed offsets from the instruction following the jump, rather than as absolute addresses. You must calculate the offset.
Example on Z80: JR Z, TARGET is a 2-byte instruction. If the JR instruction is at address 0x0150, the offset is calculated from 0x0152 (the address after the JR instruction). If TARGET is at 0x0160:
offset = TARGET - (JR_address + 2) = 0x0160 - 0x0152 = 0x0E
Encode: 28 0E (JR Z encodes as 0x28, offset 0x0E).
For backward jumps, the offset is negative, encoded in two’s complement. If TARGET is at 0x0140:
offset = 0x0140 - 0x0152 = -0x12 = 0xEE (two's complement of 0x12)
Encode: 28 EE.
The valid range for relative jumps is typically -128 to +127 bytes (signed 8-bit offset). Jumps outside this range must use absolute addressing.
Memory Monitors and Front Panels
Before software tools exist, programs are entered through hardware interfaces:
Front panel switches: Each bit of a byte has a toggle switch; a set of switches controls the address and data buses. You toggle the switches to the byte value you want, toggle another switch to write it, advance the address, and repeat. This is how the Altair 8800 and similar computers were programmed.
Hex monitor ROM: A small program in ROM that accepts hexadecimal input from a keyboard and writes bytes to memory, displays memory contents, and can start execution at a specified address. This is a much more humane interface than toggle switches. Building a hex monitor is typically the first goal after hand-assembling a tiny bootstrap.
ROM programmer: External hardware that writes an assembled program into a ROM chip. You enter the program on another computer (or calculate it by hand), load it into the programmer, and the programmer burns the values into the chip. The ROM chip is then installed in the target computer.
Reference Tables for Common CPUs
For the Z80, frequently used single-byte instructions:
NOP : 00
LD B,B : 40 LD B,C : 41 LD B,D : 42 LD B,E : 43
LD A,B : 78 LD A,C : 79 LD A,D : 7A LD A,E : 7B
LD A,H : 7C LD A,L : 7D LD A,(HL): 7E LD A,A : 7F
ADD A,B : 80 ADD A,C : 81 ... ADD A,A : 87
ADD A,(HL) : 86
INC B : 04 INC C : 0C INC D : 14 INC E : 1C
INC H : 24 INC L : 2C INC (HL): 34 INC A : 3C
DEC B : 05 ...
DJNZ n : 10 nn (nn = signed offset)
JP nn : C3 lo hi (absolute jump)
JR n : 18 n (relative jump)
JR Z,n : 28 n JR NZ,n : 20 n JR C,n : 38 n JR NC,n : 30 n
CALL nn : CD lo hi
RET : C9
PUSH BC : C5 PUSH DE : D5 PUSH HL : E5 PUSH AF : F5
POP BC : C1 POP DE : D1 POP HL : E1 POP AF : F1
For two-byte instructions: LD A, n is 3E nn. LD HL, nn is 21 lo hi (little-endian).
Practical Notes for Rebuilders
Build a laminated reference card for your CPU with the most common instructions. You will look at it dozens of times per session while learning. Having it on paper rather than in a manual you must page through saves significant time.
Practice by hand-assembling short programs you have already written in a higher-level way. Write the BASIC or assembly source, then hand-assemble 10-20 lines of it. Check your result against what an assembler produces. Discrepancies immediately reveal where your encoding knowledge is weak.
Hand assembly is slow — perhaps 10-20 instructions per hour for a beginner — but it is the inescapable bootstrap activity. Spend a few hours mastering it thoroughly. Once you have an assembler, you will rarely need to hand-assemble, but you will often need to read and interpret machine code, which requires the same knowledge.