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 STP is 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 CI register
  • Decodes the instruction into opcode and address
  • Executes the instruction using the A register

3. The Instruction Set

0 — JMPJump to the address at memory[addr]
1 — JRPJump relative by memory[addr]
2 — LDNLoad the negative of memory[addr] into the accumulator
3 — STOStore the value of the accumulator at memory[addr]
4 — SUBSubtract memory[addr] from the accumulator
5 — CMPSkip next instruction if the accumulator is negative
6 — STPHalt 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.



Written by Abdel-Rahman Mobarak
Abdel-Rahman Mobarak