用现代语言复刻阿波罗11号制导计算机:AGC模拟器开发全记录
1. 项目溯源为什么用现代语言重新拼装一台50多年前的制导计算机1.1 这个项目解决的核心问题“Apollo-11”这个项目名听起来很有历史感但它并不是在讲登月纪录片而是一个纯软件层面的复刻工程用现代编程语言重新实现一台以“阿波罗11号制导计算机AGCApollo Guidance Computer”为蓝本的虚拟机并在上面跑通原始的指令流程序。刚接触的人可能会问都有人类登月这种“真刀真枪”的记录了为什么还要在模拟器里重新做一台老计算机原因很简单当年那批硬件已经找不齐了可留存下来的存储器内容、指令手册和接口规范却是完整的。把这些代码跑在仿真环境里既能验证当年的逻辑设计也让今天的人有机会亲手操作一下半个世纪前的“智能设备”。我在动手之前给自己定了一个非常务实的边界不需要重建整机也不需要还原每一个电阻电容的行为只需要做到指令集兼容、内存映射正确、显示器按键可用并且能加载经典的飞行程序。换句话说这是一个“功能级”模拟器而不是“电路级”模拟器。功能级的好处是开发速度快坏处是一旦出现和原始行为不一致的Bug你得靠逻辑推导而不是示波器去查问题。1.2 从“裸机”到“可运行”目标范围界定如果你去翻当年那台机器的技术参数会发现它的硬件规模放到今天简直“可怜”36位字长、15位指令字内存分成固定存储和可擦除存储两大部分加起来大约只有几十KB。但它的奇特之处在于指令编码、寻址模式、中断机制都与现代CPU完全不同甚至在字长上都和主流8/16/32位体系对不上。这意味着编写模拟器时不能直接套用“读内存-译码-执行”的通用模板而要把它的外设控制器、按键面板、数字显示器全部考虑进来。我最终确定的可交付成果包含三块内容核心处理器模拟器、DSKY显示键盘面板交互界面、以及可加载的演示程序段。这样的范围既能让一个对计算机组成原理只懂皮毛的初学者在半小时内看到“程序运行起来了”也能让研究嵌入式系统的开发者深入研究会溢出、存储器分体和中断优先级等底层概念。2. 技术选型模拟器不是翻文档猛敲代码2.1 指令集仿真为什么必须从“外设”反向设计很多人在开始写模拟器时习惯先列一条指令表一条一条翻译成函数等把所有指令写完再去调试。我在做这个项目时走了相反的路先分析面板上的操作需求再来确定仿真深度。DSKY是AGC唯一的用户交互入口上面有数字显示器、程序编号灯、动词键和名词键。用户按一个“Verb 32”实际上是在告诉计算机“把呼出代码放到指定寄存器”这个动作会触发一连串内存读写和显示刷新。如果不先把外设协议定义清楚就算把TC、CCS这类指令写得再完美程序一跑起来也看不见任何反馈。这种反向设计的好处是每个指令函数的外围状态都是明确的模拟器的“主循环”不再是穷举指令而是“等待输入—刷新显示—执行一小段计算”的有限状态机。实际写代码时我甚至先做了一个只有显示刷新和按键扫描的壳子然后才往里填指令执行逻辑。2.2 架构设计与内存映射先拉一条16K字的内存基线AGC的内存分为两大区域可擦除存储erasable memory和固定存储fixed-fabric memory。固定存储类似于今天的ROM内容是出厂写死的程序和数据可擦除存储则相当于RAM存放临时变量、轨道参数和任务状态。固定存储通过“存储体切换”bank switching机制访问不同地址区间在同一时刻可以映射到不同的物理存储体。为了保持模型可管理我在模拟器里把内存组织成一张逻辑表区域起始字地址结束字地址作用可擦除存储0000000777临时变量、堆栈、中断现场固定存储公共区0100001777常用子程序、跳转表固定存储扩展体0200003777按需切换的代码段固定存储扩展体上层0400007777高地址常量与代码在具体实现里我用两个数组分别表示RAM和ROM再用一个“当前固定存储体”寄存器来决定访问哪一个ROM子分区。很多旧程序Bug都出在“明明调用了某个地址却因为bank切换错误读到了另一段代码”所以模拟器里还得提供对bank状态的日志记录。2.3 DSKY交互单纯算对指令还不够DSKY看起来像一个老式计算器但它不是外接终端而是AGC整体的一部分。它的输入不是字符流而是“按钮事件”输出也不是屏幕上的命令行文本而是七段数字显示管外加一组程序状态灯。在编写映射代码时我把每个按键解释成一个中断请求每个数字位解释成一组布尔状态显示刷新周期控制在几十毫秒。开发到这一步我才意识到一个指令正确的模拟器未必“可操作”因为原始程序可能在等待某个特定按钮事件组合如果你只是把内存里的数字算对了但是界面没有任何变化用户会以为程序死机了。所以我额外设计了一套“操作回放”功能模拟器可以把用户过去的按键序列保存下来下一次启动时自动重现方便追踪程序的分支行为。3. 核心细节深挖AGC指令流里的几个硬骨头3.1 操作码解码从15位字里读出意图AGC的指令字是15位最高位经常用于标识“该指令是否为特殊调用”剩余位被拆成操作码、地址和累加器标志位。不同指令的长度不一样某些指令需要读取两个内存字因此程序计数器需要根据当前指令跳变不同的步长。为了让解码逻辑不那么“魔法数字堆砌”我写了一个指令映射表把操作码翻译成可读的助记符再统一分发到执行函数。这里举一个实际例子指令“TC”Transfer Control本质上就是跳转但它有两个变体直接跳转和通过索引寄存器间接跳转。如果模拟器忽略间接跳转的条件很多分支代码会走进完全错误的位置。我在解码入口处加了一个断言凡是执行到未定义操作码就立即停止并打印寄存器上下文既方便调试也避免静默错误扩散。# 简化的指令解码示例展示了如何把15位字拆成操作码和地址 def decode(word): opcode (word 10) 0b11111 address word 0x3FF indirect (word 15) 1 return opcode, address, indirect3.2 补码运算与溢出老机器的算术规则比想象中繁琐现代CPU的整数加法基本采用补码溢出后直接丢掉进位或者设置一个眼见不一定为实的标志位。AGC则很特别它的数值运算大量依赖“循环累加”结构并且要求程序员时刻关注进位状态。模拟器里最值得小心的是“累加器溢出”行为。如果我只是按现代无符号数的规则单纯截断结果程序后续的所有比较就会出错。我采用的方法是单独维护一个溢出标志寄存器并在加减法完成后立即根据操作数和结果计算是否发生符号变化。DSKY上显示的数值也要经过补码转码不能直接把内存二进制呈现出来。踩过几次补码和原码混用的坑之后我总结出一条经验在模拟老机器时任何“看起来等价”的现代算术替换都很危险因为指令集文档描述的是行为而不是数学定义。3.3 中断模型与实时性模拟器里也要有“钟表”AGC并不是一个单线程轮询的控制器它有五个中断源每个中断源对应不同的优先级。模拟器不需要精确到毫秒级的中断触发但必须正确处理“中断嵌套”和“现场恢复”。具体来说当一个中断发生时CPU把当前程序计数器和累加器压入内存特定的保存区再跳转到固定的中断服务入口。我在实现中断时建了一个小任务队列界面按键、定时器溢出、以及用户外部信号都可以向这个队列投递事件主循环按优先级取出待处理事件并调用对应的中断处理程序。这种设计有一个额外好处模拟器不会被某个长计算任务阻塞DSKY的刷新时刻保持流畅。若不做这种异步处理用户点一下按钮可能要等模拟程序跑完几百条指令才能看到响应体验非常糟糕。4. 实操复盘从空文件到屏幕上跳出P014.1 启动流程与初始化引导第一次拿到一份AGC存储器转储文件时我并不知道应该从哪里开始执行文档里只说“复位后从固定存储的某个地址启动”。更实际的做法是找一条已知的初始化例程观察它引用了哪些RAM地址再反推出初始状态。调试时我在模拟器里设置了几个“哨兵地址”每当程序写入固定RAM的某一范围都会打印状态这帮助我很快确定了堆栈指针的初始位置。如果你也想复现这一步我建议不要直接试图运行完整的飞行程序而是先从一段“自检程序”开始——让CPU执行一段简单的循环每执行一条指令就改变一次显示灯这样可以验证指令集和显示系统是否同时工作。等到基础功能稳定后再加载完整的任务程序。4.2 一个可以手动运行的“轨道积分”小任务为了让模拟器不止是“能开机自检”我还写了一个演示任务输入一组模拟加速度数据计算速度增量并更新显示。它不需要复杂的动力学模型只需要用到累加器读取、内存写入和DSKY显示刷新。用户通过面板输入初始速度程序按固定步长累加加速度最终把结果输出到显示器。这个任务虽然简单却覆盖了AGC最核心的输入—计算—输出链路。为了让读者能自己尝试我把任务的关键流程整理成了一张表步骤输入操作程序行为1按下Verb 32清除缓冲区进入数据输入模式2拨动速度拨轮开关把开关状态写入对应寄存器3按下Proceed键启动累加循环4观察显示变化每秒刷新估算的速度值5按下Reset键清除中断返回待命状态4.3 验证方法用固定输入跑已知结果模拟器的最大风险是“看着在运行实际在乱跑”。我采用的验证方法是构造一批固定输入在真实文档给出的结果上做对照。例如在特定R寄存器填入初值运行P21程序后查表读取输出寄存器的期望值。只要存在一个不匹配我就把执行过程逐条指令打印出来手工推演其中某一段。某次我在验证加法指令时发现结果差了1查到最后竟然是“读循环累加器”时把进位标志复位了导致下一轮的加法少加了1。这种问题在写应用代码时几乎不会遇到但在模拟器开发里却属于高频坑。后来我干脆写了一个自动回归测试脚本把所有已通过的小任务用例集中跑一遍任何改动后都能立刻知道有没有破坏老功能。5. 排障实录模拟器里最隐蔽的五个坑5.1 时序错位第一次完整加载启动程序时屏幕上的数字“亮”了但闪烁频率完全随机。后来发现是我把DSKY刷新循环和CPU执行循环放在同一个线程里CPU执行到长任务时刷新被卡住。解决办法是把显示刷新放到独立时钟域里用一个定时器保证每秒至少刷新8次。对于大多数人来说纯JavaScript或Python模拟器里用setTimeout/threading都能解决但要注意共享内存的加锁。5.2 内存bank切换AGC的固定存储切换不是靠一条专用指令而是靠修改某个固定RAM地址的值。只要程序写入该地址后续的取指范围就变了。我最初把这条规则写死在读取函数里导致某些指令边执行边切换bank最终跳进了“随机”地址。解决办法是在每次取指后重新检查bank状态并把这部分逻辑抽成独立函数加日志输出。5.3 DSKY的“鬼影数字”模拟器调试过程中出现过“数字没有输入却自己变化”的情况。查了很久才明白原始程序的显示缓冲区和内存变量是同一片存储区计算任务可能顺带改写了显示区。解决办法是在显示刷新流程里加一个校验和如果数值被非显示指令改写就发出警告。这个现象其实非常有教学意义它体现了老式嵌入式系统缺乏内存保护的特点。5.4 输入开关消抖实体DSKY的开关是物理拨轮天然具备一定的触点抖动模拟器按键一眼看过去是“瞬时”的但没有消抖处理会导致程序把一个按键识别成多次事件。我给按键事件加了一个简单的10毫秒窗口相同按键在窗口内只触发一次中断。另外为了调试我还保留了“强制抖动模式”方便测试代码对错误输入的容忍能力。5.5 指令自修改AGC程序允许在运行时把数据写入固定存储对应的地址吗从硬件上是不能写的但某些早期程序为了实现参数覆盖会利用可擦除存储和固定存储之间的特殊映射“伪造”写入效果。模拟器如果只是单纯拒绝写操作会让这类程序无法运行。我采用了一个兼容方案允许写操作但记录一个警告日志并在界面提示“该程序使用了非标准自修改行为”。6. 接下来的扩展空间6.1 从纯软件到FPGA周期级精度功能级模拟器做出来之后我最大的体会是“算对了并不等于跑得快”。如果你希望模拟出的程序在时序上和真实硬件一致就得考虑指令周期差异。目前我还没有把所有指令的周期数标定到文档里的精确数值但已经在数据结构里预留了“计时函数”接口。下一步我打算把核心CPU逻辑搬到一个FPGA开发板上跑一遍让DSKY用真实数码管来显示感觉完全不一样。6.2 让模拟器“飞”起来接入状态方程演示任务里我用的只是固定加速度输入如果把它替换成一组真实动力学方程就能把模拟器变成一个简陋的“制导仿真台”。到那时候显示的不再是简单速度增量而是脱靶量、剩余飞行时间等参数。虽然这和真实任务还有很大差距但已经能让人直观体会到原始制导软件的运行逻辑。我个人在实际操作中的体会是做模拟器最难的不在指令集而在“把自己逆回到几十年前的程序员视角”。每当你觉得一个现代CPU特性是理所当然时老机器就会抛出一个新的意外。但也正是这些意外让人真正理解计算机体系结构为什么演化成了今天这个样子。如果你也想挑战一个复古计算项目建议从最简单的DSKY交互做起先看到数字能亮起来再谈指令集和中断那种成就感会推着你一直做下去。

