
在整理历史计算相关资料时我注意到一个很有意思的项目French CIIs Mitra-15 in SIMH. Work in Progress。简单来说这是一个把法国 CII 公司上世纪 70 年代推出的小型机 Mitra-15移植到经典模拟器框架 SIMH 中的项目而且当前仍然处于进行中的状态。很多人第一反应是都什么年代了为什么还要费力去模拟一台几十年前的老机器这个问题其实是整个小型机模拟技术发展的核心动力。模拟器不只是用来“怀旧”的它能让我们在不依赖物理硬件的情况下继续运行、分析和研究那些早已退出市场、甚至原件已经无法启动的计算机系统。对于学习计算机体系结构、操作系统的同学和开发者来说一台可运行的历史机型比任何架构图都更有说服力。这篇文章我会围绕 Mitra-15 与 SIMH 展开从背景概念、模拟器工作原理到项目环境准备、核心实现思路、调试验证方法再到常见问题与开源协作建议。既适合想了解历史计算机模拟的新手也适合准备参与这类开源模拟项目的开发者参考。1. 背景与核心概念1.1 Mitra-15 是谁Mitra-15 是法国 CII 公司Compagnie Internationale pour lInformatique国际信息学公司在 1970 年代初期推出的一款小型机。CII 是由多家法国计算机制造商合并成立的计算机公司目标是发展法国自主的计算机工业。Mitra-15 主要在法国工业界、科研机构以及高校中使用定位是面向实时控制、数据处理与科学计算的小型机。从当时的硬件特点来看Mitra-15 属于典型的小型机设计思路采用磁芯存储、前面板控制台、通过纸带或早期的磁盘设备加载程序。它的字长、寄存器结构、指令格式等设计反映了 70 年代小型机在成本、性能和可维护性之间的平衡。这里的“Mitra”在法语中也可以理解为“合作”或“联接”的含义呼应了它在工业控制中承担多设备协作任务的定位。对国内开发者来说Mitra-15 可能比较陌生。它不像 PDP-11 或者 VAX 那样在计算机历史课程中经常出现相关中文资料也少。但正是这种“陌生感”让它在 SIMH 中的模拟更具价值一台我们平时接触不到的历史机型可以通过模拟器完整地跑起来这本身就是对计算机历史资料的一种数字化存档。1.2 SIMH 是什么SIMH 是一个开源的计算机系统模拟器框架最初由 Bob Supnik 发起设计目标是模拟大量历史上的计算机系统。它最出名的地方在于支持范围非常广从 PDP 系列、VAX、IBM 大型机到各种小型机、微型机都有对应实现。SIMH 提供了一套公共的模拟器控制程序SCP无论是命令行交互、调试断点、内存查看、外设挂载还是程序的加载与执行都有统一的操作方式。用 SIMH 模拟一台机器本质上就是写一个 C 语言程序把目标机器在程序员可见层面的资源全部模拟出来。这包括 CPU 的寄存器集合、内存地址空间、指令解码与执行逻辑、中断异常机制、外部设备控制器等。SIMH 本身只提供框架和公共工具具体机器模型需要开发者自己实现。选择 SIMH 而不是从零写一个独立模拟器最直接的原因是它能帮我们省下“控制台、调试器、设备抽象、命令行接口”这些通用基础设施。我们需要做的是聚焦在 Mitra-15 本身查资料、读手册、编写 CPU 模型、接入设备。1.3 项目现状与意义目前这个项目的状态是Work in Progress也就是开发工作还没有完全结束。从仓库的命名来看开发者正在尝试让 Mitra-15 的 CPU 模型在 SIMH 框架下启动并执行程序。一个模拟器从零到能开机中间要经历硬件手册梳理、寄存器建模、指令解码、内存读写、设备接入、程序加载、联调验证等多个阶段。这项工作对理解“模拟器是如何工作”很有帮助。我们在学校里学计算机组成原理时提到“指令周期”通常都是纸面上的概念。但在模拟器里你需要亲手实现取指、译码、执行、访存、写回这个过程并且要处理设备中断、控制台输入、内存越界等边界情况。这种“把原理变成代码”的过程正是读多少本教材都替代不了的。2. 模拟器是怎么工作的2.1 模拟器的分层结构从整体上看模拟器可以分成两层。上层是“机器模型”负责模拟目标机器的 CPU、内存和外设。CPU 模型要维护 PC程序计数器、通用寄存器、状态寄存器等内存模型负责一个线性地址空间的读写外设模型则通过寄存器映射或端口映射把设备状态暴露给 CPU 程序。下层是“框架层”比如 SIMH 提供的 SCP。它负责解析用户输入、加载程序镜像、执行单步、设置断点、查看寄存器、导入导出内存以及管理模拟时钟和事件调度。框架层不关心你模拟的是 PDP-11 还是 Mitra-15它只提供标准接口让各种机器模型可以统一接入。这样做的好处是不同机器的模拟器可以共享一套调试工具。程序员在习惯了SIMH提示符之后换一台机器模型常用的deposit、examine、break、run等命令风格基本一致学习成本很低。2.2 SIMH 的公共机制SIMH 内部有几个关键机制值得提一下第一个是“事件机制”。外设或者 CPU 的一些异步行为比如磁盘的传输完成、控制台输入的到达都会生成一个事件插入到模拟时间轴中。模拟主循环从事件队列中取出下一个事件推进模拟时钟然后执行对应的处理函数。第二个是“内存和设备的统一访问”。在 SIMH 中内存和设备通常都会被映射到一套统一的访问接口中。CPU 访存时框架会判断地址落在哪个区域然后调用对应区域的读写函数。这样设备寄存器可以直接映射到地址空间里CPU 读写设备寄存器和读写内存的代码路径是一致的。第三个是“控制台设备”。每个模拟器至少要有一个控制台设备用于显示输出和接收输入。在 SIMH 里控制台设备可以连接到终端、文件或者网络端口。对 Mitra-15 这种老系统来说控制台往往是前面板开关和打印机因此在模拟器中可以用终端模式模拟前面板操作用文件模式记录输出。2.3 为什么选择 SIMH 而不是重写一个给 Mitra-15 写模拟器当然也可以从零开始写一个独立的 C 程序只要能完成 CPU 模拟和内存加载即可。但一旦真的进入调试阶段你会发现缺的东西太多了没有调试器程序跑飞了只能靠printf一步步看没有断点定位死循环非常痛苦没有统一的内存加载方式每次运行测试程序都要重新实现一个文件解析器。SIMH 把这些问题都提前解决好了。你只需要把自己的机器模型注册进 SCP所有调试设施直接可用。此外SIMH 是多人长期维护的开源项目代码风格和接口设计相对稳定。作为一个还在进行中的项目接入 SIMH 也能让后来者更容易接手因为代码结构有框架约束不完全是开发者个人风格。2.4 认识 Mitra-15 的资源模型要模拟 Mitra-15第一步不是写代码而是先搞清楚它有哪些资源。我们需要一份尽可能完整的技术手册至少包含以下信息字长和地址空间大小CPU 寄存器列表及其用途指令格式和寻址方式中断向量和异常处理方式控制台 / 外设寄存器的地址映射启动加载bootstrap流程如果手册暂时不齐可以从公开的硬件文档、早期用户在论坛或邮件列表中的讨论记录、以及已存在的模拟器实现中交叉验证。这里要特别提醒不要凭感觉假设指令编码历史上不同厂商的指令格式差异很大比如有些机器操作码在低字节有些在高字节寻址方式也各有各的定义。3. 环境准备与项目结构3.1 操作系统与工具链由于 SIMH 本身是 C 语言项目开发 Mitra-15 模拟器时准备一个常见的 Linux 环境即可Windows 下用 WSL 或者 MinGW 也完全可以。本文示例以常见环境为例重点演示配置思路。需要安装的工具包括GCC 或 Clang 编译器Make 构建工具Git 版本控制工具一个趁手的编辑器或 IDE版本需要根据你的项目实际情况调整SIMH 的构建系统通常也比较传统make就能完成大部分工作。3.2 获取源码SIMH 的源码托管在 GitHub 上。建议在动手前先git clone整个仓库然后阅读仓库根目录下的README.md和makefile了解当前版本支持的机器列表和构建方式。由于仓库迭代比较快本文不写死具体版本号以你 clone 时最新版本为准。对于 Mitra-15 项目通常会放在仓库中一个独立的子目录里例如mitra15或类似命名。目录中应该包含.c源文件实现 CPU 和设备模型.h头文件定义寄存器、结构体和常量makefile片段把该模拟器加入整体构建可能的测试程序或内存镜像文件3.3 项目目录规划一个典型的模拟器实现目录结构可以这样规划simh/ ├── makefile ├── mitra15/ │ ├── mitra15_cpu.c │ ├── mitra15_cpu.h │ ├── mitra15_console.c │ ├── mitra15_console.h │ ├── mitra15_sys.c │ ├── mitra15_sys.h │ ├── mitra15_memory.c │ ├── mitra15_memory.h │ └── mitra15.doc ├── scp/ │ ├── scp.c │ └── ... └── pdp11/ └── ...mitra15_cpu.c负责 CPU 核心逻辑mitra15_memory.c负责内存模型mitra15_console.c负责控制台设备mitra15_sys.c负责把上述模块注册进 SIMH 框架。这种拆分的目的是让每个文件职责单一方便审计和后续替换外设模型。4. 从零起步构建第一版 Mitra-15 模拟器4.1 搭建最小主循环模拟器的核心是一个循环取指、解码、执行。无论在 SIMH 中还是独立实现这个循环都不会变。下面是一个示意性的 C 语言主循环骨架展示整体思路。// 文件路径mitra15/mitra15_cpu.c // 说明这是一个简化示意骨架需按实际 SIMH 接口与目标机手册调整。 int mitra15_execute(int32_t max_cycles) { int32_t executed 0; while (executed max_cycles) { uint16_t opcode; // 1. 取指根据 PC 从内存中读取指令字 if (mitra15_fetch(opcode) ! SCPE_OK) { return SCPE_MEMORY_ERROR; } // 2. 译码并执行 int status mitra15_decode_and_execute(opcode); if (status ! SCPE_OK) { return status; } executed; } return SCPE_OK; }这段代码只表达了最核心的流程。实际项目中取指要考虑地址边界译码要同时更新寄存器状态和程序计数器执行阶段可能还要触发设备访问。每一步都需要结合手册仔细设计。4.2 CPU 寄存器与内存模型在编写 CPU 逻辑时第一步是把寄存器结构体定义出来。Mitra-15 的寄存器集合以真实手册为准这里用示例性代码表达建模思路// 文件路径mitra15/mitra15_cpu.h typedef struct { uint16_t pc; // 程序计数器 uint16_t acc; // 累加器 uint16_t sr; // 状态寄存器 uint16_t *regs; // 通用寄存器组 } MITRA15_CPU; typedef struct { uint8_t *ram; // 内存数组 uint32_t size; // 内存大小 } MITRA15_MEMORY;内存模型通常可以简化成一个字节数组但要注意 Mitra-15 的存储访问单位。如果机器按字访问那么地址和物理字节之间还有一个换算关系。很多小型机的内存保护、地址映射、甚至奇偶校验位都需要在内存访问函数里统一处理。int mitra15_mem_read(uint32_t addr, uint16_t *value) { if (addr mitra15_mem.size) { return SCPE_MEMORY_ERROR; // 地址越界 } *value mitra15_mem.ram[addr]; return SCPE_OK; }这里只是演示位置换算的思路实际项目中请以目标机器的访存粒度为准。4.3 指令解码与执行指令解码是模拟器正确性的关键。不同小型机的指令集差异很大有的采用固定长度单字指令有的采用变长指令有的操作码占 4 位有的占 6 位。下面给出一个结构性的解码示例具体编码方式需要对照 Mitra-15 手册确认int mitra15_decode_and_execute(uint16_t opcode) { uint16_t op (opcode 12) 0xF; // 示例取高 4 位作为操作码 uint16_t operand opcode 0xFFF; // 示例剩余 12 位作为操作数 switch (op) { case 0x0: // HALT 停机 return SCPE_HALT; case 0x1: // ADD 加法 return mitra15_exec_add(operand); case 0x2: // LOAD 加载 return mitra15_exec_load(operand); default: // 未实现指令 return SCPE_UNIMPLEMENTED; } }实际项目中我强烈建议先用一小部分指令把框架跑通再逐步扩充指令集。比如先把LOAD、STORE、ADD、BRANCH、HALT这几个核心指令实现然后编写一个简单的测试程序确认程序能正常执行、停止。之后再逐渐加入寻址方式、内存间接访问、中断处理等复杂特性。不要在第一天就试图把所有指令全部实现那样调试时会非常痛苦。4.4 接入 SIMH 控制台与调试器要在 SIMH 中运行需要把上面这些逻辑注册到 SIMH 的框架里。这通常包括定义设备描述结构体、初始化函数、复位函数、IO 函数和控制台命令。不同版本的 SIMH 接口细节略有差异具体可以参考同一仓库中pdp11或其他模拟器的实现。// 示意代码注册设备到 SIMH 框架 DEVICE mitra15_dev { .name mitra15, .description French CII Mitra-15 CPU, .reset mitra15_reset, .attach mitra15_attach, .detach mitra15_detach, // 其他字段省略 };接入后就可以在SIMH控制台上用命令创建内存、加载程序、设置寄存器、启动执行。这是整个模拟器变成“可用工具”的关键一步因为从此你不再需要在代码里写死测试用例而是可以像操作真实小型机一样进行交互式调试。5. 外设与加载流程5.1 程序加载与启动老式小型机的启动流程通常不是直接执行硬盘上的操作系统而是通过前面板或加载设备把一段引导程序送入内存再由引导程序加载完整的用户程序。在模拟器中我们通常用一个简单的配置脚本把程序装载进内存然后设置 PC 寄存器的初始值再启动执行。下面是一个 SIMH 配置示例思路是通用的; mitra15.sim ; SIMH 配置示例按实际命令调整 set cpu 32k load -a test.bin set pc 0 run其中的load命令会把test.bin的内容装载到指定地址。需要注意的是不同模拟器支持的镜像格式不一样可能是纯二进制、Intel HEX、SREC 或自定义文本格式。如果 Mitra-15 使用纸带格式加载命令可能需要增加格式转换逻辑。5.2 外设建模的优先级对于一台还处于Work in Progress的小型机模拟器外设建模不必一上来就做得很全。优先建议顺序是控制台设备用于基本输入输出内存装载工具用于运行测试程序指令集完善先把 CPU 跑正确再考虑纸带/磁盘等大容量存储设备控制台设备看起来简单但它承担了与用户交互的职责。很多老系统通过控制台打印运行状态、错误码和提示信息没有控制台几乎没法调试验证。磁盘和纸带则可以等 CPU 稳定之后再做因为它们涉及文件格式解析和中断时序复杂度较高。5.3 运行与验证完成配置后启动 SIMH 并执行配置脚本simh-mitra15 mitra15.sim如果一切正常你会看到SIMH提示符然后程序开始运行控制台输出相关信息。如果程序没有按预期工作不要急着修改实现先回到调试工具层面单步执行、查看寄存器和内存内容定位问题发生在哪一条指令。6. 调试技巧与验证方法6.1 与原始手册对照模拟器最常见的错误来自于“自以为理解手册”。指令的字节序、操作码位域、条件码更新规则、寻址方式的边界情况任何一处理解偏差都会导致程序行为异常。我的建议是把手册中关于每条指令的表格做成一个核对列表每实现一条指令就逐项检查指令字长是否正确操作数来源和目的地程序计数器如何变化状态寄存器如何更新是否有异常/陷阱触发这种对照工作虽然枯燥但能大幅减少后续调试成本。6.2 使用 SIMH 自带的调试命令SIMH 提供了一系列调试命令。常见的包括help ; 查看帮助 examine 100 ; 查看地址 100 的内容 deposit 100 0 ; 向地址 100 写入 0 break 200 ; 在地址 200 设置断点 step 10 ; 单步执行 10 条指令 run ; 运行用断点配合寄存器查看可以快速定位到某条指令执行时状态是否异常。比如程序在循环里跳不出去直接在循环入口打断点然后观察循环变量变化就能判断是条件判断错误还是跳转地址错误。6.3 单元级测试用已知程序验证编写大量指令级测试是让模拟器走向可靠的重要一步。可以从最简单的指令开始每个测试只验证一个功能点// 测试用例ADD 指令。 // 预期acc 从 0x10 变为 0x18。 deposit acc 0x10 deposit 100 0x1008 set pc 100 step 1 examine acc这里用0x1008作为示例指令编码实际编码需按照真实指令集。测试文件可以用脚本批量执行也可以用 SIMH 的do命令加载测试脚本。每修复一个 Bug就把对应场景加入回归测试防止以后改动又把它弄坏。6.4 性能与稳定性模拟老机器的性能压力通常不大。但要注意模拟器主循环中是否有不必要的内存读写、是否频繁进行地址合法性检查、设备事件是否频繁触发。SIMH 的事件调度是有精度的事件过多会影响运行速度但历史机器的运行频率本来就不高所以一般不用担心性能。稳定性反而更值得关注。模拟器崩溃、内存越界、无限循环空转都可能是 CPU 模型对非法指令处理不当造成的。合理的做法是遇到未知指令时返回错误码并停止执行而不是继续进入未知状态。7. 常见问题与排查清单问题现象常见原因解决思路启动后控制台无任何输出程序没有加载成功或 PC 初始值不对检查加载命令和 PC 地址单步执行看取指是否正确程序总是跑飞PC 跳到奇怪地址指令解码或分支目标计算有误对比手册核对指令编码检查立即数符号扩展内存读写结果不符合预期访存单位、地址换算或字节序错统一地址换算函数打印每次读写地址与数据设备中断无法触发事件队列或中断应答逻辑未实现在设备寄存器写入处加日志检查中断向量设置构建报错找不到某个函数SIMH 版本 API 有变化对比仓库内其他模拟器的实现调整接口调用加载镜像后内容错误镜像格式解析不对确认镜像文件格式参考同仓库其他加载器实现如果遇到上面没有覆盖的报错建议按下面的顺序排查先确认问题发生在 CPU 取指、解码、执行、访存还是设备模块。用单步执行观察前几十条指令是否和手册预期一致。在关键函数入口打日志输出 PC、操作码、操作数和相关寄存器。对照真实测试程序逐步缩小出错范围。每修复一个问题记录到测试用例中避免回归。8. 开源协作与工程建议8.1 Work in Progress 项目的协作方式如果你准备参与这个 Mitra-15 模拟项目首先要接受它当前不是完整可用的状态。这种孵化阶段的项目最重要的不是“功能全面”而是“基础正确、可推进”。在提交代码前建议先了解项目维护者的分支策略和提交规范尽量从简单任务入手比如补充文档、修正某个指令的实现、添加一个测试用例。不要一上来就重写整个 CPU 模型先建立沟通确认当前最需要解决哪些问题再动手。8.2 测试驱动改造模拟器项目非常适合测试驱动开发。每个指令、每个外设行为都可以写成可重复执行的测试用例。推荐的做法是把测试用例按指令类型分类每个测试用例写成独立的 SIMH 脚本用一个构建命令批量执行所有测试任何一次代码修改后先跑一遍完整测试集这样做的好处很明显当有人不小心改坏了一条指令时测试会在几分钟内给你响亮反馈而不是等程序运行到后期才暴露出问题。8.3 文档与提交规范历史模拟器的代码长期可读性很重要。潜在维护者可能对目标机器不熟悉代码中的注释应该做到“解释为什么而不是只解释是什么”。例如// 这里不是普通的加法Mitra-15 的 ADD 指令在进位标志为 1 时 // 还会多执行一次加 1参考手册第 34 页说明。提交信息也尽量写清楚改动对象和意图比如mitra15: fix carry flag handling in ADD instruction这样回溯历史时能快速定位修复点。8.4 后续路线更多外设、更多指令、完整系统镜像当一个最小可运行版本稳定后后续扩展方向非常多补齐未实现的指令和寻址方式实现纸带读取运行真实的历史程序实现磁盘控制器挂载磁盘镜像完善控制台 UI模拟前面板开关和指示灯编写更详细的架构文档降低后人维护门槛每完成一个里程碑都可以在 README 中更新状态。如果能让一台真实的 Mitra-15 机器运行起来并加载原始系统镜像那这个模拟器就从“教学玩具”升级成了“历史系统存档工具”。9. 一些值得记录的开发心得模拟器开发的真正难点通常不在“把代码写出来”而在于“把行为还原正确”。对 Mitra-15 这种资料稀缺的老机器这种还原工作更需要耐心和交叉验证。我个人的建议是先不要追求把所有功能做完而是把“最小可运行系统”跑通。只要 CPU 能执行一小段正确的程序控制台能输出结果框架搭建就基本完成了。之后每增加一条指令、一个设备都是在为这台虚拟机器逐步补齐生命力。如果你对小型机模拟、计算机体系结构或者开源复古计算项目感兴趣不妨克隆一份 SIMH 源码先读一读已有的 PDP-11 模拟器代码熟悉框架风格再回头对照 Mitra-15 的手册试着实现一条简单的指令。这个过程可能很慢但每一次成功执行你都会对“计算机是如何工作的”这件事有更深的体会。日后谈到老系统或许你也能用一句“我让一台几十年前的小型机在模拟器上重新跑了起来”来做总结。这本身就是一段很酷的编程经历。