Have you ever what they used to program the Apollo Guidance Computer? Back in the early 1960s they didn't have higher level languages like Python, Java, and Ruby. Even C wouldn't be invented until 1972.
MIT was given the task of both designing the hardware and programming it for the moon landing. They first had to figure out how to make the computer small, reliable, and run on less power than a 60 light bulb. This was a big challenge since at the time most computers took up entire buildings, consumed a huge amount of power and needed frequent repairs.
The machine they built had the equivalent of 160 kilobytes of memory, was only one cubic foot in size and could execute an add instruction in 24 microseconds. That's incredibly slow by today's standards, but it was cutting edge at the time. And what programming language did this sweet new machine run on? ...dramatic pause... ASSEMBLY!!!
Assembly Language is a low level programming language used to give instructions directly to the CPU. It's basically one step above machine code (ie. 0100101110010000).
There are a lot of assembly languages out there. Each computer architecture, and sometimes operating system, has their own. Let's take a look at a basic computer architecture since it will help us understand how assembly languages work.
The drawing below, and the next few like it, are screen shots from an amazing YouTube video about assembly language that was posted by a guy with the username Javidx9. I encourage you to check it out if you more information about assembly language. A link to the video it can be found at the bottom of this post.
At their core, all computers boil down to a Central Processing Unit (CPU) and some Random Access Memory (RAM). The CPU receives data from the RAM, manipulates it in some way, and then sends the result back to be stored in the RAM.
RAM is really just an array of locations each with its own address. Data can be stored in these locations and then recalled later by referencing the appropriate address.
The CPU, on the other hand, has a few different components under the hood. First, it has some registers, which is a small amount of RAM inside the CPU. Next, there's the Arithmetic Logic Unit (ALU). The ALU is responsible for performing addition, subtraction, division, multiplication and any logic operators like AND or OR. Then, there is the status flags register. This is just more RAM that stores information about the status of the CPU and the ALU. CPU's will also have a Program Counter, which is responsibly for keeping track the location of the next instruction to be executed. The location stored in the Program Counter can be updated by a change in the Status Flags Register and there by cause a branch. Finally there is an Input/Output, which is how the CPU sends and receives data.
When your computer runs a program, a section of the RAM is allocated from Program Memory. This is where each instruction is stored. As the CPU finishes executing an instruction, it outputs the address in the Program Counter and the RAM sends back to the CPU the next instruction.
Now that we understand the basics of computer architecture, let's look at how assembly talks to the CPU. All assembly language instructions have this basic structure:
OPCODE OPRAND OPRAND
An OPCODE is an operation, like move this data or add this data. OPCODES are then followed by zero or more OPRANDS. OPRANDS are the arguments that are being operated on. These could be registers, values, or addresses that point to a location in memory.
As an example, let's say we have a super simple CPU that can only move data, add or subtract that data, and jump to a new instruction. The syntax for these instruction will be as follows:
MOV DESTINATION SOURCE
ADD REGISTER VALUE/REGISTER
SUB REGISTER VALUE/REGISTER
JMP CONDITION LOCATION
With these instructions, let's try to solve the age old problem of 2 + 2. For this example we are going to assume that the integer value 2 is stored in RAM at address 0.
MOV RA,  #Move the value at address 0 in RAM to register RA MOV RB,  #Move the value at address 0 in RAM to register RB ADD RA, RB #Add the value in RA and RB and store the result in RA MOV , RA #Move the value in RA to address 1 in RAM.
After the instruction about are executed we will have the integer value 4 in RAM at address 1 like we see below.
What if we wanted to multiple? Well multiplication is really just adding a number to itself a certain number of times. Let's try to multiply 5 * 3 using our four assembly OPCODEs. For this example, the integer value 5 will be in RAM at address 3, and the integer value 3 will be at address 4.
01 MOV RA,  #RA = 5 02 MOV RB,  #RB = 3 03 MOV RC, 0 #RC = 0 04 ADD RC, RA #RC = 5 + 0 = 5, RC = 5 + 5 = 10, RC = 10 + 5 = 15 05 SUB RB, 1 #RB = 3 - 1 = 2, RB = 2 - 1 = 1, RB = 1 - 1 = 0 06 JMP NZ, 04 #If zero status flag for ALU = TRUE, jump to line 04. 07 MOV RC,  #Move 15 to address 5 in RAM
In the example we see where the Status Flag register becomes important. It has a flag that is set to true if the output of the ALU is zero, and false if the output is not zero. The NZ (Not Zero) condition evaluates to true as long as the zero status flag is false. So above we see that the CPU jumps back to line 04 twice until line 05 returns 0. Then the CPU moves on the line 07 where it moves the result to address 5 in RAM, which looks as follows.
One important design feature of the assembly language used on the Apollo 11 is that it used a priority based executive, rather than a round robin, first-in first-out, or table division executive. This means that rather that tasks sharing the CPU or just running each task to completion through the CPU in the order they were called, the program will give some tasks priority access to the CPU over other tasks. So maybe the task of not crashing into the lunar surface is given priority over displaying a routine notification that your fuel is now at 90% instead of 91%. This is were that 24 microsecond ADD execution comes into play. When you have limited computing power/speed and you're in a box that is hurling through space towards a big rock, you want the computer to execute the instructions that are going to save your life first.
This exact situation happened during the Apollo 11 Moon landing. As they began their descent, Neil and Buzz left on the rendezvous radar, which is used when reconnecting the Lunar Module to the Command Module. They thought it would be a good idea in case they needed to do a quick abort. However, the computer was not designed to have the rendezvous radar and the Apollo guidance system on at the same time. This overloaded the computer and caused the system to start dumping low priority tasks. As these tasks were dumped yellow warning alarms started going off, which led to some pretty tense moments before Neil and Buzz ultimately landed safely on the Moon.
So the system worked! It's a great example of making sure that your software design matches how that software it's going to be used. And those programmers at MIT did it without stackoverflow.com or any other modern programming tools or aids.
Now, you wanna see some actually Apollo 11 code?! Believe or not the code used to program the Apollo 11 computer is on GitHub!!! Here a little section of the code for the Lunar Module:
If you want to see the complete code, I posted a link below.
Some of the take aways:
- Fundamentally, all computers are just a Central Processing Unit (CPU) and some Random Access Memory (RAM).
- RAM stores single instructions and the CPU executes those instructions.
- The instructions being executed are in Assembly Language, which is a low level programming language that tells the CPU what information to process and how to process it.
- And Finally, if and anyone asks, the programming language that put men on the moon was...Assembly Language!