相关新闻

Tomcat 7.0.103 Windows部署指南:环境配置与避坑实践

Tomcat 7.0.103 Windows部署指南:环境配置与避坑实践

简介:apache-tomcat-7.0.103-windows-x64.zip 是面向 Java Web 初学者与中小型项目开发者的免安装 Tomcat 运行环境,解压即可使用,省去繁琐的配置步骤,适合本地调试 Servlet、JSP 及部署轻量级 Web 应用。压缩包共 648 个文件&…

2026/10/15 16:37:47 阅读更多 →
工业级多源融合定位系统:IMU主干+GNSS校准+激光约束

工业级多源融合定位系统:IMU主干+GNSS校准+激光约束

1. 项目概述:这不是登月任务,而是一套高精度工业级实时定位与建图系统“Apollo-11”这个名称一出现,很多人第一反应是1969年的载人登月——但今天我们要聊的,是某自动驾驶实验室内部代号为“Apollo-11”的一套嵌入式多源融合定位系…

2026/10/15 16:37:47 阅读更多 →
Java聊天程序实战:Socket服务端与客户端多线程编程详解

Java聊天程序实战:Socket服务端与客户端多线程编程详解

简介:面向Java网络编程初学者与课程设计者的一份C/S结构聊天程序源码包,整合服务端与客户端完整实现,适合用来理解Socket通信、多线程处理与GUI界面搭建。程序由ChatServer服务端、ChatHandler聊天线程及ChatRoom客户端组成,服务端…

