So far, we’ve learned about the Little Man Computer architecture and how to write a simple program and assemble it into machine code. You likely already know how the code is executed due to the high quality comments I’ve included in that blog.
Regardless, I built a simulator and spent quite a bit of time on it (3 hours in the initial running cut).
This blog is focused on the PHP version of my LMC and not the C++ one, they’re similar enough where this shouldn’t matter, but the PHP one was built first and is likely more complete.
Remember, these are all available on my GitHub, so if you’d like to follow along in the code, go grab it.
The script starts by pulling in several other objects — everything from how the code is processed, how I/O is handled, to the RAM and Registers is its own file.
Next, the script grabs the file you provided on the command line, sanitizes it to prevent security issues, and then loads it directly into ram using the RAM->set() method.
It may seem unconventional, after all, when you use your computer, you don’t directly populate RAM with what you want, right? You actually do. Your processor calls upon your BIOS to handle what BIOS stands for… Binary Input Output System. Your BIOS (I know, UEFI and what not exists now) is configured to have instructions waiting at what is called the “initialization vector”. This is a specific place in memory where the CPU will look FIRST. The BIOS then performs its POST test on the system, and whatever other magic it does, before bootstrapping the normal system startup, adding more to RAM. On your PC and phone, everything isn’t in RAM, a lot of it exists in disk, but when it is used it will be.
This simply allows a register value to be read and set, via ->set() and ->get(). You’ll see registers updated occasionally, like the accumulator, and I do not talk about any error handling. That is due to the fact that this does all of that. If the value is <0 or >999, an exception is thrown and execution ends. If you attempt to read a value of a memory cell, it is undefined and will throw an exception and end. It should be noted that the ->get() failing on uninitialized data is not realistic in a real computer, you can read bytes that were never specified, but you’ll get garbage back (old memory, decaying memory state, electromagnetic interference, etc). It was just nicer to build with the extra constraint and is unlikely to be offensive.
Simply stores a 1-bit value (a boolean) and throws a flag if the flag was never initialized. Again, the read failure on uninitialized values isn’t realistic, but it is unlikely to be an issue unless you modify the LMC code itself.
Can either hold an array of responses to input requests, or you can use the keyboard module instead. This is more for headless operation and is unlikely to be used for most people.
Captures text entered at the keyboard, until enter is pressed, and returns it.
Simply sets data values and gets them, does not actually handle output. This is for capturing responses if desired. The LMC.class.php handles actually echo’ing out the info.
One of the more complicated classes, this one allows setting cell (RAM) values, however it ensures integrity of the request. You cannot request data from a cell <0 or >99, and you cannot set a value <0 or >999. Any of these erroneous conditions will throw an exception.
In addition, you can dump the entire RAM picture and also import it. This is exactly how your computer hibernates — it saves the complete current RAM state and then shuts down, upon being turned on again, it pulls that from disk and restores it into RAM to get you back to where you were.
Next it pulls in:
The magic method __construct() is a constructor, it runs as soon as LMC.class.php’s class is initialized. All this does is set properties to relate to the startup parameters for RAM, registers, etc.
Next, within ::run(), an infinite loop is created. Most folks know infinite loops are bad, but your processor is just that. It will loop the code in it forever. Remember the program counter register from earlier? It increments, when it hits the end of the memory, it simply wraps back to the start again.
Next, the instruction register is set with the current RAM location’s value according to the program counter, this is then processed by using the first number as the opcode and the following two numbers as the location. So “205” becomes OpCode 2, Location 05.
000 – HLT: Stop Execution
The first OpCode we’ll encounter is probably the easiest to implement as well. HLT (or Halt) simply just breaks out of the loop and exits. The script has completed at this point. Some assemblers and documentation also have a “COB” mnemonic that does the same thing for “Close Of Business”, but this machine works only on the opcodes after assembly, so we’ll disregard that.
Also note that every one of these opcodes have logging built in for the folks who like to read what happened. I won’t mention it again, but it’s there.
1xx – ADD: Add number at xx to accumulator
- Gets the value of RAM at cell xx
- Gets the value of the accumulator
- If the negative flag is TRUE, set the return value of the accumulator as a negative number (invert the sign)
- If the sum of the RAM + Accumulator is positive, set the negative flag to false
- Set the accumulator to the new value (Remember there’s error checking within the register setter!)
2xx – SUB: Subtract number at xx from accumulator
- Get the value of RAM at cell xx
- Subtract value from accumulator (not sure why I don’t have it checking sign here?)
- Clear negative flag
- If the output of the subtraction is a negative number, negate the sign to be positive and set the negative flag
- Store the new value to the accumulator
3xx – STA: Store Accumulator value to xx
- Set the RAM location xx’s value to that of the accumulator
- Clear negative flag (Not doing this created bugs, but I feel like this should not need to be done, more work for v2)
4xx – LOL: Got you good
There are, oddly, no 4xx class opcodes, so the simulator will simply drop them. There were proposed ones by people who also built simulators, but the original one didn’t have them and I’d rather go with the standard closest to a standard than one folks would have differing opinions on.
5xx – LDA: Load value at xx into the accumulator
- Easy, sets the accumulator to the value at cell xx
6xx – BRA: Unconditional Branch to xx
Branching is interesting, it is what the actual brain looks like of a computer. This is where decisions are made. You may hear of IF statements, LOOPs, conditionals, even GOTO. All of this is handled by two types of branching: Conditional (based on some sort of condition, like if something is positive, equal, not equal, or is zero). There’s also unconditional, it’s happening if it is executed. BRA is an unconditional branch.
- Set the program counter register to xx. Important to realize that “xx” is the new location, not the data at “xx”.
7xx BRZ: Branch if accumulator is Zero
- If the accumulator value is zero, then set the program counter to “xx”
- If it is non-zero, no action is made and the main loop will iterate the program counter like normal and move onto the next step
8xx BRP: Branch if negative flag is false to xx
An easier way to remember this is “BReak if Positive”.
- If the negative flag is false, branch to “xx”
- If it is non-zero, continue like with BRZ
901: Get Input
Requests keyboard input and sets value to the accumulator
902: Send Output
Reads the value of the accumulator and outputs to the screen
You can see that computers are not as magic as people think they are, they are truly just oversized calculators. But, you tie in the ability to do billions of these operations in seconds, creative use of the output of these calculations (Drawing on the screen, playing music, etc) and you get really impressive results that are not nerdy. Please do try out building a few programs, I also have a bunch of sample programs in my repository, so check those out too! They’re not all mine, and some of them were from me searching and college students looking for homework help. It gave me a few ideas for fun challenges.
Swing back again for the section on disassembly too!
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.