A comprehensive, production-ready implementation of the Little Computer 3 (LC-3) architecture featuring a virtual machine, two-pass assembler, and interactive TUI debugger.
- Features
- Quick Start
- Installation
- Usage
- Architecture
- Test Suite
- Project Structure
- Development
- Technical Reference
- License
- Acknowledgments
- Complete LC-3 ISA: All 15 instructions fully implemented
- PSR (Processor Status Register): Privilege modes (Supervisor/User), priority levels, condition codes
- Exception Handling: Privilege violations, illegal opcodes, access violations
- Memory-Mapped I/O: Keyboard status/data registers (KBSR/KBDR)
- Dual Stack Management: Separate User and Supervisor stack pointers
- Cross-Platform: Linux, macOS, Windows support via platform abstraction layer
- Two-Pass Assembly: Efficient symbol resolution with forward reference support
- Full Instruction Set: All 15 LC-3 opcodes + pseudo-instructions (trap aliases)
- Pseudo-Operations:
.ORIG,.FILL,.STRINGZ,.BLKW,.END - Symbol Table: Hash table implementation for O(1) label lookup
- Smart Parsing: Labels work with or without colons (
:) - Error Reporting: Clear, line-numbered error messages
- Verbose Mode: Optional symbol table output for debugging
- ncurses-Based Interface: Professional split-screen layout
- Live Register Display: All registers including PC, PSR with privilege/condition flags
- Disassembly View: Real-time instruction decoding with PC indicator
- Memory Inspector: Hex dump with scrolling capability
- Breakpoints: Set/clear at any address with visual markers
- Step Execution: Single-step through instructions
- Run Controls: Run until breakpoint or HALT
# Clone and build
git clone <repository-url>
cd LC3_VM-in_C
make
# Write assembly
cat > program.asm << 'EOF'
.ORIG x3000
LEA R0, MSG
PUTS
HALT
MSG .STRINGZ "Hello, LC-3!"
.END
EOF
# Assemble
./lc3asm program.asm
# Run
./lc3vm program.obj
# Debug (interactive TUI)
./lc3vm -d program.objOutput:
Hello, LC-3!
HALT
| Requirement | Version | Purpose |
|---|---|---|
| GCC/Clang | 4.9+ | C compiler |
| Make | 3.81+ | Build system |
| ncurses-dev | 5.0+ | TUI debugger |
sudo apt install build-essential libncurses-devbrew install gcc ncursesmake # Build everything
make clean # Clean build artifacts
make rebuild # Clean + buildBuild Outputs:
lc3vm- Virtual machine executablelc3asm- Assembler executablebuild/*.o- Object files
./lc3vm [options] <program.obj>Options:
| Flag | Description | Example |
|---|---|---|
-v |
Verbose mode (log every instruction) | ./lc3vm -v program.obj |
-d |
TUI debugger mode | ./lc3vm -d program.obj |
Example (Verbose Mode):
$ ./lc3vm -v hello.obj
[PC:0x3000] 0xE005 | LEA R0, #5 | PSR:0x0002[S,Z] | R0:0x3006
[PC:0x3001] 0xF022 | PUTS | PSR:0x0002[S,Z] | R0:0x3006
Hello, World!
[PC:0x3002] 0xF025 | HALT | PSR:0x0002[S,Z]
HALT./lc3asm [options] <input.asm>Options:
| Flag | Description | Example |
|---|---|---|
-v |
Verbose mode (show symbol table) | ./lc3asm -v program.asm |
-o <file> |
Specify output file | ./lc3asm -o out.obj program.asm |
Example Assembly File:
.ORIG x3000
; Calculate 5 + 3
AND R0, R0, #0 ; Clear R0
ADD R0, R0, #5 ; R0 = 5
ADD R1, R0, #3 ; R1 = 8
; Print result (hardcoded for demo)
LD R0, ASCII_8
OUT
HALT
ASCII_8 .FILL x38 ; ASCII '8'
.ENDAssemble & Run:
$ ./lc3asm -v example.asm
LC-3 Assembler
Input: example.asm
Output: example.obj
Pass 1: Building symbol table...
Origin: 0x3000
Symbols: 1
=== Symbol Table (1 symbols) ===
Symbol Address
----------------------------------------
ASCII_8 0x3007
Pass 2: Generating code...
✓ Assembly successful!
example.obj created
$ ./lc3vm example.obj
8HALTLaunch:
./lc3vm -d program.objInterface Layout:
┌─ Registers ─────────────────┬─ Disassembly ─────────────────────────────┐
│ R0: 0x0005 R4: 0x0000 │ → 0x3000: 0x5020 AND R0, R0, #0 │
│ R1: 0x0008 R5: 0x0000 │ 0x3001: 0x1025 ADD R0, R0, #5 │
│ R2: 0x0000 R6: 0xFE00 │ * 0x3002: 0x1243 ADD R1, R0, #3 [BP] │
│ R3: 0x0000 R7: 0x0000 │ 0x3003: 0x2004 LD R0, #4 │
│ PC: 0x3000 PSR:S,Z │ 0x3004: 0xF021 OUT │
├─ Memory ────────────────────┴───────────────────────────────────────────┤
│ 3000: 5020 1025 1243 2004 F021 F025 0000 0038 .%"C..!.%...8 │
│ 3008: 0000 0000 0000 0000 0000 0000 0000 0000 ........ │
├─ Command ───────────────────────────────────────────────────────────────┤
│ s=step r=run b=break m=mem q=quit │
└─────────────────────────────────────────────────────────────────────────┘
Key Bindings:
| Key | Action | Description |
|---|---|---|
s / Space |
Step | Execute single instruction |
r |
Run | Run until breakpoint or HALT |
b |
Breakpoint | Toggle breakpoint at current PC |
m |
Memory | Set memory view to current PC |
↑ / ↓ |
Scroll | Scroll memory view up/down |
q |
Quit | Exit debugger |
Visual Indicators:
→Current PC position*Breakpoint markerS/UPrivilege mode (Supervisor/User)N/Z/PCondition codes (Negative/Zero/Positive)
┌──────────────────────────────────────────────────────────┐
│ LC-3 Virtual Machine │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Registers │ │ Memory │ │ I/O Devices │ │
│ ├─────────────┤ ├──────────────┤ ├────────────────┤ │
│ │ R0 - R7 │ │ 65,536 words │ │ KBSR (xFE00) │ │
│ │ PC │ │ 16-bit each │ │ KBDR (xFE02) │ │
│ │ PSR │ │ │ │ DSR (xFE04) │ │
│ │ IR │ │ │ │ DDR (xFE06) │ │
│ │ USP/SSP │ │ │ │ │ │
│ │ CC (N/Z/P) │ │ │ │ │ │
│ └─────────────┘ └──────────────┘ └────────────────┘ │
│ │
├──────────────────────────────────────────────────────────┤
│ Instruction Cycle: FETCH → DECODE → EVALUATE → EXECUTE │
└──────────────────────────────────────────────────────────┘
| Register | Width | Description |
|---|---|---|
| R0-R7 | 16-bit | General-purpose registers |
| PC | 16-bit | Program Counter |
| PSR | 16-bit | Processor Status Register |
| IR | 16-bit | Instruction Register (internal) |
| USP | 16-bit | User Stack Pointer (saved) |
| SSP | 16-bit | Supervisor Stack Pointer (saved) |
15 14-11 10-8 7-3 2 1 0
┌───┬───────┬──────┬───────┬─────┬─────┬─────┐
│ P │ (res) │ PRI │ (res) │ N │ Z │ P │
└───┴───────┴──────┴───────┴─────┴─────┴─────┘
│ │ └─────────┘
│ │ Condition Codes
│ │
│ Priority Level (0-7)
│
Privilege Mode (0=Supervisor, 1=User)
| Address Range | Size | Description |
|---|---|---|
0x0000-0x00FF |
256 words | Trap Vector Table |
0x0100-0x01FF |
256 words | Interrupt Vector Table |
0x0200-0x2FFF |
11,776 words | Operating System |
0x3000-0xFDFF |
52,224 words | User Program Space |
0xFE00-0xFE01 |
2 words | Keyboard Status/Data (KBSR/KBDR) |
0xFE02-0xFE03 |
2 words | Display Status/Data (DSR/DDR) |
0xFE04-0xFFFF |
508 words | Device Registers |
ADD DR, SR1, SR2 ; DR = SR1 + SR2
ADD DR, SR1, imm5 ; DR = SR1 + SEXT(imm5)
AND DR, SR1, SR2 ; DR = SR1 & SR2
AND DR, SR1, imm5 ; DR = SR1 & SEXT(imm5)
NOT DR, SR ; DR = ~SR
LD DR, label ; DR = mem[PC + SEXT(PCoffset9)]
LDI DR, label ; DR = mem[mem[PC + SEXT(PCoffset9)]]
LDR DR, BaseR, offset6 ; DR = mem[BaseR + SEXT(offset6)]
LEA DR, label ; DR = PC + SEXT(PCoffset9)
ST SR, label ; mem[PC + SEXT(PCoffset9)] = SR
STI SR, label ; mem[mem[PC + SEXT(PCoffset9)]] = SR
STR SR, BaseR, offset6 ; mem[BaseR + SEXT(offset6)] = SR
BR[nzp] label ; Conditional branch
JMP BaseR ; PC = BaseR (RET is JMP R7)
JSR label ; R7 = PC; PC = PC + SEXT(PCoffset11)
JSRR BaseR ; R7 = PC; PC = BaseR
TRAP trapvect8 ; Execute system call
RTI ; Return from interrupt (privileged)
; Trap Aliases:
GETC ; TRAP x20 (Read character, no echo)
OUT ; TRAP x21 (Write character in R0)
PUTS ; TRAP x22 (Write string at address in R0)
IN ; TRAP x23 (Prompt + read character)
PUTSP ; TRAP x24 (Write packed string)
HALT ; TRAP x25 (Stop execution)
The project includes a comprehensive test suite covering all VM features:
| Test | Description | Features Tested |
|---|---|---|
hello.asm |
Print "Hello, World!" | LEA, PUTS, .STRINGZ |
test_basic.asm |
Arithmetic operations | ADD, AND, immediate values |
test_loop.asm |
Countdown 9→0 + "Blastoff!" | Loops, BR, LD, ST, OUT |
test_fibonacci.asm |
Calculate Fib(10) = 55 | JSR, RET, subroutines |
test_stack.asm |
Stack push/pop (1,2,3 → 3,2,1) | LDR, STR, R6 manipulation |
test_io.asm |
Interactive echo (quit on 'q') | GETC, OUT, input buffering |
# Assemble all tests
for f in test_*.asm hello.asm; do ./lc3asm "$f"; done
# Run test_loop
./lc3vm test_loop.objExpected Output (test_loop.obj):
9
8
7
6
5
4
3
2
1
0
Blastoff!
HALT
./lc3vm -d test_fibonacci.objPress s repeatedly to step through Fibonacci calculation, or r to run to completion.
LC3_VM-in_C/
├── src/ # Virtual Machine source
│ ├── lc3.h # Core definitions, VM struct, prototypes
│ ├── main.c # Entry point, argument parsing
│ ├── cpu.c # Instruction execution (op_add, op_ld, etc.)
│ ├── memory.c # Memory read/write with MMIO
│ ├── platform.c # OS abstraction (terminal control)
│ ├── disasm.c # Instruction disassembler
│ ├── utils.c # Utility functions (sign extend, swap)
│ ├── privilege.c # Privilege mode switching
│ ├── exception.c # Exception handling (violations, illegal ops)
│ ├── tui.c # TUI debugger implementation
│ └── tui.h # TUI structures and prototypes
│
├── assembler/ # Assembler source
│ ├── asm.h # Token types, symbol table, prototypes
│ ├── lc3asm.c # Main assembler entry point
│ ├── lexer.c # Tokenization (labels, opcodes, numbers)
│ ├── parser.c # Two-pass logic
│ ├── symtab.c # Symbol table (hash table)
│ ├── encoder.c # Instruction encoding
│ ├── error.c # Error reporting
│ └── util.c # Number parsing, label resolution
│
├── build/ # Build artifacts (*.o files)
├── Makefile # Build system
├── README.md # Project documentation
├── .gitignore # Git ignore rules
│
└── Tests:
├── hello.asm
├── test_basic.asm
├── test_loop.asm
├── test_fibonacci.asm
├── test_stack.asm
└── test_io.asm
cpu.c(250 lines): Implements all 15 LC-3 instructionsmemory.c(1.5K): Memory read/write with MMIO device registersprivilege.c(1.2K): Supervisor/User mode switching and stack managementexception.c(1.7K): Exception handling (push PSR/PC, vector table jump)tui.c(10K): Interactive debugger with ncurses windows
lexer.c(140 lines): Tokenizes assembly source into structured tokensparser.c(200 lines): Two-pass assembly (symbol table build + code generation)symtab.c(90 lines): Hash table for O(1) label lookupencoder.c(500+ lines): Encodes all 15 instructions + pseudo-ops to machine code
# Full rebuild
make clean && make
# Build only VM
make lc3vm
# Build only assembler
make lc3asm
# Debug build (with symbols)
make debug| Target | Description |
|---|---|
all |
Build VM and assembler (default) |
lc3vm |
Build VM only |
lc3asm |
Build assembler only |
clean |
Remove build artifacts |
rebuild |
Clean + build all |
debug |
Build with -g -DDEBUG |
CFLAGS = -Wall -Wextra -std=c11 -O2
LDFLAGS = -lncurses # For TUI1. Illegal Instruction Detected
2. Check Privilege Mode
3. Switch to Supervisor Mode
4. Save USP, restore SSP
5. Push PSR to supervisor stack
6. Push PC to supervisor stack
7. Load exception vector (0x0100-0x01FF)
8. Jump to handler
Exception Vectors:
0x0100: Privilege Mode Violation0x0101: Illegal Opcode0x0102: Access Violation
Pass 1: Symbol Table Construction
1. address = 0
2. For each line:
a. If label: add (label, address) to symtab
b. If .ORIG: address = value
c. If instruction: address += 1
d. If .FILL: address += 1
e. If .STRINGZ "str": address += len(str) + 1
f. If .BLKW #n: address += n
Pass 2: Code Generation
1. For each line:
a. If instruction: encode to 16-bit machine code
b. Resolve labels from symtab (PC-relative offsets)
c. Write to output file (big-endian)
For BR, LD, LDI, LEA, ST, STI:
offset = target_address - (PC + 1)
// Must fit in 9-bit signed (-256 to +255)ADD R0, R1, #5Encoding:
15-12 11-9 8-6 5 4-0
┌─────┬─────┬─────┬─────┬───────┐
│0001 │ 000 │ 001 │ 1 │ 00101 │
└─────┴─────┴─────┴─────┴───────┘
ADD DR=R0 SR1=R1 imm imm5=5
Result: 0x1265
This project is open source and available under the MIT License.
- LC-3 Architecture: Designed by Yale N. Patt and Sanjay J. Patel
- Educational Resource: Introduction to Computing Systems: From Bits and Gates to C and Beyond (2nd Edition)
- Write Your Own Virtual Machine by Justin Meiners - Excellent tutorial on LC-3 VM basics
- LC-3 Tools by Chirag Sakhuja - Reference implementation
- Built with C11, ncurses, and GNU Make
- Cross-platform terminal handling inspired by various open-source projects
Built with ❤️ for systems programming education
Note: This is an educational project. The LC-3 architecture is designed for teaching computer architecture and assembly language programming. This implementation extends the basic VM with professional features like exception handling, TUI debugging, and a complete assembler toolchain.
Found a bug or want to contribute?
- Check existing issues
- Create a detailed bug report or feature request
- Submit a pull request with tests
**Happy Hacking! **
