Around a year ago, I built and threw a Little Man Computer (“LMC”) simulator, assembler, and a really crappy disassembler and put them up on GitHub. There’s another version in C++ up there as well, but it isn’t as fully built out or stable.
“Computer Science” in my high school days consisted of keyboarding and some QBASIC — generally typing simple commands out of a book and then learning what simple things like IF loops and the like were. Even in the mid 2000’s, Illinois State University was using FORTRAN to teach it, and for that reason I was out.
So I don’t have a formal software engineering or computer science degree, so I’m going to explain things as I understand them. Maybe this is best, maybe compsci 101 is.
What is an LMC?
Invented in 1965, a Little Man Computer was designed not out of circuits and components, but out of paper. Like the CARDIAC computer, it was used to teach students how computers work from a foundational level. It’s analog in the sense that it is similar enough to how they work to get a grasp, but distant enough so you’re not doing bitshifting or otherwise working with binary, hexadecimal, or other less familiar numeric bases.
Memory: How is code and data stored?
The total memory of the device is “100 cells”, each cell can store a value between 0 and 999. Based on the Von Neumann architecture, these cells store everything a computer knows. The code it is executing and the data used for the processing. This is really important when I get into my disassembly blog.
There are also “Registers”, which are one cell (0-999) big memory locations where data can be stored. The LMC has three of these, the Accumulator, the Program Counter, and the Instruction register.
You can think of the accumulator as your scratch pad, whenever the processor is doing work, it uses the accumulator for a ton of functions:
- When collecting input, it is stored here
- When sending output, the data to send comes from here
- When you perform math against RAM, the operand is the value in the accumulator. You can’t say “what is x – 1” without first putting one of those values in RAM and another in the accumulator and then running the math operation
- When you copy values into memory, it comes from the accumulator
- When you copy values OUT of memory, it comes from the accumulator
Register: Program Counter
LMC programs run from the same area where the data is stored — so execution has to start somewhere. When an LMC program runs, it will start from the very first cell (Cell 00). Once it performs the fetch / execute cycle, it will do a few things:
- If this instruction tells us to abort, the execution stops
- If this instruction tells us to do a jump (conditional or otherwise), it will set the program counter (PC) to the new memory cell it should read from, and repeat
- Otherwise, it will iterate the program counter and repeat
While the fetch / execute cycle is running, the currently running instruction is pulled into this register.
If the last math operation resulted in a negative number, this flag will be TRUE, otherwise it is FALSE. Since these cells can’t store negative numbers, what happens when you subtract 50 from 5? Normally, you’d get -45.
But here, you’d get 45 — and the negative flag will be set to true. This is used by the BRP instruction you’ll learn more about below.
This computer would be similar to a RISC processor these days, with an incredibly short instruction set:
|Op Code||Mnemonic||What it does||Example Assembly|
|000||BRK||Stops program execution||BRK|
|1xx||ADD||Add value at RAM xx to accumulator||line ADD var1|
|2xx||SUB||Subtract value at ram XX from the accumulator||line SUB var1|
|3xx||STA||Copies the value in the accumulator to RAM at xx||STA var1|
|5xx||LDA||Set the value of the accumulator to the value in RAM at xx||LDA var1|
|6xx||BRA||Unconditionally branch to the memory location XX (set PC to XX)||BRA line|
|7xx||BRZ||If the accumulator is zero, branch to memory location XX (set PC to XX)||BRZ line|
|8xx||BRP||If the accumulator is positive, branch to memory location XX (set PC to XX)||BRP line|
|901||INP||Read an input value and set to the accumulator||INP|
|902||OUT||Read the value in the accumulator and output||OUT|
Opcode: Short for “Operation Code” is your machine code — this is what a “compiled binary” would look like on this machine. If you opened up an EXE or BIN file on your machine in a hex editor, what you’re seeing is potentially a bunch of Opcodes.
Mnemonic: Sounds like a really cool word, but it is just what letters humans will generally use when writing code for a particular machine. The LMC does not care about these letters, these are consumed by the assembler.
Even if you have never built software, you can get a quick idea of what this does: Input, Output, Add, Subtract, perform memory operations, and stop execution.
That is wildly less than modern processors, which have an Opcode table likely like this one. These allow multiplication, division, magic, and various memory addressing modes. This isn’t important here.
In an incredibly easy to write example of LMC Assembly, these two lines will prompt you for a number (0-999), and then echo that out to the screen.
A great deal of magic is happening here. Your real computer would have interrupts for the keys you type, and would have to alter video memory to output data using character sets so you can see what you type and read what comes out.
In the next blog, we’ll talk about assembly to machine code — how this above turns into machine code, and why people don’t write machine code.
I’m going to be reading the comments in this blog series, and when it concludes, I will reach out to the person with the best comment or question. I have one copy of the below book I’ll send out to that person if they will share their address with me. It’s not my book, I just have two of them and want to give one away to somebody who is interested.