A simple Python-based simulator for the Little Man Computer (LMC) educational model. This simulator supports standard LMC instructions and includes an optional OTC instruction for ASCII character output.
The repository includes a /programs folder with example LMC programs:
sumN.lmc— sum of the first N numbersmultiply.lmc— multiplication by repeated additiondivide.lmc— integer division with quotient and remainderfibonacci.lmc— every Fibonacci number that fits in a word, with overflow detectionhello.lmc— prints "Hello, world!" using self-modifying codebubblesort.lmc— sorts an array in place with self-modifying indexed loads and storesarrays.lmc— table lookup using self-modifying codeascii.lmc— character output with OTC
I was introduced to LMC in IN1020 at UiO, where we used a browser-based LMC simulator. Nothing could be saved, so every session began by retyping the program. It only ran in the browser, it was slow, and it looked its age. I wanted a better way to write programs, save them, always see the information I wanted, and use it from the terminal. So I decided to write my own compiler and executor in Python, and used that to properly learn the ins and outs of LMC.
I hope you have as much fun with it as I did :)
- Supports LMC instructions:
LDA,STA,ADD,SUB,INP,OUT,HLT,BRZ,BRP,BRA - Supports DAT data declarations for memory
- OTC instruction to output ASCII characters
- Branch labels for loops and conditional execution
- Configurable memory size
- Debug mode to print CPU state and memory
- Step-by-step execution (
--step) for learning and debugging
You can customize the simulator with these settings in CONFIGS.py:
- Controls how many memory addresses your simulator has (default: 100).
- Sizes above 100 widen the machine word automatically so every cell stays addressable (e.g.
MEMORY_SIZE = 1000gives four-digit words:LDA 150= 5150,INP= 9001).
MEMORY_SIZE = 100- Sets the default for the
--debugflag (see Usage):DEBUG = Trueenables debug output on every run without passing the flag.
DEBUG = False- Clone the repository:
git clone https://github.com/triceraz/lmc-simulator
cd lmc_simulator- Ensure Python 3 is installed on your system.
Ensure you are in the project directory
Run the simulator with an LMC program file:
./main.py programs/arrays.lmc- Use
INPto enter numbers when prompted. OUTprints numeric values,OTCprints ASCII characters.
./main.py --debug programs/sumN.lmc- Prints the labels, a memory snapshot and a disassembly before execution, then a trace line (PC, ACC, negative flag, instruction) for every executed instruction.
./main.py --step programs/sumN.lmcShows each instruction before it executes and waits for a command:
- Enter — execute one instruction
- a number — run that many instructions before pausing again (handy in long programs like bubblesort)
- c — stop pausing and run to the end
- q — stop the program
--debug and --step combine cleanly: you get the pre-run dump first, then the same trace, paused at every instruction.
File: /programs/sumN.lmc
// Sum the first N numbers input by the user, e.g., N=5 → Sum=15
INP // Read N
STA COUNT // Store in COUNT
LDA ZERO
STA SUM // Initialize SUM = 0
LOOP LDA SUM
ADD COUNT
STA SUM // SUM += COUNT
LDA COUNT
SUB ONE
STA COUNT // COUNT -= 1
BRP LOOP // Loop while COUNT >= 0
LDA SUM
OUT // Output the total sum
HLT
// Data
COUNT DAT 0
SUM DAT 0
ZERO DAT 0
ONE DAT 1- Reads a number
Nfrom the user - Initializes a memory location
SUMto 0 - Loops from
Ndown to 0, addingCOUNTtoSUMeach iteration - Prints the total sum when finished
Example run:
| Input | Output |
|---|---|
| 5 | 15 |
| 12 | 78 |
File: /programs/bubblesort.lmc
LMC has no indexed addressing — an instruction can only reference one fixed
address. Bubble sort still needs arr[j] and arr[j+1] for a moving j, so
the program writes its own instructions while it runs.
The trick: with three-digit words, LDA 47 is stored as 547 and STA 47
as 347. Instructions are just numbers, so the program can do arithmetic on
them:
- +1 points an instruction at the next array element (
LDA 47→LDA 48) - −200 turns a load into a store (
LDA 47→STA 47)
Each round of the inner loop builds all four indexed instructions from a
single moving template CUR and stores them into exec slots — cells that
are executed right after being written:
INNER LDA CUR
STA XLOADJ // Exec slot <- LDA arr[j]
ADD ONE
STA XLOADJ1 // Exec slot <- LDA arr[j+1]
SUB C200
STA XSTAJ1 // Exec slot <- STA arr[j+1]
SUB ONE
STA XSTAJ // Exec slot <- STA arr[j]
XLOADJ DAT 0 // Runs as: LDA arr[j]
STA TMPA
XLOADJ1 DAT 0 // Runs as: LDA arr[j+1]
STA TMPBThe outer loop repeats passes until a pass makes no swaps. Equal neighbours are never swapped, so arrays containing duplicates terminate too.
Run it:
./main.py programs/bubblesort.lmcIt sorts 42, 7, 999, 0, 314, 7, 100, 55 in place and prints the result in
ascending order. To watch it rewrite itself one instruction at a time:
./main.py --step programs/bubblesort.lmc- Create a
.lmcfile in the/programsfolder - Write LMC assembly using supported instructions
- Run the simulator:
./main.py programs/your_program.lmcOUTprints numeric valuesOTCprints ASCII characters (0–127); values outside that range are an error- Values are words in the range 0–999 (with the default memory size):
ADDwraps around on overflow, andSUBbelow zero wraps and sets a negative flag thatBRP/BRZconsult DATandINPvalues must fit in the word range (0–999 by default)- Labels are case-insensitive
- Memory addresses are automatically assigned to
DATvariables - Configure
MEMORY_SIZEandDEBUGin CONFIGS.py - You can use unnamed DAT's after each other to make an array. Look at arrays.lmc for an example
- Assembly and runtime errors are reported with line numbers/addresses
- Run the tests with
python3 -m unittest