2026/10/15 16:37:47 阅读更多 →

最新新闻

Seldon Core 2 实战:用 Pipeline 把 CIFAR10 图像分类器与异常检测、漂移监控打包为生产级推理服务

Seldon Core 2 实战:用 Pipeline 把 CIFAR10 图像分类器与异常检测、漂移监控打包为生产级推理服务

人工智能大模型MLOpsLLMOps模型推理服务云原生后端 【免费下载链接】seldon-core An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models 项目地址: https://gitcode.com/gh_mirrors/se/seldon-core 点击查看…

2026/10/15 17:32:52 阅读更多 →
树形DP实战:将功补过类问题的状态设计与转移方程

树形DP实战:将功补过类问题的状态设计与转移方程

1. 从标题拆解这道题到底在考什么 “将功补过”配上“树形DP”,这个组合一出来,有经验的选手基本就能嗅到味道了。这不是一道单纯的树上背包,也不是简单的最大独立集,它把“功”和“过”这两个对立的概念同时挂在树结构上&#xf…

2026/10/15 17:32:52 阅读更多 →
海波龙(Oracle 产品)实施商怎么选?7个评估维度+服务商能力对比

海波龙(Oracle 产品)实施商怎么选?7个评估维度+服务商能力对比

海波龙(Oracle 产品)实施商怎么选?7个评估维度服务商能力对比海波龙(Oracle 产品)选型时,企业往往把注意力放在产品模块和功能演示上,等到项目进入第二年,真正决定体验的却是当初挑的…

