The Apollo Guidance Computer

Published on
71 min read––– views

Introduction

This post aims to detail the Apollo Guidance Computer (AGC) –the machine which placed man on the Moon– from an armchair computer architecture/systems enthusiast perspective. Villages of people have dedicated lifetimes to documenting, celebrating, refurbishing, emulating, etc. this machine – and I don't have much novel to add other than some diagrams and commentary I found useful to dilute the subject matter into my smoothbrain. 'Nuff downplay, let's get into it.

Designed at MIT between 1961 and 1969 from scratch specifically for use on board the Apollo spacecraft to support Moon landings between '69 and '72, 42 AGCs were built at ~$200K apiece.1 The base clock was 1.024 MHz, all data were defined in terms of 16-bits words, the computer was equipped with 2 kilowords of RAM, and 36 kilowords of ROM. The physical dimensions of an AGC are 55 x 33 x 15 cm, weighing 32 kilograms (70.5 lbs), and consuming 55 Watts. Of the 42 that were built, 7 Lunar Module AGCs crashed into the Moon, 3 Lunar Module AGCs burnt up in Earth's atmosphere, 11 returned from their missions intact and are stored away in NASA basements or Air and Space museums, leaving 21 undeployed machines left unaccounted for with the exception of a partially-complete unit on display at the History of Computing in Mountain View.

For reference, with respect to the significance of its specifications, performance, and size, the first mini computers circa 1965 were the size of a minifridge, were far too heavy, slow, and resource hungry to meet the requirements of the Apollo Program. A common method of quantifying performance of modern computers is FLOPs or Floating Point Operations, but since floating point numbers didn't formally exist at time time of the AGC's design or construction,2 and compute has obviously advanced by several orders of magnitude along any axis of performance, it almost makes more sense to benchmark other computers in terms of multiples of an AGC's computing power.

UNIVAC IDEC PDP-8AGCCommodore PETMIPSApple M1 Pro
year195119651966197719852022
# transistors5k10k16,0003,151110,00057 - 114 Bn
memory speed (KHz)0.5600851,0008.3 - 152,064,000 - 3,228,000
word width (bits)48121583264
kilo-adds / second1.938444††500~"1 million"5.2 teraflops†††
kilo-muls / second0.50.3223
weight (kg)7,300823212.2
power consumption (W)125,000400551010 - 15

† - The uppermost bit of every word was reserved for a parity-check, so words were effectively 15-bits.3

†† - The AGC was optimized for scientific computations, and therefore multiplications.

††† - a teraflop is 1 trillion FLOPs. When you start measuring things in terms of trillions of operations on a numerical representation which didn't exist at the time of the subject in question, the point becomes moot.4

Just 10 years after the AGC was built, microcomputers such as the Commodore PET wildly outclassed the AGC across the board, five decades later, the charging adapter of the laptop I'm composing this post on contains more compute power than the AGC.5 The AGC is so freaking cool, and digging through the grimy PDFs of NASA docs, the painstaking archives of history maintained by other enthusiasts who have dedicated their lives to maintaining the history of this thing,6 and then hours of YouTube videos on restoration projects.

This post is broken up into four main parts:

  1. Architecture
  2. Hardware / Peripherals
  3. System Software
  4. Mission Software

Table of Contents

here be contents ...

1 | AGC Architecture

Introduction

The Apollo Guidance Computer was very "sixties" – considered innovative for its time,7 its peripherals were indicative of its unique mission. The 15-bit effective word size, use of 1's Complement, and reliance on a rudimentary Accumulator in place of a dedicated ALU are very alien in the face of modern computer architectures. The designs of the integrated circuits, microcoded instructions, and core memory rope were groundbreaking.8 Gyroscopic accelerometers, radar, and propulsion jets, as well as the guidance and navigation software which enabled it to fly to the Moon, set the AGC apart from its contemporary and near-contemporary computers.

The Apollo Program

The AGC supported 12 missions of increasing swagger, ranging from government-tier RC enthusiasm to landing on the Moon:

  • 2 unmanned test missions conducted via ground remote control
  • 3 manned test missions
  • 7 manned landing missions

Computer control for a space mission was very novel for its time. Astronauts were resistant to relinquishing control of their ships to the binary voodoo cooked up by the nerds in Cambridge. While most of the mission could be manually executed as a fallback, the AGC eventually won out as the primary control system.

