A C++17 implementation of a virtual machine for the LC-3 (Little Computer 3) — a simplified 16-bit educational computer architecture. The VM loads LC-3 machine code binaries and executes them by emulating the fetch-decode-execute cycle of the real hardware, including its registers, memory, condition flags, and memory-mapped I/O devices.
This project is a systems-programming exercise focused on low-level concepts: binary instruction decoding, two's-complement arithmetic, bitwise manipulation, endianness handling, and polymorphic instruction dispatch.
- Full LC-3 instruction set — all 16 opcodes (
ADD,AND,NOT,BR,JMP,JSR,LD,LDI,LDR,LEA,ST,STI,STR,TRAP,RTI, reserved) are implemented. - TRAP-based OS routines — keyboard input (
GETC,IN), character/string output (OUT,PUTS), and program termination (HALT). - Memory-mapped I/O — the keyboard status/data registers (
KBSR/KBDR) and the machine control register (MCR) are intercepted transparently during memory reads/writes. - 65,536 words of addressable memory and 8 general-purpose registers (
R0–R7), matching the real LC-3 architecture. - Condition flags (
N/Z/P) updated after every register write, driving conditional branching. - Raw terminal mode on Unix-like systems, so console programs can read keystrokes immediately without waiting for Enter.
- Big-endian binary loading —
.binprogram images are read and byte-swapped to the host's native endianness. - Extensible dispatch design — instructions are implemented as individual executor classes registered in an O(1) opcode-to-executor lookup table, making it straightforward to add or modify instructions.
The VM is organized around a small set of focused components:
| Component | Responsibility |
|---|---|
LC3VM |
Orchestrates the fetch-decode-execute loop; owns memory, registers, and condition flags. |
Memory |
65,536 x 16-bit word address space; intercepts memory-mapped I/O addresses on read. |
Registers |
The 8 general-purpose 16-bit registers (R0–R7). |
ConditionFlags |
Tracks the N/Z/P condition flags used by BR. |
InstructionRegistry |
Maps each 4-bit opcode to its IInstructionExecutor implementation for O(1) dispatch. |
IInstructionExecutor |
Interface implemented by each instruction (ADD, AND, BR, ...) in src/instructions.cpp. |
ITrapRoutine |
Interface implemented by each OS trap routine (GETC, OUT, PUTS, IN, HALT) in src/trap_routines.cpp. |
ProgramLoader |
Reads a big-endian .bin image from disk and swaps it to host byte order. |
ALU |
Shared arithmetic/bitwise helpers (e.g. sign extension) used by instruction executors. |
Terminal |
Puts the host terminal into raw/non-canonical mode so keyboard TRAPs behave like real hardware input. |
LC3Runner |
Thin driver that configures the terminal, loads a program into an LC3VM, and runs it. |
| Opcode | Mnemonic | Description |
|---|---|---|
0b0000 |
BR |
Conditional branch |
0b0001 |
ADD |
Addition |
0b0010 |
LD |
Load |
0b0011 |
ST |
Store |
0b0100 |
JSR |
Jump to subroutine |
0b0101 |
AND |
Bitwise AND |
0b0110 |
LDR |
Load base + offset |
0b0111 |
STR |
Store base + offset |
0b1000 |
RTI |
Return from interrupt |
0b1001 |
NOT |
Bitwise complement |
0b1010 |
LDI |
Load indirect |
0b1011 |
STI |
Store indirect |
0b1100 |
JMP |
Jump |
0b1101 |
— | Reserved / unused |
0b1110 |
LEA |
Load effective address |
0b1111 |
TRAP |
Operating system call |
| Vector | Name | Description |
|---|---|---|
0x20 |
GETC |
Read a single character from the keyboard (no echo) |
0x21 |
OUT |
Write a character to the console |
0x22 |
PUTS |
Write a null-terminated string to the console |
0x23 |
IN |
Prompt and read a single character (with echo) |
0x25 |
HALT |
Stop execution and print a halt message |
| Address | Register | Purpose |
|---|---|---|
0xFE00 |
KBSR |
Keyboard status register |
0xFE02 |
KBDR |
Keyboard data register |
0xFE04 |
CRTSR |
Display status register |
0xFE06 |
CRTDR |
Display data register |
0xFFFE |
MCR |
Machine control register |
LC3/
├── inc/ # Public headers for the VM's components
│ ├── lc3vm.hpp # Core VM orchestrator
│ ├── memory.hpp # Memory subsystem
│ ├── registers.hpp # Register file
│ ├── cond_flags.hpp # Condition flags (N/Z/P)
│ ├── instructions.hpp # Concrete instruction executor classes
│ ├── iinstr_exe.hpp # Instruction executor interface
│ ├── instruction_registry.hpp # Opcode dispatch table
│ ├── trap_routines.hpp # Concrete TRAP routine classes
│ ├── itrap_routine.hpp # TRAP routine interface
│ ├── program_loader.hpp# .bin file loader
│ ├── terminal.hpp # Raw terminal mode helpers
│ ├── alu.hpp # Arithmetic/bitwise helpers
│ ├── bit_utils.hpp # Bit manipulation utilities
│ ├── constants.hpp # Opcodes, trap vectors, MMIO addresses
│ └── lc3runner.hpp # High-level program runner
├── src/ # Implementation files corresponding to inc/
└── tests/ # Test suite, sample programs, and build files
├── main.cpp # CLI entry point (build target: lc3vm)
├── test_utils.cpp # Unit tests for utility/ALU functions
├── test_instructions.cpp # Unit tests for instruction execution
├── Makefile # Build rules for the VM and test binaries
└── *.bin # Sample LC-3 programs (2048, fib22, print10, rogue)
The project is built with g++ (C++17) via the Makefile located in tests/.
cd tests
make # builds the lc3vm executable./lc3vm <path-to-program.bin>Example, using one of the bundled sample programs:
./lc3vm 2048.bin
./lc3vm fib22.bin
./lc3vm print10.bin
./lc3vm rogue.bincd tests
make checkThis builds and runs test_utils (ALU/utility function tests) and test_instructions (instruction execution tests).
The tests/ directory includes several pre-assembled .bin images that exercise the VM end to end:
2048.bin— the 2048 puzzle gamefib22.bin— Fibonacci sequence computationprint10.bin— prints the numbers 0 through 9rogue.bin— a small Rogue-like dungeon-crawler game
- A C++17-compatible compiler (e.g.
g++) - A POSIX-like terminal for raw keyboard input handling (see
terminal.hpp)