2026/10/15 17:32:52 阅读更多 →
STM32F103C8T6驱动SG90舵机:PWM源码与调试避坑指南

STM32F103C8T6驱动SG90舵机:PWM源码与调试避坑指南

简介:这份源码面向嵌入式初学者与需要快速验证舵机控制的开发者,围绕STM32F103C8T6如何驱动SG90舵机展开,解决从PWM信号生成到角度精确控制的实际问题。资源包共4个文件,包含2个C源文件、1个头文件与1张舵机接口示意图&#xff0c…

2026/10/15 17:32:52 阅读更多 →
ZLG Python二次开发实战:CAN总线数据采集与避坑指南

ZLG Python二次开发实战:CAN总线数据采集与避坑指南

简介:这份资源是面向嵌入式与物联网开发者的ZLG DTU系列Python二次开发示例包,重点围绕DTU 200UWGR及CAN-DTU系列无线数据传输单元,帮助解决官方demo在特定环境下无法直接运行、功能不足等问题。包内共124个文件,以98个xml配置与接…

2026/10/15 17:32:52 阅读更多 →
herdr-gpui Teleport 深度解析:带着未提交改动、运行中进程和 Agent 会话跨主机迁移工作树

herdr-gpui Teleport 深度解析:带着未提交改动、运行中进程和 Agent 会话跨主机迁移工作树

【免费下载链接】herdr-gpui Native client for Herdr on macOS, Linux, and Windows, built with Rust and GPUI. View terminal sessions, workspaces, Git worktrees, and agent activity through your local Herdr daemon. 项目地址: https://gitcode.com/gh_mi…

2026/10/15 17:31:51 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →