news 2026/10/16 1:41:57

阿波罗11号AGC制导计算机:磁芯存储与解释器架构的工程复现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
阿波罗11号AGC制导计算机:磁芯存储与解释器架构的工程复现

1. 项目缘起与整体设计思路

1.1 为什么一个五十多年前的导航计算机值得今天重新拿出来折腾

Apollo-11 这个标题,在技术圈里其实指向一个非常具体的东西——阿波罗十一号登月任务中使用的制导计算机(AGC,Apollo Guidance Computer)。它的指令舱版本叫 Comanche 055,登月舱版本叫 Luminary 099。这台机器的运算能力放到今天看简直可怜:主频大约 2 MHz,内存是 36K 字的磁芯存储器(core rope memory),字长 15 位。但就是靠这套硬件,完成了从地球轨道到月球轨道、从分离到降落、从月面起飞到返回地球的全部制导计算。

我之所以想把这个项目重新梳理一遍,是因为它几乎是一个“极限约束下做工程”的完美样本。现在做嵌入式或者系统开发,动不动就是几百兆内存、几个 G 的主频,资源从来不是第一约束。但 AGC 不一样,它的每一个字节都要靠手工编织磁芯来存储,程序写错了不是重新烧录,而是要重新穿线。这种约束下逼出来的设计思路,对今天做资源受限系统、做高可靠性系统的人,仍然有非常直接的参考价值。

这个项目适合谁来看?如果你是对计算机体系结构、嵌入式系统、实时操作系统、容错设计感兴趣的开发者,或者你单纯对“人类怎么用这么点资源把两个人送上月球”这件事好奇,那这篇内容就是写给你的。我会尽量把 AGC 的硬件结构、软件架构、核心算法、以及实际运行中的关键环节拆开讲清楚,同时补充一些我在复现和模拟过程中踩过的坑。

1.2 整体方案选型:为什么是磁芯存储器而不是别的

AGC 的存储方案选择,是整个项目里最值得先讲清楚的一件事。当时可选的存储技术其实不止一种,但最终选了磁芯存储器(core memory)和磁芯 rope 存储器(core rope memory),背后有几个硬性约束。

第一是可靠性。磁芯存储是非易失性的,断电之后数据不会丢失。在太空环境里,辐射、温度变化、供电波动都是常态,任何需要持续供电才能保持数据的存储介质都是风险点。磁芯的物理特性决定了它天然抗辐射、抗断电,这一点在登月任务里是决定性的。

第二是密度和功耗。磁芯 rope 存储器把程序“编织”进存储阵列里,每一位的状态由导线穿过磁环的方式决定——穿过环心表示 1,绕过环外表示 0。这种存储方式密度极高,一个 36K 字的存储器体积和功耗都远小于当时任何其他方案。代价是程序一旦编好就不能改,所以 AGC 的软件在发射前经过了极其严苛的验证。

第三是读写速度。磁芯存储的访问时间大约在微秒级别,对于 2 MHz 主频的处理器来说完全够用。而且磁芯存储的读写是破坏性的,读完之后需要立刻重写,AGC 的存储控制器专门处理了这个时序。

注意:如果你在模拟器里复现 AGC,磁芯 rope 存储器的“只读”特性必须严格模拟。很多模拟器为了省事把它当成普通 RAM,结果程序行为跟真实硬件对不上,尤其是自修改代码相关的逻辑会完全跑偏。

1.3 软件架构的核心取舍:为什么用解释器而不是直接写机器码

AGC 的软件架构里有一个非常关键的设计决策:它没有把任务程序直接编译成机器码,而是设计了一个解释器(Interpreter),任务程序用一套更高级的伪指令编写,运行时由解释器逐条翻译执行。

这个选择乍看很反直觉——解释执行比直接执行慢得多,而 AGC 的运算资源本来就紧张。但背后的逻辑是:AGC 要同时处理制导、导航、控制、显示、键盘输入等多路任务,如果全部用机器码写,代码体积会迅速膨胀到磁芯存储器装不下的程度。解释器虽然增加了运行时开销,但它让任务程序的指令密度大幅提高,同样的功能占用的存储空间小得多。

而且解释器还带来了一个额外好处:它把数学运算(比如三角函数、向量运算)封装成了伪指令,任务程序不需要关心底层怎么实现,只需要调用。这在当时没有高级语言、没有标准库的环境下,等于自己造了一套领域特定语言(DSL)。

实测下来,这个解释器的执行效率大约是直接机器码的十分之一到五分之一,但换来的存储节省是数倍。在存储比算力更稀缺的场景下,这笔账算得过来。

2. 核心硬件细节与实操要点

2.1 处理器结构:15 位字长背后的计算逻辑

