Assembler & Simulator
An assembler that turns assembly language into 16-bit machine code, and a simulator that executes it — memory, registers, flags and all.
This project is two tools that meet in the middle. The assembler takes assembly language and returns 16-bit machine code; the simulator takes that machine code and executes it, reporting the state of memory and every register as it goes.
Writing both halves is what makes the exercise worth doing. The assembler forces you to be exact about instruction encoding, and the simulator immediately punishes you when you are not — every instruction you emit is one you then have to decode and execute. Together they give a complete picture of how assembly is translated and then actually run inside a computer system.
-
Assembler — assembly to 16-bit machine code
Handles every supported instruction, processes labels and variables, checks for illegal instructions with distinct error messages per failure mode, and emits the corresponding binary for error-free code.
-
Line-type parsing
Distinguishes the three shapes a line can take — a variable definition at the top of the program, a label followed by an instruction, or a bare instruction — and handles each correctly, including resolving labels to addresses.
-
Syntax handling across operand types
Manages opcodes, registers, memory addresses, and immediate values, each with its own encoding rules inside the 16-bit word.
-
Error detection
Detects and reports typos, undefined variables, misuse of labels, and incorrect syntax — with a distinct message per error type rather than a single generic failure.
-
Simulator — machine code execution
Loads and executes code from system memory, printing the program counter and all register values after every instruction, then dumping the complete memory state once the program halts.
The assembler reads assembly instructions — including variable definitions and labels — and produces one 16-bit binary word per line. Anything it can't encode comes back as an error message instead of silently wrong output.
var X
mov R1 $10
mov R2 $100
mul R3 R1 R2
st R3 X
hlt
0001000100001010
0001001001100100
0011000011001010
0010101100000101
1001100000000000
The simulator reads 16-bit machine code from stdin and is built from four distinct components, each mirroring a real part of the machine.
-
Memory (MEM)
Stores 512 bytes, initialized to zeros, and handles 8-bit address input.
-
Program Counter (PC)
An 8-bit register pointing at the instruction currently being executed.
-
Register File (RF)
Manages register values, covering the general-purpose registers and the FLAGS register.
-
Execution Engine (EE)
Executes each instruction by updating the register file and the program counter based on what it reads out of memory.
After every instruction the simulator emits the program counter alongside the full register state, then dumps all of memory once the program halts — so you can single-step a program and watch the machine change underneath it.
<PC (8 bits)> <space> <R0 (16 bits)> <space>...<R6 (16 bits)> <space> <FLAGS (16 bits)>
< 16-bit data>
...
< 16-bit data>
-
Assembler
Add your code to the Simple-Assembler directory and execute it with the script at Simple-Assembler/run.
-
Simulator
Add your code to the SimpleSimulator directory and execute it with the script at SimpleSimulator/run.
-
Automated testing
An automatedTesting harness evaluates commits across a range of options, so regressions in either half surface without manual re-checking.
Further reading: Jaideep's write-up.