Virtual AGC Integration
The Virtual AGC project provides a cycle-accurate emulator of the Apollo Guidance Computer — the onboard computer that navigated the spacecraft to the Moon and back. gr-apollo connects to this emulator, feeding it telemetry data exactly as the real spacecraft’s telecommunications system would have, and receiving commands exactly as the real uplink would have delivered them.
This connection closes the loop: you can run the actual Apollo flight software, receive its downlink telemetry through a realistic RF signal chain, and send it commands through the DSKY interface.
What is Virtual AGC?
Section titled “What is Virtual AGC?”The Virtual AGC (yaAGC) is Ron Burkey’s emulator of the Apollo Guidance Computer, hosted at ibiblio.org/apollo. It executes the original flight software — the actual code that guided Apollo 11 to the lunar surface — instruction by instruction, using the AGC’s native 15-bit word architecture and fixed-point arithmetic.
The emulator exposes its I/O channels over TCP sockets. Each I/O channel corresponds to a hardware interface on the real AGC: the DSKY (display/keyboard), the inertial measurement unit, the rendezvous radar, the digital autopilot, and — most relevant here — the telecommunications system.
gr-apollo’s agc_bridge module connects to yaAGC’s TCP socket and speaks its 4-byte packet protocol, acting as the telecom hardware that sits between the AGC and the radio.
The 4-byte packet protocol
Section titled “The 4-byte packet protocol”yaAGC communicates using a simple binary protocol: each I/O operation is a 4-byte TCP packet. The protocol encodes a 9-bit channel number and a 15-bit value, matching the AGC’s native I/O architecture (the real AGC had 512 possible I/O channels and used 15-bit words).
Each byte carries a 2-bit signature in its upper bits that identifies its position in the packet. This makes the protocol self-synchronizing — if the TCP stream gets misaligned, the receiver can scan for valid signature sequences to recover.
block-beta
columns 8
block:b0:2
b0h["00"] b0d["Channel[8:4]"]
end
block:b1:2
b1h["01"] b1d["Ch[3:1] | Val[14:12]"]
end
block:b2:2
b2h["10"] b2d["Value[11:6]"]
end
block:b3:2
b3h["11"] b3d["Value[5:0]"]
end
style b0h fill:#5c3a1a,stroke:#bd7a3a
style b1h fill:#5c3a1a,stroke:#bd7a3a
style b2h fill:#5c3a1a,stroke:#bd7a3a
style b3h fill:#5c3a1a,stroke:#bd7a3a
style b0d fill:#1a3a5c,stroke:#3a7abd
style b1d fill:#1a3a5c,stroke:#3a7abd
style b2d fill:#1a3a5c,stroke:#3a7abd
style b3d fill:#1a3a5c,stroke:#3a7abd
The bit-level layout of each byte:
| Byte | Bits 7-6 | Bits 5-0 | Content |
|---|---|---|---|
| 0 | 00 | Channel bits 8-3 | High channel bits |
| 1 | 01 | Channel bits 2-0 (in bits 5-3), Value bits 14-12 (in bits 2-0) | Channel/value boundary |
| 2 | 10 | Value bits 11-6 | Middle value bits |
| 3 | 11 | Value bits 5-0 | Low value bits |
The encoding in protocol.py follows the original C implementation from yaAGC/SocketAPI.c:
b0 = (channel >> 3) & 0x3F # signature 00 is implicit (bits 7-6 = 0)b1 = 0x40 | ((channel & 0x07) << 3) | ((value >> 12) & 0x07)b2 = 0x80 | ((value >> 6) & 0x3F)b3 = 0xC0 | (value & 0x3F)The parser validates the signature bits before extracting the channel and value, rejecting malformed packets:
if (b0 & 0xC0) != 0x00: raise ValueError(...) # must be 00xxxxxxif (b1 & 0xC0) != 0x40: raise ValueError(...) # must be 01xxxxxxif (b2 & 0xC0) != 0x80: raise ValueError(...) # must be 10xxxxxxif (b3 & 0xC0) != 0xC0: raise ValueError(...) # must be 11xxxxxxTelecom channels
Section titled “Telecom channels”The AGC has hundreds of I/O channels, but only four are relevant to the telecommunications system:
| Channel | Octal | Decimal | Name | Direction | Purpose |
|---|---|---|---|---|---|
| DNTM1 | 034 | 28 | Downlink Telemetry 1 | AGC -> Ground | High byte of AGC word (bits 14-8) |
| DNTM2 | 035 | 29 | Downlink Telemetry 2 | AGC -> Ground | Low byte of AGC word (bits 7-0) |
| OUTLINK | 057 | 47 | Digital Downlink | AGC -> Ground | Additional digital data |
The AGC writes 15-bit words to these channels. The PCM system reads channels 034 and 035 at word positions 34 and 35 of each telemetry frame, encoding the AGC’s data into the serial bit stream that reaches the ground.
In gr-apollo, this path is reversed: the pcm_demux block extracts bytes from word positions 34 and 35 of decoded frames, and the downlink_decoder reassembles them into 15-bit AGC words.
| Channel | Octal | Decimal | Name | Direction | Purpose |
|---|---|---|---|---|---|
| INLINK | 045 | 37 | Uplink Data | Ground -> AGC | DSKY keycodes and commands |
Each write to channel 045 triggers the AGC’s UPRUPT interrupt, causing the flight software to read and process the 15-bit word. The INLINK is the only way for the ground to interact with the AGC during flight.
In gr-apollo, the uplink_encoder formats DSKY commands (VERB, NOUN, digits, ENTER) into 15-bit words, and the agc_bridge delivers them to yaAGC via the TCP socket.
AGCBridgeClient filters incoming packets to these four channels by default (the AGC_TELECOM_CHANNELS frozenset). Other AGC channels — the DSKY display driver, IMU CDUs, jet select logic — generate traffic on the socket but are not relevant to the telecom system.
15-bit word reassembly
Section titled “15-bit word reassembly”The AGC uses 15-bit words internally. The PCM telemetry system carries 8-bit bytes. Each AGC word is split across two PCM channels:
block-beta
columns 15
block:high:7
h1["bit 14"] h2["13"] h3["12"] h4["11"] h5["10"] h6["9"] h7["bit 8"]
end
block:low:8
l1["bit 7"] l2["6"] l3["5"] l4["4"] l5["3"] l6["2"] l7["1"] l8["bit 0"]
end
style high fill:#5c3a1a,stroke:#bd7a3a
style low fill:#1a3a5c,stroke:#3a7abd
- DNTM1 (channel 034) carries bits 14-8 in the lower 7 bits of its byte. The MSB of the byte is unused.
- DNTM2 (channel 035) carries bits 7-0 directly.
The reassemble_agc_word() function in downlink_decoder.py performs the join:
agc_word = ((dntm1_byte & 0x7F) << 8) | dntm2_byteAt the high PCM rate (50 frames/sec), one AGC word pair arrives every 20 ms. The DownlinkEngine accumulates 400 of these words (taking approximately 8 seconds) into a complete downlink buffer before attempting to decode the contents.
Downlink list types
Section titled “Downlink list types”The AGC organizes its downlink telemetry into “lists” — structured snapshots of internal state tailored to the current mission phase. Each list type contains a different set of navigation variables, autopilot parameters, and system status words.
The list type is identified by the lower 4 bits of the first word in each 400-word buffer:
| ID | List Name | When Used |
|---|---|---|
| 0 | CM Powered Flight | Launch, translunar injection, course corrections |
| 1 | LM Orbital Maneuvers | LM engine burns in lunar orbit |
| 2 | CM Coast/Alignment | Translunar and transearth coast, IMU alignment |
| 3 | LM Coast/Alignment | LM IMU alignment and coast phases |
| 7 | LM Descent/Ascent | Powered descent to lunar surface, ascent to orbit |
| 8 | LM Lunar Surface Alignment | Surface operations, pre-liftoff alignment |
| 9 | CM Entry Update | Atmospheric reentry guidance |
Each list type has a defined structure specifying what each word position within the 400-word buffer represents — things like the spacecraft’s position vector, velocity, gimbal angles, fuel remaining, guidance targets, and alarm status. Fully decoding these structures requires mission-specific knowledge (the word assignments changed between Apollo missions as the flight software was updated).
Uplink commands
Section titled “Uplink commands”Ground-to-spacecraft commanding works through the DSKY (Display and Keyboard) interface. The ground station sends DSKY keycodes via the Up-Data Link, which delivers them to AGC channel 045 (INLINK). Each keystroke triggers the UPRUPT interrupt, and the flight software processes it exactly as if an astronaut had pressed the key.
The 15-bit INLINK word encodes the keycode in bits 14-10:
Bits: 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 [--- 5-bit keycode ---] [---------- data / zeros ----------]Key DSKY keycodes (5-bit values in octal):
| Key | Octal | Decimal | Purpose |
|---|---|---|---|
| VERB | 21 | 17 | Begin verb entry |
| NOUN | 37 | 31 | Begin noun entry |
| ENTER | 34 | 28 | Execute / Proceed |
| RESET | 22 | 18 | Key release / Error reset |
| CLEAR | 36 | 30 | Clear current entry |
| 0-9 | 20, 01-07, 10-11 | various | Digit keys |
| + | 32 | 26 | Positive sign |
| - | 33 | 27 | Negative sign |
A typical ground command sequence — say, requesting program P63 (Braking Phase) via Verb 37 — looks like this:
sequenceDiagram
participant GS as Ground Station
participant UE as uplink_encoder
participant AB as agc_bridge
participant AGC as yaAGC
GS->>UE: VERB 37
UE->>AB: (ch 045, VERB keycode)
AB->>AGC: 4-byte packet
Note over AGC: UPRUPT fires
UE->>AB: (ch 045, digit 3)
AB->>AGC: 4-byte packet
Note over AGC: UPRUPT fires
UE->>AB: (ch 045, digit 7)
AB->>AGC: 4-byte packet
Note over AGC: UPRUPT fires
GS->>UE: ENTER
UE->>AB: (ch 045, ENTER keycode)
AB->>AGC: 4-byte packet
Note over AGC: UPRUPT fires,<br/>V37 N63 executes
AGC-->>AB: DNTM1/DNTM2 words<br/>(telemetry response)
AB-->>GS: Downlink PDUs
Each (channel, value) pair is sent as a separate 4-byte TCP packet. The AGC processes one word per UPRUPT, so multi-word sequences (like the 4-packet V37 ENTER sequence above) are sent in order. The AGCBridgeClient handles the TCP mechanics; the UplinkEncoder handles the DSKY encoding logic.
The full data path
Section titled “The full data path”Putting it all together, the complete round-trip path from spacecraft to ground and back:
graph TB
subgraph Spacecraft ["Spacecraft (yaAGC Emulator)"]
AGC["Apollo Guidance<br/>Computer"]
end
subgraph RF ["RF Signal Chain"]
SIG["USB Signal<br/>(2287.5 MHz)"]
end
subgraph Receiver ["gr-apollo Receiver"]
PM["pm_demod"]
SC["subcarrier_extract"]
BPSK["bpsk_demod"]
FS["pcm_frame_sync"]
DM["pcm_demux"]
DD["downlink_decoder"]
BR["agc_bridge"]
UE["uplink_encoder"]
end
subgraph Ground ["Ground Station"]
DISP["Telemetry Display"]
CMD["Command Entry"]
end
AGC -->|"ch 034/035<br/>DNTM1/DNTM2"| SIG
SIG --> PM --> SC --> BPSK --> FS --> DM
DM -->|"agc_data PDU"| DD
DD -->|"400-word snapshot"| BR
BR <-->|"TCP port 19697<br/>4-byte packets"| AGC
BR --> DISP
CMD --> UE -->|"DSKY keycodes"| BR
BR -->|"ch 045 INLINK"| AGC
style Spacecraft fill:#1a1a2e,stroke:#3a7abd
style RF fill:#2e1a2e,stroke:#bd3a7a
style Receiver fill:#1a2e1a,stroke:#3abd3a
style Ground fill:#2e2e1a,stroke:#bdbd3a
The downlink path (top to bottom) is fully implemented: complex baseband samples go in, decoded telemetry PDUs come out. The uplink path (bottom to top) sends DSKY commands through the AGC bridge to the emulator.
Connection management
Section titled “Connection management”The AGCBridgeClient handles the TCP connection to yaAGC as a background concern. It runs a receive loop in a daemon thread, automatically reconnects with exponential backoff if the connection drops, and delivers packets via callbacks.
Connection states:
| State | Meaning |
|---|---|
disconnected | No TCP connection. Attempting to reconnect. |
connecting | TCP handshake in progress. |
connected | Active connection. Reading packets, ready to send. |
The backoff starts at 0.5 seconds and doubles up to a maximum of 30 seconds. This means if yaAGC is not running when gr-apollo starts, the bridge will keep trying without flooding the network with connection attempts.
The GNU Radio wrapper (agc_bridge block) exposes three message ports: uplink_data (input), downlink_data (output), and status (output). The status port emits the connection state string whenever it changes, which can be connected to a QT GUI label or logged for monitoring.
Hardware restoration
Section titled “Hardware restoration”While gr-apollo works with the Virtual AGC emulator, others have gotten the real hardware running. CuriousMarc (Marc Verdiell), along with Mike Stewart, Ken Shirriff, and a team of volunteers, have restored actual Apollo Guidance Computers and S-Band telecommunications equipment to operational status. Their work provides invaluable validation that the specifications gr-apollo implements match the behavior of real flight hardware.
Of particular interest: Building an Apollo transmit station with Keysight instruments shows modern Keysight signal generators and analyzers driving the same S-Band uplink chain that gr-apollo models in software. Inside the WILD Lab of CuriousMarc is a Keysight-produced tour of the lab where the restoration work happens.