AGC 的处理器字长是 15 位,不是今天常见的 8 位、16 位、32 位。这个选择跟它的应用场景直接相关。制导计算需要处理的角度、速度、位置数据,精度要求大约在 1/2^14 左右,15 位字长(1 位符号位 + 14 位数值位)刚好能满足这个精度,同时比 16 位省一位存储。

指令格式是单字长指令,一条指令 15 位里包含了操作码和地址。操作码占 3 位,地址占 12 位,所以直接寻址范围是 4096 字。但 AGC 的存储空间是 36K 字,远超 12 位能表示的范围,所以它用了存储体切换(bank switching)机制——通过特殊的指令切换当前存储体,间接扩展寻址范围。

寄存器方面,AGC 有一个累加器 A、一个低位累加器 L(用于双精度运算)、一个程序计数器 Z、一个存储地址寄存器 S、一个存储数据寄存器 G,以及若干变址寄存器。这套寄存器配置非常精简,但配合解释器使用,足够完成所有制导计算。

实操心得:在模拟器里调试 AGC 程序时,累加器 A 和 L 的联动是最容易出错的地方。双精度加法、乘法都会同时用到这两个寄存器,单步跟踪的时候一定要同时观察 A 和 L 的值,只看 A 会漏掉进位和溢出。

2.2 磁芯 rope 存储器的模拟要点

磁芯 rope 存储器的物理结构是一组磁环,导线穿过磁环的方式决定了存储的位值。在模拟器里,这部分通常用一个只读数组来表示,但有几个细节必须注意。

第一是地址映射。AGC 的存储空间分为固定存储(rope)和可擦写存储(core)两部分。固定存储存放程序代码和常量,可擦写存储存放运行时变量。两者的地址空间是连续的,但访问特性完全不同。模拟器需要正确区分哪些地址是只读的,哪些是可读写的。

第二是存储体切换的时序。AGC 的存储体切换不是瞬时的,切换指令执行后需要几个机器周期才能生效。如果模拟器忽略这个延迟,程序在切换后立即访问新存储体的数据就会读到错误的值。

第三是奇偶校验。AGC 的存储字包含奇偶校验位,读取时会自动校验。如果模拟器不模拟这个机制,程序在遇到存储错误时的行为就跟真实硬件不一致。真实硬件在检测到奇偶错误时会触发硬件异常,AGC 的软件有专门的错误处理程序来应对。

2.3 输入输出接口:DSKY 与 AGC 的交互方式

DSKY(Display and Keyboard)是宇航员与 AGC 交互的唯一界面。它有一个数字显示屏、若干状态指示灯、以及一个数字键盘。宇航员通过输入两位数的“名词”和“动词”来调用特定的程序功能,比如“名词 01”表示显示当前时间,“动词 06”表示开始执行某个程序。

DSKY 与 AGC 之间的通信是通过一个专门的接口电路完成的。AGC 定期扫描 DSKY 的键盘状态,检测到按键后读取键码,然后根据当前程序状态决定如何响应。显示更新则是 AGC 主动写入 DSKY 的显示寄存器。

这个交互模式在今天看很原始,但它的设计哲学很清晰:宇航员不需要记住复杂的命令,只需要输入数字代码。而且 DSKY 的显示和键盘是分离的,显示内容由 AGC 控制,键盘输入由宇航员操作,两者通过 AGC 的程序逻辑协调。

注意:在模拟器里复现 DSKY 交互时,按键去抖动逻辑必须实现。真实 DSKY 的键盘是机械触点,按下和释放时会有抖动,AGC 的扫描程序有专门的去抖动处理。如果模拟器直接读取按键状态,会出现一次按键被识别成多次的问题。

3. 实操过程与核心环节实现

3.1 搭建 AGC 模拟环境的完整步骤

如果你想自己跑一遍 AGC 的程序,最直接的方式是用现成的模拟器。目前社区里比较成熟的有两个方向:一个是纯软件模拟器,用 C 或 Python 实现 AGC 的指令集和外围设备;另一个是基于 FPGA 的硬件模拟,用真实的磁芯存储器或者 SRAM 来模拟存储行为。

纯软件模拟器的搭建步骤大致如下。首先获取 AGC 的指令集文档和存储映射表,这些资料在社区里是公开的。然后实现处理器核心:指令译码、寄存器操作、存储访问、中断处理。接着实现存储系统:固定存储用只读数组,可擦写存储用可读写数组,注意地址映射和存储体切换。最后实现外围设备:DSKY 的显示和键盘、IMU(惯性测量单元)的数据接口、以及发动机控制信号。

代码结构上,我建议把处理器核心和外围设备分开,用事件驱动的方式连接。AGC 的很多操作是异步的,比如 IMU 数据更新、DSKY 按键、发动机状态变化,这些事件需要按时间顺序注入到模拟器里。

