I rebuilt a computer in TypeScript - here's how it works
2025-10-24
Manchester Baby?
In 1948, the Manchester Baby became the first computer to execute a program stored in electronic memory. The computer consisted of 32 words of RAM, each being 32 bits long, and an instruction set of seven operations. To understand how early computers worked, I decided to recreate this machine in TypeScript
Why I Built It
I wanted to understand how early computers worked at the hardware-logic level, and how they used binary operations, registers and memory to perform calculations. Simulating this machine forced me to think about every step the CPU performs: fetching instructions, decoding them, executing them, and writing the result back to memory.
How It Works
The simulator I created is built around three main components:
- Memory (32 Words): Each word is 32 bits long, and acts as both program and data storage.
- Registers:
A: Accumulator (for arithmetic)CI: Current Instruction (program counter)PI: Present Instruction (fetched word)
- Control Loop: fetch → decode → execute → repeated until
STPis encountered
Core Functions
1. Creating and Resetting the Machine
export function newMachine(initial?: Word[]): State {
const mem = new Array<Word>(MEM_SIZE).fill(0);
if (initial) {
for (let i = 0; i < Math.min(initial.length, MEM_SIZE); i++) {
mem[i] = toInt32(initial[i]);
}
}
return {memory: mem, A: 0, CI: 0, PI: 0, running: true};
}
This sets up the 32-word memory and clears all of its contents. It also initializes the registers to their default values.
2. Fetching, Decoding, and Executing Instructions
export function stepInstruction(s: State): State {
if (!s.running) return s;
fetchInstr(s);
const decoded = decode(s.PI);
executeDecoded(s, decoded);
return s;
}
Each cycle:
- Fetches the next instruction from memory using the
CIregister - Decodes the instruction into opcode and address
- Executes the instruction using the
Aregister
3. The Instruction Set
0 — JMP | Jump to the address at memory[addr] |
1 — JRP | Jump relative by memory[addr] |
2 — LDN | Load the negative of memory[addr] into the accumulator |
3 — STO | Store the value of the accumulator at memory[addr] |
4 — SUB | Subtract memory[addr] from the accumulator |
5 — CMP | Skip next instruction if the accumulator is negative |
6 — STP | Halt the machine |
With only these instructions, you were able to perform loops, conditions, and basic arithmetic operations.
Example Program
The program below loads a number, subtracts another, stores the result, and then halts.
machine.memory[0] = encode(Op.LDN, 10); // Load -M[10]
machine.memory[1] = encode(Op.SUB, 11); // Subtract M[11]
machine.memory[2] = encode(Op.STO, 12); // Store result in M[12]
machine.memory[3] = encode(Op.STP, 0); // Halt
machine.memory[10] = -5; // Initial Value
machine.memory[11] = 3; // Second Value
machine.memory[12] = 0; // Result
When you run it you get:
A = 2
M12 = 2
CI = 4
running = false
The accumulator now correctly computes 5 - 3 = 2, proving the machine works!
What I Learned
- How CPUs mask and wrap program counters
- The logic behind fetching, decoding, and executing instructions
- How something so simple can perform arbitrary computations
- How the first computers worked
Reflections & Resources
Building this in TypeScript taught me a lot about Computer Architecture and how early computers worked. Every program, no matter how complex it looks, is built upon moving numbers around and performing operations on them. Recreating the Manchester Baby showed me that you don't need thousands of dollars to understand the fundamentals, you just need curiosity, a bunch of code, and a few cups of coffee.