资讯详情

复刻阿波罗11号导航计算机AGC:异步计算与FPGA实践

📅 2026/10/15 16:38:30 | 华诺云谱 👁 阅读
复刻阿波罗11号导航计算机AGC:异步计算与FPGA实践
我一直觉得Apollo-11留给后人的除了那个被无数人重复讲述的历史瞬间还有一台不太起眼的“铁皮盒子”。大多数人听说过它叫AGC却很少有人真的打开原理图看一眼那是一台上世纪六十年代的机载计算机没有硬盘、没有显示器甚至没有一个完整意义上的主时钟。为了把这条历史脉络摸透我过去半年做了一个复刻项目——从寄存器堆到DSKY键盘显示单元尽量用现代器件把 Apollo-11 的导航计算机重新“搭”出来。这篇文章不是什么严谨的历史考据更像一份项目笔记我会讲AGC到底在任务里负责什么为什么它的字长是15位而不是8位/16位为什么没有主时钟也能运行程序以及我在用FPGA复刻时踩过的三个大坑。无论你是想自己动手复刻一台还是纯粹对被登月光环盖住的计算机技术感兴趣这篇都能给你些有用的参考。1. AGC不是“微型电脑”它是给宇航员当副驾驶的异步计算器1.1 它在Apollo-11里的真实分工先说清楚一个容易被忽略的事实Apollo-11上的宇航员并没有“手动开飞船”开到月球。大部分时间飞船的导航、姿态控制和发动机点火时序是由机载计算机完成的宇航员通过面前那个叫DSKY的键盘显示单元与它对话。DSKY长得像一只加厚版的收银机上面有一排数字/正负号按键、九个“动词”按键和九个“名词”按键。宇航员想要查看某个导航参数就先按动词再按名词计算机把结果显示在数字区。AGC在任务里的角色简单来说就是“副驾驶加上算盘”它接收惯性测量单元里陀螺和加速度计的输出把那些脉冲计数换算成速度、位置根据预装的轨道数据在合适的时候触发发动机点火时序同时还要管理飞船的姿态让服务舱发动机的推力方向尽量准确。所有这一切都在一台重量约三十多公斤、功耗不算低的计算机里完成。放到今天这套配置连一块智能手表的算力都比不过——它没有浮点单元没有操作系统程序直接写在磁芯内存里甚至很多导航公式都是查表算出来的。但正是这样一台“简陋”的设备完成了载人登月最关键的计算工作。我在复刻的时候才真正意识到当时的工程师不是把复杂度藏进硬件而是把整个系统简化到每一分资源都用在刀刃上。1.2 15位字长、没有主时钟两个最反直觉的设计第一次看AGC寄存器和数据通路的时候我以为自己眼花它居然不是16位计算机而是15位字长。准确说每个存储单元是15位数据位加1位奇偶校验位物理上走16根线。为什么不是规整的16位数据字因为当时存储器件和逻辑设计都追求极致的节省15位足以表达指令和数据而那1位校验位作为完整性保护非常划算。这种“数据位加校验位凑成总线宽度”的思路后来在我复刻内存模块时带来了不少麻烦。更反直觉的是AGC没有中央时钟。现代CPU不管多复杂都有一个主频在背后推动所有逻辑AGC不是这样。它每执行一条指令是在总线上发出一个启动脉冲然后一条由单稳态电路和延时网络组成的“脉冲链”会在几十微秒内依次打开各个寄存器门、触发内存读写、计算ALU结果。你可以把它想成一台完全靠电路本身的时序级联来运作的机器而不是靠某个统一的节拍器。没有主时钟意味着所有操作都更像“事件驱动”。这个设计在当年是为了省电、省器件但对复刻者来说相当“不友好”。当你想用现代逻辑去仿真它时如果直接给它加一个全局时钟就会丢掉原机最微妙的异步行为。我后来在调试时有一半时间都在模拟这种脉冲链的感觉。2. 复刻方案取舍为什么我放弃了C模拟器和单片机最后选了FPGA2.1 三条技术路线的对比复刻AGC这种古董计算机网上能查到的路线大致有三条纯软件模拟器、单片机仿真、FPGA硬件复刻。我身边有朋友用C语言写了一个能在PC上运行的AGC模拟器能跑当年的汇编程序屏幕上画一个DSKY窗口。这个方案最适合研究软件行为但对硬件内部的总线竞争、时序关系几乎不关心也很难让你“摸”到机器的真实手感。用单片机做则是另一条路。单片机价格便宜、上手快但它的内部结构是串行执行加中断跟AGC那种寄存器阵列加异步脉冲链的并行结构相差很远。不是做不到而是很多逻辑都要“翻译”成单片机的C代码相当于用一把瑞士军刀去雕刻大理石能出活但很别扭。真正跑起来之后代码还很难跟原始原理图建立对应关系。比较下来FPGA最贴近原机的本质它有真正的并行逻辑可以自由定义寄存器和总线Verilog能直接描述字级的数据通路而不是像C那样把所有东西都摊成一个循环。更实际的一点是FPGA板上可以直接接我的DSKY键盘矩阵和数码管做完的成果是一个看得见摸得着的设备而不是一个命令行窗口。方案上手难度与AGC结构吻合度能跑原始程序硬件交互C模拟器低低能需要额外GUI单片机中中能但很费力可以但同步复杂FPGA高高能天然适合外设我最终选了FPGA。核心理由是我想研究的不仅是AGC的行为还有它那种“没有主时钟的计算方式”。只有用Verilog把寄存器堆、总线、时序发生器按原理图重新组织起来才能真正理解为什么当年的人敢把那么重要的任务交给一台异步机器。2.2 顶层模块划分与我的Verilog骨架在FPGA工程里我把整套系统拆成了几个顶层模块CPU核心寄存器堆、ALU、时序发生器、存储器接口RAM和ROM、DSKY键盘扫描模块、以及显示驱动模块。AGC的存储分两类一类是“可擦除存储”——也就是RAM另一类是“固定存储”——就是只读ROM。可我一开始差点做错把ROM镜像直接当成现代二进制文件往里塞后来才发现原机镜像里每条“字”都带着独立校验位。这一点等下专门说。下面是我简化后的顶层模块骨架你可以看到CPU核心和外设模块之间我刻意保留了“外部时钟”和“脉冲启动”两套信号module agc_top ( input wire ext_clk, // 仅用于DSKY扫描和调试显示 input wire start_pulse, // 模拟AGC的启动脉冲 input wire [11:0] key_row, // 键盘矩阵行 output wire [7:0] seg_data, // 数码管段码 output wire [3:0] seg_sel, // 数码管位选 output wire [3:0] alarm_led // 告警灯 ); // CPU内部寄存器堆 reg [14:0] A, L, Q, Z, S; // 累加器、返回地址、程序计数器等 // 存储器接口 wire [14:0] read_data; reg [14:0] mem_addr; reg read_strobe; // DSKY接口 wire [3:0] verb_code, noun_code; wire key_event; // 时序生成和寄存器门控逻辑省略 endmodule这段代码只是给个整体印象真正的细节都在每个模块内部的门控逻辑上。我个人强烈建议不要在CPU核心部分强行引入一个全局时钟来“统一调度”。外设模块当然可以用自己的时钟但CPU的核心数据通路最好还是模仿原机的脉冲链方式习惯了这套写法之后再读历史资料里的逻辑图会顺很多。3. 先啃下存储与指令系统再谈“织”进去的程序3.1 磁芯绳存储用“织布”的方式写只读程序AGC的固定存储区容量大约是36K字每字15位加1位校验位。这个体积在今天任何一个编译器分分钟就能写满但在当时这些程序不是“写”进去的而是“织”出来的。固定存储用了一种叫磁芯绳存储的物理结构一排排磁环像珠子一样被编成网格程序里的每一个0或1由一根导线是否穿过某个磁环来决定。导线穿过磁环代表1绕开磁环代表0。你可以把整个存储区想象成一块手工毯子每一针下去都在定义某个地址的某个bit。有一次软件改动往往意味着要从这张“毯子”里抽出一根线重新穿。当年的工程师拿着一张写满八进制代码的纸坐在工作台前像织工一样把程序“绣”进磁环阵列里——这种极端的制造方式后来再也看不到了。在我自己的FPGA实现里当然不需要真的去织线。我用只读存储器的模型把当年汇编器生成的ROM镜像按地址填入一个case语句但我保留了每个word的原始宽度和校验位没有为了方便而改成十六进制字节流。这样做的好处是只要镜像数据格式与原始机器一致我的调试过程和后来跑真实程序都能少踩很多坑。3.2 指令系统里的倔强简化指令、软件查表、显式返回地址第一次看AGC指令手册感觉像在读一个“极简主义宣言”。它没有现代意义上的复杂指令集很多操作都建立在单个15位字的读写和位操作上。若是想计算正弦、余弦硬件里并不会有专门的指令工程师直接把表格数据放在ROM里程序取整查表再插值。这种思路放到现在很笨拙但对当年那点存储和逻辑门数量来说是再合理不过的取舍。还有一个很“老派”的设计是子程序调用。现代CPU大多有调用栈或者至少自动保存返回地址AGC的做法简单粗暴你用转移指令跳转时返回地址被放进一个专门的寄存器子程序结束时再手动把它取回来。写汇编程序的人必须非常小心否则一个不小心嵌套调用就会把返回地址覆盖得干干净净。我至今记得第一次在模拟器里看一段老的AGC汇编代码满是手动保存地址、恢复地址、检测溢出、查表跳转之类的操作。和今天写高级语言完全不一样你要时刻清楚自己正在哪一段内存、哪个bank、链路寄存器里存的究竟是上面还是下面一层的返回地址。复刻完这套指令逻辑我最大的感受是——当年的系统能跑通几万行这种代码真是个奇迹。4. 通电调试里最该记住的三道坎4.1 第一道坎没有主时钟时CPU事件靠“脉冲链”驱动我先把Verilog工程写好烧进FPGA接上DSKY面板通上电结果第一件事就是所有逻辑像疯了一样乱跳。原因很简单我给CPU核心加了一个50MHz的全局时钟想用普通状态机来模拟AGC指令。但AGC的每一个操作本质上都是异步事件你用固定时钟去切状态所有“脉冲链”的顺序就变成了完全随机的时序竞争。我后来查了大量资料才真正明白问题的本质。原机里一条加法指令的动作不是“在某个时钟上升沿完成”而是“一个启动脉冲触发一连串门控信号”每个门控信号持续一段极短时间前一个信号的下降沿又触发下一个信号。我用逻辑分析仪看着自己板子上的信号发现某个寄存器的写入毛刺总在内存总线上造成竞争。解决办法是抛弃原来的“全局时钟派”写法把指令执行拆成几个握手阶段先请求存储等存储完成信号回来再进行ALU计算最后才把结果写回寄存器。这个过程在Verilog里更像“面包板接逻辑门”而不是写普通状态机。改完这版程序执行顺序才稳定下来。4.2 第二道坎DSKY按键处理不是“消抖”就完事硬件时序理顺之后我搭好DSKY的键盘矩阵接上一根根排线以为输入处理不过就是普通按键消抖。结果测试时发现每次我按一个“动词”键DSKY经常把同一个按键识别成两次有时又漏掉一次。刚开始我以为是轻触开关质量问题后来翻原版手册才发现AGC的键盘中断是由内部定时中断驱动的它大约每0.1秒才允许处理一次键盘输入。问题在于现代轻触开关的抖动时间虽然远小于0.1秒但我的扫描程序用的是外设自己的时钟给一个按键事件打上“有效”标记的时间窗口恰好可能和CPU读取键盘缓冲区的窗口错开造成一次物理按键被读成两次。最终我只好把键盘扫描做成独立状态机先用一个1kHz时钟做消抖消抖通过后只产生一个脉冲事件再由CPU的中断请求线捕获那个同步后的脉冲。这段经历让我意识到复刻古董计算机时最难的往往不是CPU本身而是让原机的“慢速逻辑”和现代外设的“快速扫描”恰好衔接起来。如果不理解系统里有两套不同的时间源这种问题会查到你怀疑人生。4.3 第三道坎奇偶校验位不是给你添麻烦的还有一个我差点忽视的安全问题ROM镜像里的奇偶校验位。前文说过每一个15位字后面跟着1位校验位。当我从网上开源工具链获取ROM镜像时我的脚本为了按“字节”处理直接把校验位剥离了。结果程序跑到一半AGC就触发告警灯DSKY显示异常的告警状态。我最初以为是程序代码没选对型号后来对比了完整镜像和去校验位镜像的二进制差异才明白问题出在这里。AGC硬件在读取固定存储时会实时检查校验位如果不对就会触发告警流程。当年这么做是为了防止导线编织错误或存储器件老化导致的静默错误。我建议所有复刻者在做数据转换时一定保留原始“字校验位”的格式不要让任何工具把15位数据当成16位字节流截断。这个坑不深但一旦踩进去排查起来非常浪费时间。5. 跑通一次最小演示以及新手复刻的起步路线5.1 最小演示从加电自检到DSKY上出现待机指示整套系统真正跑通是在我把存储器、中断、DSKY三个模块全部连好之后。我先烧了一个自己写的极小测试程序上电后依次点亮DSKY的数字各个段位再等待键盘输入。每按下一个数字键数码管就显示对应数字每组合一次“动词名词”就会把代码里预设的一组数据以八进制形式显示出来。虽然这个演示离“真正跑通AGC汇编程序”还有距离但看到键盘矩阵和显示单元被CPU正确识别的那一刻我还是挺有成就感的。紧接着我换上从开源工具链生成的原始AGC程序镜像DSKY的待机界面出来了程序计数器停在待命地址按键询问状态也能得到正确的数据。那种感觉不亚于第一次点亮一块新焊好的开发板。不过真实AGC的运行速度比我预想的慢。它执行一条指令要十几到几十微秒按照现代标准简直是像放慢动作。当你亲手用逻辑分析仪去抓这种“慢腾腾”的时序时反而更容易理解为什么当年的导航程序能做得那么紧凑、高效——每一微秒的计算窗口都是认真规划出来的。5.2 给准备动手的人我建议的起步路线和你最好避开的弯路如果你也对复刻AGC或类似老式计算机感兴趣我建议按下面这个顺序来先用模拟器跑通汇编指令。不要一上来就写FPGA因为你对指令行为还没有直觉。在FPGA上先做最小核心寄存器堆、ALU、几条指令加法、转移、读存储器就够了。把它做成一个“能工作的简化版AGC”再一点点往上加指令。做DSKY之前先把键盘扫描/数码管驱动单独验证确认外设时序和CPU不冲突。最后再把存储镜像切换成真实程序。切换那一刻才是真正的“历史重现”。避开的弯路不要一开始就追求完全复刻原机的时钟与门级实现那个工程量非常大。先做到“指令集和存储镜像兼容”就已经能学到很多了。很多人在这一步就停住了但光是理解AGC为什么没有时钟、为什么用15位字长这趟复刻就已经值回票价。如果让我从头再选一次我会把更多时间放在整理原始机器的时序解析上而不是急着写代码。生产环境里手头有逻辑分析仪的朋友可以把DSKY的中断请求线单独引出来观测这对排查按键重复触发和存储校验告警都极有帮助。希望这篇笔记能帮你少走点弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