← All posts

CPUSim: the CPU simulator I built for Zanichelli

I was reminded today of CPUSim, a browser CPU simulator I built for Zanichelli in 2020 to show students how machine code runs, one glowing register at a time.

By 5 min readLeggi in italiano

I was reminded today of an old project of mine, CPUSim, and I couldn’t resist opening it up again. In the summer of 2020 I built it for Zanichelli, the Italian educational publisher, as a small simulator that shows students how a CPU executes machine code. You write a few lines of a toy assembly language, press play, and watch the instruction travel from memory to the registers while each part of the diagram lights up in red. It was super fun to work on, and reading the code six years later made me smile more than once.

TL;DR

  • CPUSim is a teaching tool, described in the repo as “a CPU simulator used in schools to teach machine code”.
  • It animates the fetch–decode–execute cycle on a diagram with registers, an ALU, buses and 1,000 cells of RAM.
  • The instruction set has about twenty mnemonics, three addressing modes and a stack.
  • I built it between May and July 2020 with React, TypeScript, Redux Toolkit, the Monaco editor and an Ohm grammar, and packaged it for Windows with Electron.

What the simulator shows

On one side of the screen there’s a code editor for the program, plus smaller editors for the data and the stack. On the other there’s an SVG drawing of the CPU: the operand registers R0 and R1, the accumulator A, the index register IX, the stack pointer SP, the program counter PC, the instruction register IR, a decoder, an ALU, the memory address and memory data registers (MAR and MDR), and the address and data buses that connect everything to RAM.

Memory is one address space split into three sections: cells 0 to 99 hold code, 100 to 499 hold data, and 500 to 999 are the stack. Every register is clickable, so a student can set a value by hand before running a program. There’s a speed slider, pause and resume, a button to execute one instruction at a time, and you can save a program to a file and open it again later.

The spec I worked from is a PDF that’s still in the repo, in Italian. It describes the architecture, lists every instruction, and for each one says exactly which components should light up, and in which order. One detail I love: a footnote says MAR and MDR exist only in the graphics, because the RAM is really just an array.

The instruction set

The language is small enough to learn in a lesson:

  • SET R0 #5 loads a constant into a register.
  • ADD, SUB, MUL and DIV always compute R0 op R1 and put the result in A.
  • MOV R0 copies A back into a register, and INC IX / DEC IX step the index register.
  • LOD R0 120 and STO 120 read and write memory. LOD R0 @3 adds IX to the address, and $ makes the address relative to SP.
  • JMP, JMZ, JML and JMG jump always, or only when A is zero, negative or positive.
  • PSH, POP, CAL and RET work with the stack, and HLT stops the machine.

The syntax lives in a grammar written with Ohm, which parses each line as you type and marks errors in the editor. It reads almost like the spec:

Set = "SET" SetRegister "#"Integer

Lod = "LOD" LodBody

Fetch, decode, execute

The default program in the editor is five lines long:

SET R0 #1
SET R1 #2
ADD
STO 100
HLT

Take the ADD, which sits at address 2 (the editor numbers lines from 0, like memory). In the fetch phase, PC holds 2. PC, the address bus and MAR light up, then memory cell 2, then MDR, the data bus and IR,. PC lights up once more as it’s incremented to 3, ready for the next round. In the decode phase, IR and the decoder light up. In the execute phase, R0 and R1 light up, then the ALU, then A, which now holds 3. The next instruction, STO 100, sends that 3 over the data bus to cell 100, the first cell of the data section, and HLT ends the program.

The code for this is pleasantly literal. Each instruction is an async Redux thunk that switches lights on and off with a pause in between. This is the fetch phase, lightly trimmed:

const instruction = cpu.codeMemory[cpu.pc]
const animationInterval = computeAnimationInterval(cpu.executionSpeed)

dispatch(setLightsFetchStart(true)) // PC, address bus, MAR
await sleep(animationInterval)

dispatch(setLightsFetchStart(false))
dispatch(lightRamAddress({ address: cpu.pc, light: true }))
await sleep(animationInterval)

dispatch(setLightsFetchEnd(true)) // MDR, data bus, IR
await sleep(animationInterval)

dispatch(setLightsFetchEnd(false))
dispatch(lightPc(true))
dispatch(incrementPc())

If you look closely, the simulator cheats a little. The instruction is already read out of the array on the first line, before a single light turns on. Everything after that is theatre for the students. For a teaching tool that’s the right trade-off: the state changes are simple reducers, and the effort goes into making each step visible and slow enough to follow.

How it was built

The whole history is in git: 114 commits between 22 May and 28 July 2020, ending with version 1.0.0. It’s a Create React App project in TypeScript, with Redux Toolkit for the CPU state, Tailwind for the layout and the Monaco editor (the one inside VS Code) with custom syntax highlighting and autocomplete for the instructions. The CPU diagram is a big inline SVG whose colours and values come straight from the Redux state, and a good part of the history is commits like “Align address bus label” and “Draw lines between ALU and registers”. In the last commits I added Electron and an installer build for Windows, so it could run as a desktop app.

Why I still think about it

These days I spend my time on real-time systems and AI agents, very far from R0 and R1. Still, having to make every step of the fetch–decode–execute cycle visible was a great exercise, and the habit of asking what actually happens underneath has stayed useful. When a Postgres query is slow, the answer is often in how rows are read from disk. When a model is slow to serve, it’s often in how memory moves on the GPU. The abstractions are great, until the moment you need to see through them.

If you’re curious about what I’ve worked on since, there’s more about me here. And if you teach computer science and want to play with CPUSim, the code is on GitHub.