In order to understand the purpose of the AGC, we must understand the motivation and scope of its mission(s). Instead of landing the complete spacecraft on the Moon (for which an extremely large rocket would've been required), the plan consisted of landing the smaller Lunar Excursion Ferry while the larger craft with the fuel for the return trip remained in Lunar orbit.

9

Apollo Craft

10

The Apollo Spacecraft was composed of three Modules: the Command Module, the Service Module , and the Lunar Excursion Ferry (or Lunar Module).

Now, a supersonic overture of the mission, in order to get an idea for what sorts of tasks the AGC needs to complete. At the beginning of the three day journey, the Command and Service Module (CSM) extracts the Lunar Module (LM) and docks with it.

11

By decelerating on the far-side of the Moon, the craft enters Lunar orbit. Two astronauts board the Apollo via the docked intersection, the Lunar Module detaches and begins powered descent, landing (cue applause), to later ascend and rendezvous with the coupled Command and Service Module which had completed an orbit.

After re-docking, the Lunar Module is jettisoned (RIP bozo) and the remaining Modules accelerate around the darkside of the Moon to escape orbit and begin the return trajectory towards Earth (cue second round of applause).

For reentry, the Service Module is also jettisoned so just the Command Module remains.

Both the Command and Lunar Modules had an on-board AGC with unique interfaces and slightly different I/O devices, with software adapted for the specific spacecraft which the astronauts interfaced with via the display and keyboard units.

Overview

The AGC consists of a Von Neumann Accumulator machine with 15-bit, 1's complement Big Endian arithmetic. Its primary responsibilities included:

  • Maintaining the state vector position, speed\langle \text{position, speed} \rangle
  • Monitor and control Saturn V during launch
  • Stabilize attitude
  • Calculate and control engine burns

This section/post will detail what each of those characteristics mean and their implications of the architecture while simultaneously covering the following topics:

  • AGC Instruction set
  • Arithmetic
  • Instruction encoding
  • Memory model
  • I/O
  • Interrupts

Instruction Sets

Machine code instructions vary widely; the instruction set of a modern ARM processor –mainly optimized for runtime performance– consists of ~400 instructions,12 allowing for versatile instructions for myriad scenarios.

On the other end of the spectrum, we have Reduced Instruction Set Computers (RISC) processors. For example subleq13 is an academic One Instruction Set Computer (OISC) language that shows that a single instruction can be enough to solve the same problems as all other Turing Complete languages:

subleq a, b, c{M[k] M[b]  M[a];if M[b] 0  goto   c \text{subleq a, b, c} \begin{cases} \text{M[k] } \leftarrow \text{M[b] } - \text{ M[a];} \\ \text{if M[b] } \leq 0\; \mathtt{ goto } \; \text{ c } \\ \end{cases}

A computer from the sixties consisted of only a few thousand transistors whereas today's have billions. The AGC had 36 instructions placing it just to the right of subleq on the following scale while still satisfying the performance constraints of the mission:

AGC Instruction Set

There are 6 categories of AGC instructions:

  • Load/Store (9)
  • Arithmetic (10)
  • Logic (1)
  • Control flow (5)
  • I/O (7)
  • Interrupts (4)

Memory Model

The memory model is the cornerstone of the instruction set. Memory consists of 4096 cells numbered hexidecimally (The AGC docs are largely written in octal, but I CBF, octal memes abound in the footnotes) from $000 - $FFF (the dollar sign is used as a shorthand prefix for hex values). Each cell consists of a 15-bit word numbered between $0000 and $7FFF:

Registers and the Accumulator

Almost all changes to data in memory go through the 15-bit Accumulator also denoted as the A\text{A} register. Registers are simply named cells in memory. A program can copy words between the Accumulator and the memory cells, and can also add, subtract, multiply, and divide values as they're moved around:

Data in memory can have many meanings depending on how they're interpreted. Words might represent integers, or perhaps machine code instructions since each instruction has a unique identifier. Code and data sharing the same address space in the memory model makes the AGC a Von Neumann machine.

The next register of immediate importance is PC\text{PC}: the CPU's Program Counter. PC\text{PC} always holds the address of the instruction to be executed next. To see how these two registers interact, let's take a look at how the Load, Store, and Add instructions work in action.

  • Load follows the format ld register, [$address] and copies the contents of a given memory cell (address) into the specified register, typically the Accumulator: A\text{A}. After this instruction is performed, PC\text{PC} advances to the next instruction.
  • From the semantics of Load, we also get Store: ld [$address], register which simply does the inverse, loading the contents of the register into the target address. Like with all Load/Store instructions, the first argument is the destination, and the second is the source.
  • Add has the "signature" add register, [$address] and it adds the value at address to the value stored in the specified register, again typically A\text{A}.

Given the following machine instructions:

load a, [$390]
add a, [$391]
store [$392], a

The Accumulator performs the following operations:

Load

The Load instruction copies the value at $390 into A\text{A}.

Add

The Program Counter advances, and then the value stored at $391 is added to A\text{A}, overwriting A\text{A} in the process:

Store

The Program Counter advances again, and the value stored in the Accumulator is written to the cell at address $392.

Instruction Set Continued

We can document all 36 instructions with diagram cards of the following form:

For readability sake, I've included modernized arm-inspired syntax alongside the esoteric AGC instructions which appear in all their glory in the source code.14

We've already seen Load, Store, and Add:

Load

e.g. ld a, [k] read "load a indirect k" which loads the value at memory address k, and copies it into the Accumulator A\text{A}.

Store

e.g. ld [k], a read "store a in indirect k" which copies the value in the Accumulator into memory address k.

Add

e.g. add a, [$k] read "add indirect k to a" which add the value at k to A\text{A}.

Exchange

Next we have Exchange xchg a, [k] which atomically swaps the contents of the target register a and a memory cell k.

Subtract

From Add, we also get Subtract which subtracts the value at k from A\text{A}.

Now, the result of subtraction can be negative, so we briefly need to pause to discuss how negative numbers are expressed on the AGC.

1's Complement

Consider first the set of 4-bit, unsigned binary numbers 0000 - 1111 corresponding to the decimal values 0 - 15.

binaryunsigned decimalnaive 2's complement1's complement
000000
000111
001022
001133
010044
010155
011066
011177
10008-0-7
10019-1-6
101010-2-5
101111-3-4
110012-4-3
110113-5-2
111014-6-1
111115-7-0

It is conventional for signed numbers to use the uppermost bit to denote whether it is positive or negative (this is 2's Complement), and using the remaining bits to encode an absolute value. Consequently, there are 2 values for ± zero: 0000 and 1000 which is annoying and hard to work with since zero-transitions (sequences passing over zero) need to be handled specially.

1's Complement reverses the order of the negative numbers which makes zero-transitions simpler, but still duplicative and inefficient. Modern 2's Complement has a single encoding for zero, is backwards compatible with unsigned addition and subtraction, is reversed, and uses the last "duplicate" representation of -0 to encode 8 instead. 60's-era computers designed for scientific calculations like the AGC typically used 1's Complement. We can plot the numbers 0 - 15 as spokes on a circle to visualize negation and operation:

The numbers 0 - 7 correspond to their unsigned values and binary representations, and the negative numbers are organized in the reverse order, mirrored across the vertical axis. Unlike 2's Complement, the two halves are perfectly symmetrical for all powers of 2, so negating or complementing a number is as easy as flipping all of its bits.

non-negativebinarybinarynon-positive
+000001111-0
100011110-1
200101101-2
300111100-3
401001011-4
501011010-5
601101001-6
701111000-7

Addition in the positive space is equivalent to the signed version:

And the same is true for addition involving negative numbers:

Things get interesting when we have a zero-transition:

Addition is performed modulo the word size, so the resulting computation is 6+11mod16=16 + 11 \mod 16 = 1. We have to carry the extra digit which gets "eaten" by the duplicate zero to add 11 to the modulo sum to yield unsigned 22 = signed 22. This is called the End-around carry which, in 1's Complement, needs to be added to the end result.

Another tricky case is when the result of our computation exceeds the word-width. An overflow occurs when the signed result does not fit into the number space e.g. 4-bits. Signed 7+17 + 1 overflows to 7-7 which is incorrect!

The same thing happens when overshooting a negative number. After applying an End-around carry, the signed result of 77 is incorrect:

These errors are flagged in the Accumulator via the same bit which the rest of the words use for parity.15

16

Consider the following program and memory layout:

If we have code that reads the maximum value 7FFF from memory and adds 1, the result is 0 and an overflow is detected, so the Accumulator is flagged.

The Store instruction, in addition to writing A\text{A} to memory, does extra work if there's an overflow condition. It clears the overflow flag, writes ± 1 into A\text{A} to indicate the direction of the overflow and skips to the next instruction.

This way, the program can detect the overflow and use the ± 1 to apply the signed carry to a higher-order word. By storing the residual value from A\text{A} back into memory, we have a double word result:


Load Complement

Returning back to our enumeration of the instruction set, Load Complement allows us to easily negate a number by flipping every bit in the word while in-flight to the target register:

e.g. ldc a, [$k] read "load complement indirect k to a" which loads the negated value at k to A\text{A}.

Increment, Augment, and Diminish

Adding 1 to a word is such a common operation that there's a dedicated instruction for it to be accomplished in place rather than doing the load, adding, store rigamarole: Increment, inc [k].

There is no corresponding Decrement instruction; instead the AGC has Augment and Diminish.

Augment adds 1 to all positive values and -1 to all negative values:

Inversely, Diminish converges a given value towards ± 0:

Multiply

Optimized for scientific calculations, the CPU has dedicated multiplication circuitry allowing it to outperform larger computers with less prohibitive constraints from the same era (recall the first table comparing kilo-muls/s). The Multiply instruction reads a value from memory and multiplies it by the value stored in the Accumulator.

Multiplying two signed, 15-bit words requires up to 29-bits to store the result. Therefore, the complete result of a mul instruction is written to two registers. Up to this point, we've only been concerned with the Accumulator A\text{A}, but the CPU has 8 primary "registers" in total. Again, the nature of the Von Neumann architecture makes it so that all memory cells are technically registers, but we say 8 registers in the conventional sense of computer architecture -- I have no business calling something conventional, I don't really know what I'm talking about. In particular, B\text{B} is a separate 15-bit register mostly used together with the Accumulator for instructions that deal with 30-bit data such as –but not limited to– arithmetic.

The upper half of a mul result will be stored in A\text{A} and the lower half in B\text{B}. Double word values are expressed with the uppermost bits stored in lower addresses (such as A\text{A}) and the lowermost bits stored in higher addresses (such as B\text{B}), making the AGC a Big-Endian machine.

Assuming the normalized form with matching signs, double word values are interpreted via concatenation of the 28-bit values of the words, retaining the sign of the upper word.

Divide

Division also works with double words by taking the combination of the A, B\text{A, B} registers as the dividend, and a word from memory as the divisor. div also produces a two word results: the quotient and the remainder. The former is written back into A\text{A}, and the latter to B\text{B}.

Double Word Arithmetic Instructions

Other instructions which use both A, B\text{A, B} as a double word register include:

Load Double

Load Double looks up the value at memory address k and the subsequent cells and loads them into A, B\text{A, B}.

Load Double Complement

Load Double Complement does the same, negating the values before storing them in the registers.

Exchange Double

While there's no instruction to store A, B\text{A, B} in a single step, there is a double word exchange: xchg ab, [k].

Add Double

Adds two doubles, need I say more.

Exchange B

To load and store just the B\text{B} register, we have Exchange B. Note that, though identical in function to Exchange, this instruction has a different opcode and is therefore unique from the other xchg which works on the Accumulator. We'll take a look at why that is when we more-closely examine the memory map.

Indexing

When working with tables of data, there is an indexed addressing mode. Any instruction which takes an address as an argument can use it. For example

ld a [$700 + [$80]]

in conjunction with the following memory state, is interpreted as ld a, [$702] and is useful when we have a "table" of contiguous data starting at address $700, as well as an index for that table elsewhere in memory.

We might encounter an instruction without a base address, such as ld a, [[$80]] which imputes a base address of $0, expanding to ld a, [$0 + [$80]] which is effectively a just pointer.

Control Flow Instructions

By default, instructions are executed sequentially, with the Program Counter PC\text{PC} incrementing as instructions are executed, always pointing to the next instruction. PC\text{PC} itself is another one of the primary registers, occupying cell $005. Control flow instructions like jmp and its conditional variants change where PC\text{PC} points.

Jump and Conditional Jumps

when the CPU hits a jmp instruction, it will load its argument into PC\text{PC}, meaning that execution will continue at that address k.

jz (Jump If Zero) and jlez (Jump Less Than Zero) examine the value of the Accumulator before jumping, if neither condition is met, the Program Counter is unchanged and execution proceeds as normal:

Count, Compare, and Skip

No, that's not three instructions, just one behemoth of control flow, the god instruction. The designers saw subleq and thought "why not?" ccs is a four-way fork for execution. Depending on whether the value at the specified memory address is +n, -n, +0, or -0, the Program Counter will jump to one of the next four instructions.

If you (as an AGC software developer) know the value at k is positive or zero, you can ignore the last two cases and just fill the first two slots with non-foobar code. Conversely, for the strictly negative inputs, we can fill the first two slots with placeholder values (or even data, if we're feeling evil) since they should never be reached for execution. They could/should instead be filled with addresses for error handling.

Note that since ccs also puts the absolute diminished value of the memory address in A\text{A}, we can use ccs a for loops that count down the Accumulator.

Call

The Call instruction is meant for calling subroutines or functions. It's similar to jmp with the exception that it saves its origin of invocation so that the callee can return to the Program Counter of its callsite later. This is accomplished by incrementing PC\text{PC}, copying it to the another primary register called LR\text{LR} (the Link Register at adress $002), then overwriting PC\text{PC} with the argument of the call instruction.

Return

At the end of such a subroutine, a ret instruction will copy the contents of the Link Register back into the Program Counter.

Exchange LR

If a function needs to call its own subroutine, the program needs to save the LR\text{LR} beforehand and restore it afterwards. This is achieved via the xchg lr, [k] instruction:

xchg lr, [$4fff]
call $ee0
xchg lr, [$4fff]
ret

For additional levels, a stack can be constructed manually using indices (in practice, this sounds about as tedious as weaving strands of wire together by hand to build RAM).

Registers

So far, we've discussed three of primary registers which occupy the first eight words in memory.

  • A, B\text{A, B} are used for arithmetic and staging,
  • LR\text{LR} stores an address to return to after calling a subroutine,
  • PC\text{PC} keeps track of what to execute,
  • and 0\text{0} (notably NOT stored at memory address $0) always contains the value zero.
    • This cell is "read-only", insofar as values written to this address are instead discarded.

They can be accessed and referenced by all instructions which take memory addresses as arguments, allowing us to achieve things like ld a, b via ld a, [1]. Zero can be referenced via [7], we can increment the accumulator via inc [0], and we can use B\text{B} as a pointer by reading from the double indirect [[1]] which is equivalent to ld a, [b].

The other three primary registers which we haven't discussed yet are used for juggling remote banks of memory. Let's have a peak at those.

Memory Map

Taking a closer look at memory, recall that we have 4096 words at our disposal, ranging from memory address $000 - $FFF. As we just saw, registers occupy the very bottom of memory. Including them, there are 1024 words of Random Access Memory (RAM), leaving 3072 words of Read Only Memory (ROM).

RAM

Taking a closer look at the RAM, the first 768 words are fixed RAM, and the latter 256 words are used for banked RAM. There are eight banks of additional RAM with 250 addressable words of memory that can be swapped in and out.

Fixed RAM always points to banks 0, 1, and 2.

ROM

ROM is organized similarly, with the lower kiloword consisting of 32 banks governed by the FB\text{FB} register left-shifted by 10, and the upper 2 kilowords being fixed.

Support for more than 32 kilowords of ROM was added last minute, though only some of these super banks were ever used.17 The super banks can be accessed via a SUPERBNK bit to switch the upper 8 banks with the super banks for a total of 40 accessible kilowords.18 Similar to fixed RAM, fixed ROM always shows the contents of banks 02 and 03. Fixed ROM contains core operating code and fixed RAM core operating data which needs to be available at all times. The remaining functionality is distributed between the banks:

Switching RAM banks

Switching between banks can be done by writing to the Erasable Bank register EB\text{EB} via the ld eb, a instruction which is equivalent to ld [3], a. E.g. if A\text{A} contains $5, EB\text{EB} will expose RAM bank 5 starting at memory address $300.

The same store instruction can be used to write to the Fixed Bank register FB\text{FB} via ld [4], a. But that alone won't suffice in the common case: if code in one bank wants to call code in another bank like so:

ld fb, a
call $4e5

writing the bank number stored in A\text{A} to FB\text{FB} will switch the bank the code is currently running on, so it won't ever be able to execute the call instruction. Instead, it will execute some completely unrelated code that happens to be at the same address in the upper bank!

This motivates the following set of Load/Store instructions.

Call Far

To call code on a different bank, we need to change FB\text{FB} and PC\text{PC} atomically via callf which is an alias for the existing Exchange Double instruction by way of xchg ab, [4].

Return Far

The same idea is used for a Far Return to atomically swap AFB\text{A} \leftrightarrows \text{FB} and BPC\text{B} \leftrightarrows \text{PC}.

Additional Registers

Bank Registers

The two bank registers hold 5 and 3 bits to denote one of the 8 RAM banks or one of the 32 ROM banks, respectively. Those bits are tactically padded by zeros so that they can be simultaneously encoded in the BB\text{BB} register which references Both Banks.

Call Far Both Banks

Used in tandem with callfbb alias which is a double word exchange instruction to update the Program Counter and both banks which is useful to simultaneously handover control of a subroutine with the RAM banks corresponding to its private variables and code somewhere in ROM.

Return Far Both Banks

Additionally, Return Far Both Banks restores the RAM/ROM bank config:

The register layout was designed with BB\text{BB} after PC\text{PC} rather than alongside the other bank registers to allow for a double word exchange with FB/PC\text{FB/PC} or BB/PC\text{BB/PC}:

Shadow Registers

That completes the overview of all eight primary registers. Of course, there are eight additional reserved words immediately above these registers in memory shadowing our registers for additional state management. We'll get to them later:

Editing Registers

Above the shadow registers are 4 additional memory cells referred to as the Editing Registers ROR, SHR, ROL, SHR7\text{ROR, SHR, ROL, SHR7}:

which make up for the conspicuously-absent shift and rotate instructions which students of machine code (masochists) are familiar with.

Rotate Right

The ROR register rotates any 15-bit value that gets copied to it by 1 bit in the clockwise direction:

Shift Right

Immediately above, the SHR register shifts a value copied to it to the right by one, truncating the least significant bit and duplicating the most significant bit which preserves the sign of numerical values:

Rotate Left

To save us from 14 clockwise bit-rotations via load/storing to the ROR register, there's a dedicated ROL register:

Shift Right by Seven

And lastly, the SHR7 register shifts a value to the right by 7 bits, filling the leading bits with zeroes. This is needed for the interpreter software covered the 3rd section of this post on the System Software.

I/O

So far we've seen that the AGC's CPU is connected to RAM and ROM comprising the memory bus, but the computer also communicate with various peripheral devices within what is called the I/O Bus.

We've illustrated the address space for memory; I/O channels exist separately from the Memory Bus.

This space consists of 512 words of additional addressable space called Channels, numbered $000 - $1FF.

Channel Instructions

Similar to the AGC's other Load/Store instructions, each channel is 15-bits wide effective and can be written to/read from via the In and Out commands:

In

In takes a source memory address channel to read from.

Out

Out takes a target channel to read to.

I/O Variants

I/O instructions can operate on whole words or use the bitwise boolean operators to change specific bits, yielding fundamental boolean algebra at bit-level granularity.

Both instructions have a boolean variant -And and -Or. The -And variants clear individual bits and the -Or variants set:

And there's also an in^ instruction to XOR values:

For many devices, each I/O cell contains 15 control bits which can, for example, toggle on a lamp on a display:

To make boolean operations also work across registers, I/O channels $001 and $002 are aliases for B, LR\text{B, LR} allowing the AGC to bithack the primary registers:

and a, b ≡ in& a, [1]
or a, b  ≡ in| a, [1]
xor a, b ≡ in^ a, [1]

Counters

Above the primary registers, the shadow area, and editing registers, there's yet another special area reserved for counters. Like I/O channels, they connect to external devices, but rather than sending bits or whole words, they instead are controlled by hardware pulses or cause hardware pulses themselves.

On every pulse, TIME1\text{TIME1} is incremented. Other counters such as the CDU\text{CDU} counters take the value stored in them by and count down, generating pulses while doing so.

Interrupts

When I/O devices need to signal to the CPU, they can interrupt normal execution. Conceptually adjacent to the Program Counter pointing to the next instruction, the Instruction Register IR\text{IR} holds the current opcode of the instruction to be executed (it's effectively PC1\text{PC}-1). When an interrupt happens, the CPU copies PCPC\text{PC}' \leftarrow \text{PC} and IRIR\text{IR}' \leftarrow \text{IR} before jumping to the location indicated by the kind of interrupt. Note that IR\text{IR}' is an actual register, and IR\text{IR} is just the name we assign to the cell in memory we're executing at any given time.

Interrupt Instructions

Interrupt Return

When an interrupt handler finishes servicing the device, iret copies PCPC\text{PC} \leftarrow \text{PC}' and IRIR\text{IR} \leftarrow \text{IR}' back into the main registers so that execution can continue at the normal location.

When an interrupt occurs, PC, IR\text{PC, IR} are automatically saved and the remaining (highlighted) registers A, B, LR, BB\text{A, B, LR, BB} need to be saved by the application code if necessary. Once the interrupt handler finishes, the Interrupt Return instruction restores PC’, IR’\text{PC', IR'}. Note that the overflow condition flag in the Accumulator cannot be saved or restored!

There are eleven interrupt handlers which reside within fixed ROM starting at $800.

There are four words allocated for each interrupt entry; INT 0\text{INT 0} denotes the entry point for a hard reset. Typical interrupt entry code saves A, B\text{A, B} to A’, B’\text{A', B'}, loads A, B\text{A, B} with a bank containing the Program Counter of the actual handler code, and then jumps there.

Interrupt

Along with the previously mentioned iret instruction, there are also several instructions to cause an interrupt, as well as to globally enable or disable interrupts if we're in the middle of sequence of operations which cannot be safely stored/restored if they were interrupted, like when an arithmetic carry occurs.

Interrupt On

Interrupt Off

Night Watchman

There is also one more special location in memory at $037 called the Night Watchman. This cell needs to be read from/written to every 0.64s19 otherwise the system will be deemed unresponsive and invoke the RESET interrupt.

            # If you're all done, a nice but complex infinite loop that
            # won't trigger a TC TRAP GOJAM.
ALLDONE     CS      NEWJOB      # Tickle the Night Watchman
            TCF     ALLDONE

(the fun part about writing these gratuitously lengthy blogposts in Markdown –along with the absence of spellcheck standard formatting 🙃– is that when I forget to include a citation in the middle of the post like this, I can either take 30 mins to manually ++ every other footnote citation to make room for this citation, or I can copout and complain about the torturous editor I choose to use when writing, slap a playful "typo factory" banner on the landing page, and abdicate most responsibility for what boils down to shoddy attention to detail.)

Instruction Encoding

In the examples we've seen so far, the codes represent a specific instruction. Typically, the first 3 bits of the instruction denote the opcode wich uniquely* identifies the instruction, and the remaining 12 bits can be used to specify an address:

Recall the ld instruction has a base value of $3000, so with a target address of $100`, the instruction in memory is $3100:

A 3 bit opcode allows us to enumerate 8 unique instructions, but we have 36 (not including aliases which don't need unique opcodes) The eight "free" opcodes are assigned like so:

Analyzing our memory map, we can note that RAM addresses always start with two leading zeroes, and ROM always start with anything but two leading zeroes:

The store instruction ld [k], a –which only makes sense for a RAM target address– only needs to encode 10 address bits rather than 12, yielding some additional bits we can use to encode three more RAM-only instructions. The same is true for the increment instruction inc [k] which makes room for three more, as well as ccs which shares an opcode with jmp (001) which only works on ROM.20

Since jumps to the bank registers don't make much sense, these address targets are used to encode sti, cli, and EXTEND.

Extend is a prefix: it changes the meaning of the opcode of the next instruction allowing for a second set of 2-word instructions:

The last special instruction is call2, which is an alias for call lr, which is the same as ret. But the CPU doesn't special case this instruction, ret is a side-effect of calling memory at location 2. It executes the instruction encoded in the Link Register, and the 12-bit address with a leading zero decodes into another call instruction which transfers control to the call address.

Indexed Addressing

Indexed Addressing is achieved via the INDEX prefix. An indexed instruction consists of two instruction words: INDEX and the base instruction which can be thought of as a curried argument to Index.

For example, the following indexed Load instruction

INDEX [$80]
ld a, [$700]

becomes ld, a [$700 + [$80]]:

It's worth noting that INDEX is an actual instruction.

The CPU reads from the given address, then adds its value to the instruction code of the following instruction (PC) which is then stored in the Instruction Register IR\text{IR}.

The resulting instruction code is then used for the next instruction, e.g. ld a, [$703] which is the desired effective address for our indexed load.

Indexed Interrupts

If an interrupt occurs after the INDEX, that is a problem because IR\text{IR} contains the effective instruction code which will be saved/restored from IR\text{IR}'.

And finally, there is one index code with a special meaning for when the target address appears to be referencing a shadow register, it is actually iret which –in the big ole table of instructions– gets smushed between INDEX and xchg ab, [k]:

Architecture Summary

Looking at the AGC's instruction set and architecture as a whole, there are many quirky and unusual features when compared to modern architecture:

  • The use of 1's Complement rather than 2's,
  • There is no status register, and the overflow flag can't even be saved, so interrupts are disabled until overflows are resolved
  • The Store instruction may skip a word under certain circumstances
  • The ccs instruction is a bastard of creation, skipping several words in a way that would've given Dijkstra an aneurism
  • There are no shift/rotate instructions, but rather corresponding registers which shift and rotate values when written to
  • Most boolean instructions only work on I/O channels
  • Indexing is achieved by hacking the subsequent instruction's code
  • The Architecture has no native conception of a stack, complex indexing setups must be used if such a data structure is needed as we'll see in the System & Mission Software sections

2 | Hardware

21

Overview

The AGC runs at just over 1 MHz, is microcoded and uses integrated circuits with both core memory and core rope memory. This is the realm that I know the least about, and for which there were the most PDFs to drool over.

Each component is comprised of roughly 500 logic gates. Data paths are made up of 15 wires moving whole words around the central units, and the control paths are just a single-bit wire for managing timing and control.

22

To this point, I've been affectionately referring to some of the primary registers by modernized names such as A, B, LR, PC\text{A, B, LR, PC}, but back in the day they were actually named A, L, Q, Z\text{A, L, Q, Z} as indicated by the big blue guy in the middle.

Main Clock

The main system highlighted in orange on the left hand side is clocked at 1.024 MHz, feeding pulses into the Sequence Generator which iterates through twelve stages T1 - T12 which describes one complete Memory Cycle or MCT.

The number of memory cycles needed to execute an instruction is generally proportional to the number of memory accesses required. For example, Load, Add, and Store each take 2 memory cycles to read from memory, and store the value to/from the Accumulator, whereas the vanilla Jump instruction just takes one cycle to read the value from memory into the Program Counter.

The sequence generator contains a collection of 12-step microprograms for each MCT for each instruction. For example during this Load instruction, each entry of the MCT sends control units pulses to the other units which are connected via the Write Bus.

Microcodes

The control instruction WA for example instructs the register unit to put the contents of A\text{A} onto the write bus,

and RA reads the value of the bus back into A\text{A}:

WS will copy the contents of the bus contents into the memory address register, and RG and WG will read and write the G\text{G} register which buffers a cell's value after reading before a write.

At the beginning of an MCT at step T2, the hardware sends the memory address S\text{S} –usually an encoded instruction– to memory (1) and copies the contents of that address into G\text{G} (2). The second half of the MCT, starting at T10, copies G\text{G} back into memory:

If we examine the memory timing alongside the microcode with pseudo code as well, we can see that the MCT for the Load instruction loads the value from memory into G\text{G}, copies that value into B\text{B}, and concludes by copying it into A\text{A}.

Perhaps more interestingly, the Exchange instruction:

  1. saves A\text{A} to B\text{B},
  2. reads memory into G\text{G},
  3. copies the result into A\text{A},
  4. copies the old value into G\text{G},
  5. and stores that G\text{G} into memory

More complex operations span several MCTs such as Division which, when translated to modern pseudo-microcode looks like this:

// TODO: (which I'm leaving in) if you hate yourself, you can copy over the remaining DV1 - DV7 blocks... This is how we end up with fast-inverse square root hacks... to avoid hundreds of hardware-level copies to just end up dividing by zero anyways. You wanna see some undefined behavior, let me tell you hwHAT!

# DV0
xx  RA    WB  TSGN        # T1
0x  RC    WA  TMZ   DVST  # T2
1x  DVST                  # T2
xx  RV    WB  STAGE       # T3
# DV1
x0  RL    WB              # T4
x1  RL    WB  TSGN        # T4
0x  RL    WB  TSGN        # T4

Unprogrammed Sequences

There are more microprograms than just those associated with machine instructions. For example, since there's only a single adding unit in the whole computer, incrementing and decrementing counters is done by converting the pulses through the I/O counter unit to special instructions which then get injected into the instruction stream:

There are fourteen of these Unprogrammed Sequences with their own microprograms. Some shift counters, some are for interacting with debugging hardware, and the last two are used for interrupt and reset sequences.

counter shiftsdebugginginterrupt/reset
PINCINOTRDRUPT
DINCINORLDGOJ
MINCFETCH
MCDUSTORE
PCDUTCSAJ
SHINC
SHANC

From NAND to Tetris23 the Moon

The complete schematics of all of the hardware logic gates comprise only 49 sheets which is less than I would expect to fill up during an introductory electrical engineering course. The whole implementation only uses a modified 3-input NAND gate:24

Each integrated circuit fits two such gates,

25

and about 100 of these form a logic module.

26

The AGC contains 24 of these modules, as well as some I/O and power Modules in Tray A:

Tray B contains driver amplifier modules as well as RAM and ROM which were exposed so that they could theoretically be replaced during a mission if needed:

Memory

RAM is implemented via magnetic core memory which stores bits in magnetized toroids. Reading a bit clears it, so the memory sequencer is also responsible for re-writing every bit after it is read. Without mass storage like tape, the AGC has a zany amount of ROM: over 500,000 handwoven bits.27

28

Core rope memory encodes bits via wires that go through or past a ferrite core. The two trays are fitted together and hermetically sealed making for a comparatively compact computer.

Peripherals

Several external devices were connected to the computer to interface with the AGC. The three primary categories of peripherals are State Vector Maintenance, Unique, Mission Control/Astronaut Interfaces. Each peripheral was governed via counters and I/O channels, and occasionally Interrupts.

Gyroscope

The gyroscope is the core peripheral that the AGC was built around. The AGC controls rotation of the Apollo craft via the CDU Command counters, then the gyroscope detects rotation around the three axes of the spacecraft which can be read via the CDU counters. The gyroscope enables the AGC to always know the craft's orientation in space which is call its attitude.

Accelerometer

The accelerometer measures acceleration forces around the three axes which can be read from the PIP counters.

Optics

Optics on the Command Module are used to measure the relative positions of celestial bodies. The AGC uses the OPTCMD counters to move the optics to point in the general direction of a star and the reads in the astronauts fine tuning via the OPT counters

Landing Radar

Landing radar is located at the bottom of the Lunar Module and measures the distance to the ground. The RADARUPT interrupt will trigger whenever a new measurement is available to be read from RNRAD.

Rendezvous Radar

The Lunar Module's rendezvous radar measures the distance of Command and Service Modules during rendezvous. After setting two angles in the CDUT and CDUS counters to point the module towards the other craft. This peripheral will automatically track it (via complex trig, the kind that requires double words and counters n carryovers n stufff) and trigger the RADARUPT when new data is available to read from RNRAD.

RCS Jets

The Command, Service, and Lunar Modules all contain Reaction Control System Jets that emit small bursts for holding or changing their attitudes. On the Lunar Modules, there's just a single bit for each of the 16 jets (consider the fun issues that can happen here with just a single parity. Any odd multiples of jet have to misfire to be undetected). Setting a bit to 1 will make the jet fire. System software uses a dedicated timer (TIME6) and interrupt (T6RUPT) for timing the pulses.

DSKY

The Display and Keyboard Module affectionately referred to as the DSKY is the primary unit for astronauts to interact with the AGC. It contains 19 keys, 15 lamps, and several numeric output lines. Keystrokes trigger the KEYRUPT interrupt, and the corresponding key number can be read from the MNKEYIN I/O channel. The display itself is driven by the OUTO channel.

Now yes, yes it is in fact true that intermediate computations were necessary to convert from metric to imperial units before physical quantities could be displayed to the astronauts. This is exactly the kind of trivia I would have at the ready if my inferior country didn't land on the Moon first too.

Here's its vogue glam shot:

And here's a close-up rendering of the UI:

The up- and downlinks enable bidirectional digital S-band radio communications between the mission control and each spacecraft at selectable speeds of either 1.9 or 51 kilobits/s . Data words from mission control are readable from the INLINK counter, triggering and UPRUPT, and words to be sent back from the AGC must be stored in the DNTM1 I/O channel. A DOWNRUPT signals the program when it can load the register with the next word to be beamed back to mission control in Houston.

† poor man's footnote, leave me alone,

The aircraft for the experiment is a C-54, designated NASA 432. The spacecraft transmission, as specified for the Block I Saturn-Apollo vehicle, is at a carrier frequency of 2.2875 Gc with two subcarriers: telemetry (TLM) at a 51.2 thousand bits per second (kbps) rate on 1.024 Mc (PCM/PM/PM), voice on 1.25 Mc (FM/PM)

– "CALCULATION OF SIGNAL MARGINS FOR THE CSM UNIFIED S-BAND DOWNLINK CHANNEL APOLLO AS-202 TO AIRCRAFT NASA 432", Goddard Space Flight Center, August 1966.

And thus, we have completed an admittedly abbreviated overview of the hardware which the AGC supports.

3 | System Software

Now that we have somewhere-hovering-north-of-rudimentary understanding of the AGC architecture and hardware, we can appreciate the software itself which helped put man on the Moon.

The AGC's operating system is a priority-based, cooperative, preemptive real time, interactive, and moderately fault-tolerant system with support for virtual machines. Throughout the mission, the AGC is constantly juggling mathematical calculations (which can take several seconds), as well as I/O with the various devices described in the previous section/post.

The operating system services interrupts when a device needs attention/input for e.g. a DSKY keypress, regular servicing in the background like updating the display to reflect the hypothetical keypress, as well as real time control/feedback such as flashing a lamp to indicate a DSKY program status, or firing a boost.

The Scheduler

With just a single CPU, the OS must intelligently switch between tasks. In general, batch-processed multitasking computers work on long-running jobs one after another:

Jobs have different priorities though e.g. interrupts are almost always more important than mundane servicing, so the scheduler checks a priority queue of jobs awaiting executing at set intervals (e.g. 20ms) and swaps out the active job for the job having a higher priority if one exists and executes it until that job either terminates and is removed from the queue, or another job with a higher priority enters the queue:

Curiously, scheduling is achieved by the jobs themselves which must yield intermittently to the queue, which makes the AGC a cooperative multitasking priority-based computer.

Jobs

You know, the thing your dad tells you to get when you're being a vagrant teenager? A job is described by a 12-word data structure in memory containing references to a Program Counter and Both Banks register pointing to where the job will start/continue executing. A job also contains a word describing a job's status and priority.

The Core Set contains 7 job entries, with the 0th slot being reserved for active execution, and a priority of -0 indicating an open slot.

When a new job is created with a higher priority than the job occupying the zeroth slot, the YIELD operator exchanges the 12-words so the new job becomes Job 0, and the previously executing job is relegated to a different slot.

Jobs can be put to sleep and/or yield to other jobs even if they have lower-priorities by negating their own priority so the scheduler won't pick them back up on the batch interval. It follows that job can be awoken by negating their priorities again.

The first eight words of a job are used for local storage for a job, and since jobs are Exchanged in and out of slot 0 of the Core Set, these addresses conveniently never change.

Job Management

The Executive has a set of subroutines that control the job data structures acting as a very primitive, but sufficient API for job management. These subroutines includes:

  • NOVAC - create a new job defined by a memory address (BB\text{BB}), starting address (PC\text{PC}), as well as an integer priority P[0,31]P \in [0, 31]
  • PRIOCHNG - change the priority of the current job
  • JOBSLEEP - put the current job to sleep
  • JOBWAKE - wake up a given job specified by its CADR (Core Address)
  • ENDOFJOB - terminates the current job

†: (tbh, if I can make it through this post with fewer than 1.5 more of these, I'll be happy). Executive Source Code. Ibiblio.

We also have the YIELD subroutine which, although not an executive function but rather a two instruction sequence that checks the NEWJOB\text{NEWJOB} variable –the night watchman at $037– which always contains the job ID of the highest priority job available:

If the job in the active/zeroth Core Set slot is the highest priority, ccs continues to the 2nd half of the YIELD sequence, otherwise a higher-priority job exists and YIELD invokes the CHANG1 †† subroutine which switches to that job. Recall that if NEWJOB\text{NEWJOB} isn't accessed regularly, then that triggers a system reset interrupt, because it implies that cooperative multitasking is stuck and the hardware needs to be reset.

††: whelp. More Executive Source Code. Ibiblio.

Math

Much of the code onboard the AGC is dedicated to scientific computations. Computing even just the linear combination of two vectors

av1+bv2a \cdot \vec{v_1} + b \cdot \vec{v_2}

requires hundreds of machine instructions, so the AGC was shipped with libraries to provide comonly used operations on single-, double- and even triple-word precision fixed point values as well as vectors and matrices:

Library code includes an 8 word Multipurpose Accumulator (MPAC) which can hold a double, triple, or a vector depending on what mode its configured to:

In C-like pseudocode, vector addition might resemble:

load_vector(vector_t *v1);  // load v1 into MPAC
mul_double(double_t *a);    // multiply with a scalar
push();                     // save it
load_vector(vector_t *v2);  // multiply the 2nd vector
mul_double(double_t *b);
add_double(pop());          // add the result to the saved value

Formulas like this one need to store intermediate results (viz a viz push() and pop()), so a 38 word stack is provided. If a job uses math code, the MPAC, mode, and stack pointer occupy the remaining words of the Core Set Entry:

The stack is part of a core data structure called the Vector Accumulator (VAC)†††. A Job can be created with, or without a VAC depending on which subroutine it's created with:

  • FINDVAC - create a new job with a VAC
  • NOVAC - create a new job without a VAC, as seen previously

†††: "Vector" here is a misnomer since VAC's can be used for generic storage

Virtual Machining

Machine code implementations of the linear combination example given above would be prohibitively verbose, with many function calls passing pointers. So, the designers of the AGC (the wicked smaht boys from MIT) developed a compact language to abstract away the cumbersome machine code interpreted at runtime to simplify authoring other-wise complex subroutines: virtual machines.

The C code to add scalar multiples of two vectors in this compact language might instead resemble the following shorthand:

VLOAD V1  // load_vector(vector_t *v1);
DMP A     // mul_double(double_t *a);
PUSH      // push();
VLOAD V2  // load_vector(vector_t *v2);
DMP B     // mul_double(double_t *b);
DAD       // add_double(pop());

This language is Turing Complete, and in addition to the VAC, has two index registers, two step registers (45 minutes in, I think I can safely get away with an "oh no step-register" joke here), and Link Register for state management.

The encoding of the compact interpreted language fits two 7-bit opcodes in a single word, allowing for 128 unique opcodes and also motivates the SHR7\text{SHR7} register.

Compact instructions are processed two at a time, so the subsequent two addresses (at least) are the operands allowing 14-bit addresses $0000 - $3FFF.

14-bit addresses implies that interpreted code doesn't have to deal with the complicated memory layout anymore and can instead index directly into about half of the ROM address-space at a time. At the lowest half of each kiloword ($0000 - $0400 = 1024 words) RAM is visible, and the remaining 16 kilowords from $0400 - $3FFF can point to the upper or lower halves of ROM:

Interpreted Code Instruction Set

Within a Job, regular machine code can be mixed with these instructions via invocation of the EXIT interpreted instruction which continues execution of the subsequent instructions as machine code, and the corresponding machine code instruction, CALLINTPRET switches back to interpreted interpretation :-).

Interrupts

In addition to long-running math tasks, the system software also supports device drivers (which implies I/O ((which implies interrupts))). When a device needs attention, e.g. the case of a DSKY keypress, an interrupt is triggered:

The current job will be interrupted, the interrupt handler read will the device data, and yield back to the job as quickly as possible:

If there's still more work to do, the interrupt handler can schedule another job later:

Timed Interrupts

Some devices need to be serviced regularly, so a 120μs timer causes interrupts that read/write data from/to various devices:

For example, the numeric display of the DSKY only allows updating a few digits at a time, so its driver is triggered by the 120μs timer to batch-update its digits across the display:

Drivers

The timer interrupt cycles through eight phases which distributed device drivers over time to minimize the duration of a single interrupt handler:

Waitlist Tasks

Some devices need to be driven at exact intervals. If a jo needs to flash a lamp twice, it might turn it on immediately, then schedule three waitlist tasks for the future at specific times to continue toggling it:

(Please clap for my artistic rendition of a lamp. R.I.P. Thomas Aquinas you would've loved Excalidraw)

Scheduling tasks in this manner is accomplished by storing the sorted delta times of waitlisted tasks in a data structure called LST1\text{LST1} where the zeroth entry (which is actually just the familiar timer register TIME3\text{TIME3}) is always counting down in a TIMER register, and the remaining entries point to PC, BB\text{PC, BB} "tuple" entries in an accompanying LST2\text{LST2}:

And there are of course more subroutines to create new tasks (WAITLIST) and end the current task (TASKOVER).

Coping with Interrupts in real time

The timer that controls the waitlist (TIME3\text{TIME3}) has a granularity of 10ms. Other timers fire at the same rate, but with different offset:

and the amount of work triggered by them (as well as by interrupt handlers) are designed to be short enough to never overlap with the next scheduled interrupt. This is complicated by device unscheduled which can occur at anytime. The duration of an interrupt handler causes latency which can reduce the maximum duration of timer handlers and triggered work:

Core system software can make no guarantees about timing

it's up to all the components to cooperate in order for the real time goal to be met

Shell

The AGC has a shell program called PINBALL which allows astronauts to interact with the AGC via the DSKY.

  1. Keypress interrupts schedule a job that collects the digits describing a command, and updates an in-memory representation of what should be on the display
  2. The 120μs timer triggers the display update code
  3. When the command is complete, PINBALL schedules a new job
  4. Mission control also has a remote shell via their own DSKY which communicates with the remote AGC via S-band radio

Program Alerts

System softward that supports human life has to be able to communicate malfunctions and recover from them. The ALARM subroutine takes the subsequent word from the instruction stream, displays it, and illuminates the PROG light on on the DSKY – to be interpreted as a warning or an error message:

Program Abort

Some kinds of failures, triggered by hardware watchmen or just run of the mill code bugs, can make it impossible for normal operations to continue.

Abort interrupts include:

  • Interrupt watchman
  • JMP error
  • NEWJOB error
  • Parity error

In addition to displaying the error code, they also cause the hardware to reset – but the system does provide some recovery services when an abort occurs.

Jobs often contain recovery code in the following layout:

During its execution, the job sets the recovery phase, and if an abort happens in any job, the currently set recovery routine gets executed. Recovery handlers may do some cleanup and attempt re-execution from a "safe" checkpoint where the recovery phase was set, skip to a different phase, or cancel the job altogether.

The PHASCHNG call sets the phase of a given job in a data structure called the Recovery Table:

Each phase is associated with a descriptor of a Task, or a Job (with or without a VAC). A second table with even phases contains two jobs/tasks each. Phase 0 disables recovery (talk about a footgun) and Phase 1 disables recovery while retaining the display contents:

So, during normal execution with several jobs and scheduled tasks, when an abort happens:

  1. The contents of the Core Set and Waitlist are cleared
  2. The contents of the recovery table are activated, scheduling tasks and jobs for all jobs which configured recovery code
    • Some failures like corrupted memory (Parity error) are unrecoverable. They cause a full restart with a fresh initialization of the system without any recovery code.

4 | Mission Software

In this section, we'll cover: the User Interface, launch sequence, attitude determination once in orbit, how the Auto Pilot and powered flight was achieved; upon reach the Moon, we can additionally trace the execution of the Lunar landing, rendezvous, reentry, as well as some contingencies encountered over the course of the nine Apollo Lunar missions from Apollo 8 in 1968 to Apollo 17 in 1972.

The Happy Path

User Interface

Starting with the UI, now that we have a basic understanding of how system software schedules and executes jobs and tasks, leveraging VM support in order to translate vast stretches of machine instructions in to compact-er library code, handling interrupts and aborts incurred from executing all these pieces that are wired into peripherals which –from a systems perspective– represent a physical and digital shitton of surface area for errata, ... we can begin to appreciate how humans can use the AGC to fly to the Moon.

The AGC operates similarly to other Command Line Interfaces with the exception that it only has numeric keys for generic input. As a result, commands and keywords must be encoded. To display a segment of memory on a modern CLI, we might execute the following bash command:

$ hexdump target_file.o⏎

and press the enter key to signal to our shell that we've finished typing and it should do whatever it is that we just commanded it to do. In AGC/DSKY parlens, hexdump is a verb and our target_file is the corresponding noun. On the AGC, we'd instead input:

where VERB 0 1 specifies a program display, a primitive analog to something akin to hexdump, followed by NOUN 0 2, and the ENTR key. This particular executions invokes a subroutine demanding an argument. On a normal system, our shell might display some sort of prompt like > to indicate that more input is required, e.g.

$ display memory⏎
> 25

On the AGC, flashing VERB and NOUN values indicate that it is awaiting additional input:

After specifying the argument to our command thus far via 25 ENTR, we then get to see the word in memory at address o2529. The same verb/noun encoding paradigm is used when proactively prompting the user for input. e.g.

VERB 0 6 NOUN 1 1

decodes to enter csi_sign_time meaning the AGC needs the Coelliptic Sequence Ignition time for takeoff. Some commands require addition confirmation dialogs such as

VERB 9 9

which has the astronaut confirm engine ignition with the proceed key PRO. Each mission Module is equipped with physical paper copies of comprehensive reference "docs" for all verbs and nouns, as well as cheat sheets for critical path sequences:

†††††: Apollo Guidance Computer Block II – CMC Data Cards

Can you imagine if you forgot the verb encoding for cancellation of a spurious Lunar Rendezvous test command as you're preparing for takeoff back into Lunar orbit, and some would-be-clouted-on-Stack-Overflow MIT nerd vibe checked you from 380,000 km away to ask if you read the docs? Docs which are floating around you on an index card as the hundred billion dollar vehicle you're supposed to link up with in a couple hours peeks out from the horizon of the Moon. I'd jettison myself out the airlock.

We dial in and begin the mission to the Moon:

¶: this one is particularly embarassing because I had to insert it between sequences of ever-growing dagger footnotes. Pathetic. Anyways, this image comes from the NASA Apollo 10 Press Kit for IMMEDIATE release May 7, 1969. (It's probably the single most content-rich document linked on this whole page, excluding the ibiblio site ofc)

Liftoff

Beginning with liftoff, the AGC has a passive monitoring mode awaiting the severance of the umbilical cables which starts the mission clock. In case this trigger fails, one DSKY is prepped with VERB 75, a program to zero the mission time, just awaiting a delicate ENTR keypress to manually start the mission timer. We can view mission elapsed time at any time via VERB 16 NOUN 65:

During the flight with the Saturn V, the AGC continues to perform largely passive operations such as monitoring of the flight path. Control of the Saturn V is achieved with its own launch vehicle digital computer – a completely different box of frogs. The DSKY automatically shows VERB 16 NOUN 62 which correspond to velocity, rate of change of attitude (both in ft/s, God bless America), and altitude above the launch pad in nautical miles. All units displayed on the DSKY, as well as the position of the decimal point are implicitly inferred by the user according to the active verb/noun/program.

In the event of an emergency, the AGC can take over full control of the launch vehicle and, in extreme cases, astronauts could even steer the whole stack into orbit themselves. And, again, they wanted to††††††, can you blame them? Not often you find yourself in the drivers seat of a 110 m rocket with 30 million Newtons of force on a joystick.

††††††: (hammer the over) I read numerous things that say astronauts were apprehensive about giving the AGC as much autonomy as it ended up having, but no primary sources with the MIT/NASA/Goddard insignia...

In just under twelve minutes, the 1st and 2nd stages have completed, and we're now using a small burn from 3rd stage thrusters to enter the 185 km obit, circling earth every 88 minutes, or right before bullet (4) on this alternate flight path diagram:

In Orbit

But how do we, or the AGC, know when we're in the correct orbit? The AGC + Mission Control are monitoring the position and velocity state vector. In order to get where we want to go, we first have to know where we've been. These state vectors are stored as three-dimensional double-word vectors:

Position is determined via telescope and space sextant.

†††††††: PBS

Space sextants work similarly to an 18th century nautical sextant. By measuring the angle between the horizon and a celestial body. In space, we have the added flexibility of selecting terrestrial or lunar horizons, and there's no shortage of celestial bodies in space pick from for the second point of reference. The AGC is shipped with 45 of them:

30

30

Optics hardware on board the Command and Service Module can be moved to point towards the Earth or the Moon. Program 52 rotates the entire spacecraft to point one axis of the sextant –the Landmark Line of Sight or LLOS– at the nearer of the two major bodies. Astronauts then use the optics to exactly align the horizon to the LLOS.

†††††††: Adler, Doug. "The story of the Apollo sextant." Astronomy, June 4, 2018.

They then locate one of the known stars via telescope and point the starline to it so the AGC can read the trunnion and shaft angles. Repeating this a couple more times in different locations ~triangulates a three-dimensional position of the vehicle in space.

Lunar Module State Vector

On the other hand, the optics hardware aboard the Lunar Module was skimped upon as a means of weight reduction to meet mass constriants. Any alignment would require rotation of the whole Lunar Module and was thus reserved for determination of the landing site as well as for the rendezvous sequence.

Dead Reckoning

As we move in space, our state vector changes over time. But with two fixed locations, as long as we're coasting, we can establish our speed and predict future positions via dead reckoning: continuing to fly off in the direction of our state vector. Since position and velocity are known, we can extrapolate future positions. Unfortunately, the near-extrapolation method doesn't work all too well in space as we have non-negligible gravitational forces which bend our path. To get around this reality, some combination of mathematical models must be employed. The AGC features two implementations:

  • Conic Integration via the Keplerian Orbit Model, which assumes one, perfectly round gravitational body influencing our flight path, and
  • Encke's integration method for perturbation which considers multiple bodies with gravitational imbalances.

31

While the Apollo spacecraft was capable of flying to the Moon on its own, ultimately NASA decided that the primary source for the state vector updates during this portion of the mission should be be Mission Control, sent via three remote satellites communicating with the remote AGC idling on Program 27 listening over S-band for state vector updates. Nevertheless, Mission Control does not know Apollo's attitude better than Apollo itself. Recall that attitude is the orientation of the craft in space along its 3 axes of rotation: pitch (front/back), roll (left/right), and yaw (top/bottom), with the front being set to the conic nose of the craft, and the other axes being symmetrical. Gryroscopic gimbals making up the Inertial Measurement Unit (IMU) allow precise measurements and reading on any axis.

Digital Autopilot

Before leaving Earth's orbit, let's discuss the Digital Autopilot which is the single biggest program in the AGC, making up roughly 10% of all source code on both the Lunar and Command and Service Modules. The implementations vary significantly for each vehicle though, due to differing flight modes, thruster sets, and physical symmetries. As there is no friction in space, even the tiniest adjustment cause the vehicles to rotate, so the AGC's autopilot uses the jets to keep the attitude within certain thresholds – so called deadbands.

Autopilot is also employed in case the astronauts ever need to use the controllers for the thrusters, returning the Command and Service Module or Lunar Module to these target thresholds after manual adjustments allowing for fly-by-wire control. As any thruster could break at any time, the autopilot calculates, amongst myriad other things such as center of gravity, weight distribution, etc. – the ideal burn mode, even with a reduced/less-than-ideal number of thrusters.

VERB 49 NOUN 22 accepts an attitude vector a an argument and calculates the most efficient burn vector to reach the target orientation. Program 20, given by VERB 79 NOUN 79, allows for stable rotation which is required for certain sequences such as temperature control or landing site observation. Powered flight via AGC-governed engine burns, that were usually calculated by Mission Control.

Trans Lunar Injection

With the ability to orient and position ourselves in space, it's time to fly to the Moon, which typically happened in the middle of the 2nd orbit around Earth, approximately 2:45 into the light. This should be accomplished by the 3rd stage (S-IVB) of the Saturn V. So, once again the AGC should take only a passive role in measuring Trans Lunar Injection via Program 15. After separation from the S-IVB, we are on our way!

Lunar Landing

Once in Lunar orbit, separation of the Command and Service Module and the Lunar Module happens about 4:45 minutes before landing (weird phrasing). At the designated time, the Lunar Module begins testing the hardware needed for landing and rendezvous: radar, landing gear, strobe, and VHF – and the IMU is realigned. Additionally, there's lots of prep work on the Lunar Module including preparation of the Abort Guidance System (AGS), which is another, simpler computer which is able to get the LM + astronauts back into orbit and can dock with the Command and Service Module in case of emergency.

Powered Descent

The Lunar Module's AGC has a special program P63: breaking phase. It toggles on the Landing Radar and updates the state vector. The AGC then controls the burn to reach the correct corridor to the surface, consuming a minimal amount of fuel. This process is fully automatic. The Lunar Module is oriented with the descent engine normal to the Lunar landing site – at this point, visibility is close to zero. Program 64 starts automatically at around 8,000 feet above the surface so the Commander of the Lunar Module can search for a suitable touchdown point. Program 66 holds the Lunar Module at a stable attitude above the surface so the Commander can manually adjust the height at a rate of 1 ft/s to slowly descend to the surface. Ideally, horizontal movement of the Lunar Module at this point should be zero.

After touchdown, the crew manually executes Program 68 which confirms landing for the benefit of the AGC, ensures that the engine has been switched off, terminates an average G routine which was necessary for Trans Lunar Injection, and tunes the Digital Autopilot to a forgiving setting to prevent drastic corrections caused by measuring the rotation of the Moon, (though it's not completely disabled in case of emergency).

On the Moon

Take some of the hardest photos produced by the culmination of humanity's technological achievement, claim the Moon for the boys back home,

and after playing around at the Fra Mauro LZ32 cut to the rendezvous sequence.

Lunar Rendezvous

This sequence was developed throughout the Gemini Project.33 The strategy calls for the active vehicle (in this case, the Lunar Module) to follow the orbiting vehicle (the Command and Service Module) on approach from below. Advantaged by active propulsion and a tighter, faster orbit, the Lunar Modules reaches the Command and Service Module in less than two orbit. Two different methods for rendezvous were used across all the Apollo missions involving Lunar Rendezvous:

  • The more conservative approach, a Coelliptic Rendezvous required 1.5 orbits for the Lunar Module to catch up to the CSM at the benefit of having ample time to plot course, monitor progress, and make corrections for various orbit scenarios.
  • The riskier alternative directly aimed the Lunar Module towards the Command and Service Module, completing rendezvous in less than a single orbit. This method was used starting with Apollo 14, after Mission Control had more experience under their belt and could safely optimize for time and fuel.

Preparation for takeoff began two hours prior to liftoff, in both cases involving alignment of the IMU and visually monitoring the orbit of the Command and Service Module to calculate rendezvous data via Program 22. At liftoff T-1 hour, Program 12 –Powered Ascent– is executed, the necessary input is provided (liftoff time and velocity of target). The AGC performs a countdown and requires confirmation before proceeding with liftoff.

Orbital reentry takes all of seven minutes (which is a pretty long time, in my opinion), but it can take between 1.5 - 2.5 hours to comed up behind the CSM depending on which rendezvous method is used. During this time, Program 20 is running, measuring the state vector of the orbiting vehicle via Rendezvous Radar, VHF antenna, and optics for visual alignment. It computes the necessary corridor and respective maneuvers to position the active vehicle in an interception course.

Many other programs also run in parallel to calculate the and execute the necessary mid-course burns.

Crew aboard the CSM actively track the LMs ascent and approach, and the CSM's AGC reciprocates calculation of the active vehicle's state vector in order to facilitate remote control in case of emergency. The LM halts 50m away, at which point it reorients its previously-upward-facing-docking port towards the CSM. The CSM then takes over the active vehicle role, and activates Program 79 –Final Rendezvous– which decelerates the CSM to close the distance, and commences docking procedures. Seconds before contact, the Digital Autopilots on both spacecraft are disabled to avoid simultaneous attitude correction of the combined craft.

Trans Earth Injection

R.I.P. Alex Jones, you would've hated Trans Earth Injection. The active AGC is fed parameters specifying the desired Earth orbit so it can calculate the burn used to begin the return trip. Mid-course corrections during the Trans Earth Injection are accomplished in a similar fashion to the Trans Lunar Injection, this time controlled by the AGC rather than the Launch Vehicle Computer.

Reentry

Reenty parameters are calculated by Mission Control once in orbit around the Earth and transmitted via S-band uplink. First, the entry sequence Program 61 starts at T-25 minutes, accepting various parameters such as latitude, longitude, splashdown radius, velocity, and target reentry angle. Confirming these values kicks off Program 62, which "guides" the astronaut through a checklist to complete manual separation of the Command Module from the Service Module which is not governed by the AGC. Likewise, completion of Program 62 initiates Program 63 –Entry Initialization– at which point the autopilot is handling thrust control to break the Command Module out of orbit and into Earth's atmosphere. Finally, Program 64 –Main Reentry– is automatically initiated, monitoring descent trajectory, splashdown location, and calculating optimal entry solutions.

Velocity reduction during reentry is achieved by invoking combinations of 2 other programs:

  • Program 65 –Entry Up Control– which performs atmospheric coasting to extend range and duration of this sequence, and
  • Program 66 –Entry Ballistic– which is what it sounds like

34

Program 67 –Final Phase– performs final maneuvers to reach the splash zone. Parachute deployments, confetti cannons, cheeky automated telegrams to Brezhnev to suck it are all handled by the Earth Landing Sequence Computer. The AGC has done its job.

Beautiful, meticulously precise, constant "single-threaded" execution for 195 hours, 13 minutes (in the case of Apollo 11).

Contingencies

Apollo 13: Deadlock

The Powered Descent sequence typically consumes 84% of the AGC's ~80,0000 instructions/s of processing power [ars technica citation], but due to incorrect power supply design, the rendezvous radar generated an additional 12,800 involuntary instructions per second, ironically summing to exactly the remaining 15% of possible load. Cooperative multitasking allows for a queue of jobs to build up resulting in executive overflow, and ultimately a 1202 program alarm. The OS automatically performs Program Abort, canceling and restoring all jobs in the span of a few seconds, and landing was able to commence.

Apollo 13: "Houston, we have a problem..."

55 hours, 55 minutes, and 53 seconds in the mission, Jack Swigert's utterance35 which extend into the transcendental vernacular of operators for years to come occurred when the mission experienced an explosive oxygen tank in the Service Module, about 320,000 km from Earth.

Fortunately, the crew was able to recover via free-return trajectory to safely return the astronauts back to Earth, but they had to migrate the Lunar Module in order to survive as the CSM was completely shutdown, including its AGC.

The IM configuration had to be transferred to the Lunar Module, adapted to its distinct orientation. Subsequently, manual burns and midcourse corrections were performed by the Abort Guidance system on the Lunar Module due to power constraints. A Successful reboot of the CSM's AGC was achieved hours before reentry.

Apollo 14: Flaky Abort

36

Floating solder board on the Abort Button introduced the possibility of unwanted abort which would return the Lunar Module back to orbit. This was solved within hours by re-programming the AGC to spoof execution of a different program which would ignore the abort button during Powered Descent.37 Real Abort activation, if needed, would have to be keyed in via DSKY

References38

Footnotes

  1. See also: "The cost of this effort, from January 1962 to January 1971, has been about $57 million. The cost to reach the primary objective of the project, the first lunar landing in July 1969 was about $45 million."

    Rankin, Daniel Allen. "A Model of the Cost of Software Development for the Apollo Spacecraft Computer." MIT, June, 1972.

  2. The Floating Point format was standardized by IEEE754 in 1985.

  3. The PAR generates and tests the memory parity bit. The lower 15 bits of each memory word hold AGC instructions or data. Each word is protected by a 16th “odd parity” bit. This bit is set to 1 or 0 by a parity generator circuit so a count of the 1's in each memory word always produces an odd number.39 A parity checking circuit tests the parity bit during each memory cycle; if the bit doesn’t match the expected value, the memory word is assumed to be corrupted and a PARITY ALARM panel light illuminates on the IO Module.

    Pultorak, John. "Apollo Guidance Computer (AGC): How to build one in your basement – Part 4: Memory (MEM) Module." 2004.

  4. M1 Benchmarks

  5. The latest USB-C chargers are apparently more powerful than Apollo 11’s computer." The Verge

    Worth also notng that this coffe-table-tier factoid is disputed with a bit more nuance on the AGC for Dummies (you and I, dear reader) Ibiblio page:

    Is it true that the Apollo computer had less power than a digital pocket calculator? No it is not true. The performances of the Apollo Guidance Computer were comparable to that of the central processing unit of computers like the Apple II, the Commodore 64 and the ZX Spectrum, the personal computer that opened the way for the digital revolution at the end ot the 1970's. The big difference was that the AGC was highly reliable and [...]. Its architecture is not much different from that of modern microcontrollers used today for the most disparate tasks, with the difference that the latter are at least 10 to 20 times faster than the AGC and enormously smallest (being integrated in a single chip). For this reason programming and using the AGC is not very far from the world in which control systems and embedded systems designers and hobbyists work today.

    https://www.ibiblio.org/apollo/ForDummies

  6. iblio.org/apollo – seriously, this is one of the best websites I've ever cast eyes upon. Made with love, equipped with instructions for how to clone the whole site for offline viewing, spinup a virtual AGC, and meticulous records and correspondence with other primary sources on the AGC – this shit rocks. This is a total copout, but if there's any factoid lacking a citation, it can probably be found on this site. Additionally, this conference talk was foundational for the structure and illustrations captured throughout this post.

  7. Legitimately simpler architecture (excluding its quirks) than the MIPS architecure commonly taught in the undergraduate Computer Architecture courses

  8. Courtesy of Eldon Hall who was lead hardware designer – Computer History Museum

  9. https://airandspace.si.edu/sites/default/files/images/5317h.jpg

  10. https://miro.medium.com/v2/resize:fit:1400/1*ukZOxy1EFbgeAAPKdekK-Q.jpeg

  11. http://www.apolloproject.com/sp-4205/c132.gif

  12. The armv8, widely considered to be a Reduced Instruction Set Computer (RISC) architecture A64 Base Instruction manual contains 354 instructions, and by comparison, x86 (a Complex ISC) has ~1500

  13. https://esolangs.org/wiki/Subleq

  14. https://github.com/chrislgarry/Apollo-11

  15. We have no way to check the validity of a data word after its been copied into memory - parity bit is used for data read from devices which are more prone to faults. Numbers within the Accumulator are the source of truth for the system, so we're free to use the free bit to indicate overflows and carries to upstream computations.

  16. Ramon Alonso and Albert Hopkins, "R-416 The Apollo Guidance Computer." MIT Instrumentation Laboratory, August, 1963.

  17. Fixed Memory consists of 36,864 words partitioned into 36 banks of 1024 words each. An address in the range 2000-3777 automatically requires the Fbank bits (15-11). The F-bank bits can be considered as defining banks 00-37. However, if the fixed bank is in the range 30-3?, the Superbank is required. There are 3 bits defining Superbank addresses O-7, The convention is that the Superbank only defines banks 30,40. The 2048 fixed addresses, banks 2 and 3, are addressable also as 4000-7777. These latter addresses do not utilize the fixed or Superbank bits in locating the addressed register and are used for bank switching routines. An FCADR = XXXXXYYYYYYYYYY is used by the programmer in constructing a fixed-bank address. The X bits are loaded into the F-bank and 2000 is added to the Y bits, giving an address between 2000 and 3777. To set the fixed bank and erasable bank bits and the Superbank bits, the programmer can use BBCON = XXXXX---YYY-EEE. If one writes Both-Bank, the Xs go into the fixed bank register and the Es go into the E-bank register. Similarly, if one reads the Both Bank register, the bank bits go into the respective positions on the write lines. The Ys are used for writing into the Superbank and reading out of it. Thus, this type of word takes care of all the bank settings.

    Donald J. Bowler, et al. "Apollo Guidance Computer Imrpovement Study." MIT Charles Stark Draper Laboratory, January, 1970.

  18. This is actually controlled by a subroutine called POODOO 💩

    See: MIT/IL Software Anomoly Report

  19. Which corresponds to the clock speed, for reasons we'll cover in the System Software section.

  20. call can be used to work around the ROM-only limitation of jmp

  21. "HARDWARE"

  22. Ramon Alonso and Albert Hopkins, "R-416 The Apollo Guidance Computer." MIT Instrumentation Laboratory, August, 1963.

  23. https://www.nand2tetris.org/

  24. "Guidance Computer and Associated Ground Support Equipment: Quarterly Reort." Raytheon Company – Space and Information Systems Division. 31 March, 1963.

  25. Hall, Eldon C. (AKA dat boi). "A case History of the AGC Integrated Logic Circuits." MIT Instrumentation Laboratory December, 1965.

  26. iblio.org/apollo

  27. 36 kilowords of ROM at 16 bits per word = 589,824

  28. https://images.computerhistory.org/revonline/images/500004481-05-01.jpg?w=600

  29. Most of the AGC/DSKY interface operates in terms of octal

  30. T.B. Murtagh. "Analysis of Sextant Navigation Measurements During Lunar Module Rendezvous." NASA, Charles T Hyle and Alfred N. Lunde. "Apollo Experience Report - The Application of Computerized Visualization Capability to Lunar Mission." NASA 2

  31. Actually just my own diagram since I couldn't find any simple ones

  32. Named after the famous 15th century Venetian cartographer who produced the most detailed map of the time. wikipedia.org/wiki/Fra_Mauro

  33. the Gemini program defined and tested the skills NASA would need to go to the Moon in the 1960s and ‘70s. Gemini had four main goals: to test an astronaut's ability to fly long-duration missions (up to two weeks in space); to understand how spacecraft could rendezvous and dock in orbit around the Earth and the moon; to perfect re-entry and landing methods; and to further understand the effects of longer space flights on astronauts.

    Bridge to the Moon NASA

  34. I. Bogner, W.G. Heffron. "Capabilities of the Entry Guidance Equations for Mission AS-202." Bellcom Inc.

  35. Or rather, his re-utterance, since Mission Control didn't hear him the first time

  36. O'Brien, Frank. "A deep dive into the Apollo Guidance Computer, and the hack that saved Apollo 14." Ars Technica, January, 2020.

  37. Transcript from "Day 5, part 3: Troubleshooting the LM Computer." NASA.

  38. No, I have not read Infinite Jest.

  39. Read about more sophisticated methods of error detection and correction in my sister post about Voyager 2