When I was in college, one of the CS professors presented a model of the Manchester Baby (it was presented as the Manchester Mark I, but that was a significantly more complicated computer), one of the earliest computers. It had 7 instructions (and honestly, it didn’t need them all).
For the purposes of this, here is some vocabulary for you:
- register – a small chunk of memory built into the computer.
- accumulator – a register that is typically used for arithmetic or logic. Simple computers will only have one.
- memory – short-term storage for data, readily accessible to the processor.
- address – a number that refers to a location in memory.
LDNEG address – read the value from memory at address and negate it and store it in the accumulator.
STORE address – store the accumulator into memory at address.
SUB address – subtract the value in memory at address from the accumulator and leave the result in the accumulator.
SKNEG – if the result of the last operator was negative, skip the next instruction, otherwise perform the next instruction.
BR address – continue execution at address
BRREL offset – add offset to current address and continue execution there
HALT – stop execution
That’s it. And honestly, everything that a modern computer can do is really a variation on this set in some way. The cool thing is that this computer was Turing complete. You can compute any computable function on it provided that there is enough memory (spoiler: there really wasn’t). The problem is that programs end up being very wordy to do even simple things. For example, let’s say that you wanted to write a program to add three and four. It should look like this:
What this does is reads 3 from a memory location, which becomes -3 because LDNEG negates it. It puts this into Scratch. Then it reads 4 from a memory location which becomes -4. It puts this into Scratch1, then it re-loads it and it becomes 4. Then it subtracts -3 which is 4 – -3 = 7 which we store in Result and halt. Ta-da!
And you immediately see the problem with this assembly language: it’s very chatty. It takes 2-3x the work of most other assembly languages. It does, however, break down the capabilities of a typical computer into these categories:
- Move values from/to memory and/or registers
- Perform (limited) arithmetic/logic
- Change flow unconditionally or unconditionally
The last one is where HALT falls. In addition, many processors include an instruction called NOP or NOOP which stands for No Operation. It’s an instruction that does nothing except consume time. Some processors include dedicated instructions for input and output which could go into either “move values” or “other”.
And while that seems like a depressingly small number of things, that’s really it.
In the class where this was presented, the professor offered a prize of $1 for the student who could come up with the shortest (correct) program (including data) to calculate factorial. For a computer that didn’t have multiplication, let alone addition, this was a pain, but straight forward. And quite honestly, this is where most of assembly language programming falls: it can be a pain, but it’s typically straight forward.