# 简化的 AGC 模拟器主循环结构 class AGC: def __init__(self): self.memory = [0] * 36864 # 36K 字存储 self.registers = {'A': 0, 'L': 0, 'Z': 0, 'Q': 0} self.running = True def step(self): # 取指 instruction = self.memory[self.registers['Z']] # 译码 opcode = (instruction >> 12) & 0x7 address = instruction & 0xFFF # 执行 self.execute(opcode, address) # 更新程序计数器 self.registers['Z'] = (self.registers['Z'] + 1) & 0xFFF def execute(self, opcode, address): # 根据操作码执行对应操作 pass

3.2 加载 Luminary 099 程序并运行

AGC 的登月舱程序 Luminary 099 是公开的,你可以找到它的 rope 存储器镜像文件。加载过程需要注意几点。

第一是镜像格式。Luminary 099 的镜像通常是按存储体组织的二进制文件,每个存储体对应一段连续的地址空间。加载时需要按照存储体映射表把数据放到正确的地址。

第二是启动序列。AGC 上电后会执行一段自检程序,检查存储器和处理器是否正常。自检通过后进入待机状态,等待 DSKY 输入。如果自检失败,DSKY 会显示错误代码。

第三是程序选择。AGC 的固定存储里存放了多个程序段,包括制导程序、导航程序、控制程序、显示程序等。运行时根据任务阶段选择加载哪些程序段。登月阶段主要用的是 P63(制动阶段)、P64(接近阶段)、P65(着陆阶段)、P66(手动控制阶段)这几个程序。

实测下来,从加载镜像到程序正常运行,最容易出问题的环节是存储体切换的初始化。AGC 上电后默认的存储体配置可能跟程序期望的不一致,需要在启动序列里显式设置。

3.3 复现阿波罗十一号着陆最后阶段的关键计算

阿波罗十一号着陆的最后阶段,AGC 的 P66 程序接管了控制。这个阶段的核心任务是:根据宇航员的手动输入和雷达高度数据,计算发动机推力指令,控制登月舱以安全速度接触月面。

计算过程大致是这样的。首先读取雷达高度和下降速度,然后根据预设的着陆轨迹计算期望高度和期望速度。实际高度和期望高度的差值、实际速度和期望速度的差值,分别输入到比例-积分-微分(PID)控制器里,输出推力调整量。最后把推力调整量转换成发动机的节流阀指令。

这个计算每秒钟执行多次,每次计算的结果通过 DSKY 显示给宇航员,同时发送给发动机控制器。宇航员可以根据显示数据判断是否需要手动干预。

实操心得:在模拟器里复现这个计算时,雷达数据的噪声模拟很关键。真实雷达有测量误差,AGC 的软件有专门的滤波算法来处理。如果模拟器直接给理想数据,PID 控制器的行为会跟真实情况差很多,尤其是积分项会积累出错误的偏差。

4. 常见问题与排查技巧实录

4.1 程序跑飞:存储体切换错误的排查思路

AGC 程序跑飞最常见的原因是存储体切换错误。表现是程序计数器跳到一个不该去的地址,或者读取到错误的数据。排查思路如下。

首先检查存储体切换指令的执行时序。AGC 的存储体切换需要几个机器周期才能生效,如果切换后立即访问新存储体,可能读到旧存储体的数据。在模拟器里,这个延迟必须正确实现。

其次检查存储体映射表。AGC 的存储体映射不是线性的,有些地址段是固定的,有些是可切换的。如果映射表配错了,程序访问某个地址时会落到错误的存储体。

最后检查中断处理。AGC 的中断处理程序可能会修改存储体配置,如果中断处理没有正确保存和恢复存储体状态,返回主程序后就会跑飞。

4.2 DSKY 无响应:键盘扫描与显示更新的时序问题

DSKY 无响应通常有两个原因:键盘扫描没跑起来,或者显示更新被阻塞。

键盘扫描是 AGC 的一个后台任务,定期读取键盘状态。如果这个任务的优先级被其他任务抢占,或者扫描周期设置得太长,按键就会丢失。检查方法是看 AGC 的任务调度表,确认键盘扫描任务的执行频率。

显示更新被阻塞通常是显示程序出了问题。AGC 的显示程序负责把数据转换成 DSKY 能识别的格式,然后写入显示寄存器。如果显示程序陷入死循环,或者显示寄存器的地址配错了,DSKY 就不会更新。

注意:DSKY 的显示寄存器是只写的,读回来的是上次写入的值。如果程序逻辑依赖读取显示寄存器来判断状态,会得到错误的结果。正确做法是维护一个软件副本,写入显示寄存器的同时更新软件副本。

4.3 常见问题速查表

问题现象可能原因排查方法解决方案
程序计数器跳转到非法地址存储体切换时序错误单步跟踪切换指令后的几条指令在切换指令后插入延迟
DSKY 按键无响应键盘扫描任务未执行检查任务调度表提高键盘扫描任务优先级
显示数据不更新显示程序死循环检查显示程序的循环条件修复循环退出条件
计算结果偏差大雷达数据噪声未模拟对比理想数据和实际数据加入噪声模型和滤波器
中断处理后程序跑飞存储体状态未保存检查中断处理程序的保存/恢复逻辑在中断入口保存存储体状态
自检失败存储器镜像加载错误校验镜像文件的校验和重新加载正确的镜像

4.4 独家避坑技巧:从实际调试中总结的经验

第一个技巧是关于存储体切换的。AGC 的存储体切换指令执行后,下一条指令的取指可能还在旧存储体里。所以切换指令后面最好跟一条空操作(NOP),给硬件足够的时间完成切换。这个技巧在官方文档里没有明确写,但从实际运行结果看是有效的。

第二个技巧是关于中断的。AGC 的中断处理程序执行时间必须尽可能短,因为中断期间其他任务都被挂起。如果中断处理程序里做了复杂的计算,会导致任务调度延迟,影响制导计算的实时性。我的做法是把中断处理程序里非紧急的部分放到后台任务里执行。

第三个技巧是关于 DSKY 显示的。DSKY 的显示刷新频率不需要很高,人眼对闪烁的感知阈值大约在 50 Hz 左右,但 AGC 的显示程序如果每秒钟刷新几十次,会占用大量处理器时间。实际设置成每秒钟刷新几次就足够了,把省下来的时间留给制导计算。

第四个技巧是关于模拟器调试的。在模拟器里加一个指令跟踪日志,记录每条指令的执行地址和关键寄存器值。程序跑飞的时候,回看日志就能快速定位到出问题的指令。这个日志不需要一直开着,只在调试特定问题时开启就行,否则日志文件会迅速膨胀到几个 G。

5. 从 AGC 项目里能带走什么

5.1 资源约束下的设计哲学

AGC 项目最值得带走的东西,是它在极端资源约束下的设计哲学。存储比算力稀缺的时候,用解释器换存储空间;可靠性比性能重要的时候,用磁芯存储换抗辐射能力;实时性要求高的时候,用中断和任务调度保证关键任务的执行。

这些取舍逻辑在今天依然适用。做嵌入式开发的时候,如果 Flash 空间紧张,可以考虑用压缩或者解释执行的方式减少代码体积。做高可靠性系统的时候,如果运行环境恶劣,可以考虑用非易失性存储或者冗余设计。做实时系统的时候,如果任务多而处理器慢,可以用优先级调度保证关键任务的响应时间。

5.2 软件工程层面的启示

AGC 的软件工程实践也有不少值得借鉴的地方。它的程序是用一套伪指令写的,然后由解释器执行。这套伪指令的设计非常克制,只包含了制导计算必需的操作,没有多余的功能。这种“够用就好”的设计思路,在今天做领域特定语言或者框架设计的时候,仍然是一个重要的参考。

另外,AGC 的软件验证流程极其严格。因为程序烧录到磁芯 rope 存储器之后就不能改了,所以发射前的验证必须覆盖所有可能的场景。这种“一次做对”的要求,在今天做关键系统开发的时候,仍然是一个值得追求的目标。

5.3 后续可以继续折腾的方向

如果你对 AGC 项目感兴趣,后续可以继续折腾的方向有几个。一是把 AGC 的模拟器移植到 FPGA 上,用真实的硬件时序来运行程序,观察跟纯软件模拟的差异。二是复现 AGC 的制导算法,用现代编程语言重新实现,对比计算结果。三是研究 AGC 的容错机制,看看它是怎么处理存储错误、处理器错误、传感器错误的。

我个人在实际操作中的体会是,AGC 这个项目最大的价值不在于它有多复杂,而在于它用极其有限的资源完成了极其复杂的任务。这种“在约束下做工程”的能力,是每个工程师都应该具备的。每次我觉得自己的资源不够用的时候,想想 AGC 的 36K 存储和 2 MHz 主频,就会觉得其实还有很大的优化空间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 1:36:05

Unity字体裁剪性能优化方案:从动态字体到静态图集

如果你接手过一个中大型Unity项目,大概率遇到过这样的场景:界面功能都做完了,真机一发版,聊天框一打开就顿一下,Profiler里跳出的是字体纹理重建。这通常不是你的业务代码有问题,而是字体裁剪没做好。Unity…

作者头像 李华