
如果你最近在关注复古计算retrocomputing社区可能会注意到一个现象大部分被“复活”的历史机器要么是 DEC 的 PDP-8、PDP-11要么是 IBM 的老系统或者 Data General 的 Nova资料多、镜像全、社区热闹。但真正让我觉得有价值的往往是那些冷门到只剩法文手册和几张照片的机器。比如这次要聊的主题法国 CII 公司的 Mitra-15 出现在 SIMH 模拟器项目中而且状态被明确标成 Work in Progress。这里先说一个我的判断Mitra-15 的指令集复杂度、CPU 频率、内存规模放在今天的技术水准下都不算难真正难的不是“让一条指令跑起来”而是让 CPU、内存、中断、时钟、外设和操作系统形成一个整体达到“可以接受的历史行为一致性”。这也是为什么很多模拟器项目常年停留在 WIP 状态而不是一两个周末就能交付。如果你只看表面很容易误以为模拟器开发就是把 ISA 文档翻译成 C 代码实际上远不是这么简单。这篇文章不会假装我们已经有了一个完整的 Mitra-15 仿真方案。我会从公开资料出发把 Mitra-15 的历史背景、SIMH 的架构原理、以及“在 SIMH 中新增一台机器”的工程流程完整梳理一遍。读完你可以了解这台法国小型机在计算工业史上处于什么位置SIMH 到底是怎么工作的如果你想参与推进一个 SimH 模拟器项目应该从哪里入手中间会遇到哪些坑。这个话题适合对计算机体系结构、模拟器开发、历史系统维护感兴趣的人也适合正在做“老系统移植”的工程团队参考。1. 为什么要在 SIMH 中模拟 Mitra-15很多人第一次看到“French CIIs Mitra-15 in SIMH”这个标题时都是同一个反应CII 是什么Mitra-15 又是什么这恰恰说明一个问题——我们现在的计算机史叙事绝大部分是以美国公司为主线的DEC、IBM、Intel 占据了太多篇幅。而法国从 1960 年代开始曾经试图建立一条独立于美国厂商的计算机工业体系Mitra-15 就是这条脉络里的一个具体产物。从技术价值角度看模拟一台历史机器本质上是在做“知识保存”。硬件会老化机器会停摆但仿真代码可以长期运行在现代操作系统上。今天的人想研究 Mitra-15 的操作系统、汇编语言、工业控制接口不需要真的找到一台还能开机的古董机器只需要一个足够精确的模拟器。这就是 SIMH 这类项目存在的核心意义。从工程角度看Mitra-15 的模拟器又是一个很好的教学案例。它的规模不大不小比 8 位微处理器复杂比现代 x86 简单得多有完整的小型机特征——16 位字长、累加器、变址寄存器、内存映射、中断和外设接口。用这样一台机器去理解“一台计算机是怎么从通电到加载操作系统的”比直接面对现代处理器要清晰得多。另一个容易被忽略的点是法国 CII 的机器在很多技术细节上与美国的 DEC/IBM 路线不同它有自己的指令风格、自己的外设接口规范、自己的操作系统设计思路。模拟 Mitra-15 不只是在“复制硬件”同时也是在保留一种不同的工程设计文化。从公开资料看法国当时走的是“国家主导、支持本国厂商、覆盖大型机到小型机”的路线Mitra-15 是这个路线里面向实时控制和工业现场的代表。所以这个 WIP 项目的价值不能只看 README 里写了多少代码而要看到它背后代表的那个“被遗忘的计算世界”。它值得被模拟不是因为它的性能有多强而是因为它承载了历史信息。2. Mitra-15 是什么从法国“计算计划”走出的工业小型机Mitra-15 是 CIICompagnie internationale pour linformatique国际信息处理公司在 1970 年代初期推出的一款 16 位小型计算机。要理解 Mitra-15不能只把它看成一台普通小型机它的背景非常特殊。1966 年法国政府启动“计算计划”Plan Calcul目标是扶植本国的计算机工业减少对美国厂商的依赖。CII 就是在这个背景下成立的它被赋予了生产大型商用机和科学计算机的任务。后期 CII 也进入小型机领域Mitra 系列就是面向实时控制和数据处理的小型机产品线Mitra-15 是其中很有代表性的一档。从定位上看Mitra-15 属于“实时控制小型机”。它被大量用在工业过程控制、实验室数据采集、教学实验、以及某些军事与航天配套场景中。这个定位决定了它的设计思路不需要非常高的主频但需要可靠的 I/O 处理能力、中断响应能力和模块化扩展能力。这类机器往往更看重“能不能稳定响应外部事件”而不是“每秒钟能算多少次”。从技术特征看Mitra-15 的公开资料显示它是一款 16 位字长的机器采用中小规模 TTL 逻辑电路实现。它的指令系统不是微处理器式的一长串复杂指令而是典型的小型机设计有基本的算术逻辑指令、访存指令、跳转指令、以及用于输入输出的指令。寄存器结构里包含程序计数器、累加器、以及若干用于地址计算和循环控制的变址寄存器。内存容量在当时的工业小型机中处于主流水平可以通过扩展模块增加容量。考虑到资料大多数是法文能拿到手并准确翻译成工程模型本身就是一件很耗时的工作。下面把 Mitra-15 和当时常见的美国小型机做一个粗略对比注意这里的对比是“定位层面”的不是精确的跑分对比项目Mitra-15PDP-8PDP-11Data General Nova厂商法国 CIIDECDECData General字长16 位12 位16 位16 位主要定位实时工业控制实验室/教育通用小型机通用小型机操作方式面板/控制台面板/纸带面板/终端面板/纸带文化背景法国计算计划美国小型机文化美国小型机文化美国小型机文化这个对比能说明一个重要问题Mitra-15 不是低配版 PDP-11它是一台“按照法国工程师对实时系统的理解”设计出来的机器。它的外设接口、总线结构、中断机制都带有 CII 自己的风格。因此模拟 Mitra-15 不能简单套用模拟 PDP-11 的方法必须回到它的原始手册里去。从历史命运看CII 后期经历了与 Honeywell 合并等一系列变动最终演变为 Honeywell Bull、Bull再后来归属于 Atos 旗下。Mitra-15 这个型号从推出到退出市场的时间窗口并不长但它留下了一批真实可考的程序和文档。这些材料正是模拟器开发者最需要的“真源”。3. SIMH 的核心原理模拟器、虚拟机和历史系统的“第二人生”SIMH 是 Bob Supnik 发起的历史计算机模拟器框架。最初的动机是保存和运行 DEC 老系统的软件后来覆盖范围扩展到了 DEC、Data General、IBM、HP、MIT、Xerox 等大量历史计算机。它用 C 语言编写强调可移植性可以在 Windows、Linux、macOS 等多种平台上构建。现在的 Open SIMH 项目在 GitHub 上持续维护活跃度不错。解释 SIMH必须先解释一个容易混淆的概念模拟器emulator和虚拟机virtual machine不是一回事。虚拟机通常运行在同一个 CPU 架构上通过虚拟化硬件接口让客户端操作系统直接执行大部分指令性能损耗很小而历史计算机模拟器通常是在完全不同的 CPU 架构上一条一条地解释执行目标机器的指令。模拟 PDP-11 时你是在用 x86 或 ARM 的指令去“假装”自己是 PDP-11 的 CPU。指令不是硬件直接执行的而是靠软件翻译后执行。SIMH 采用的核心执行模式是“解释执行”。它的主循环可以简单概括为取指fetch—— 从模拟内存中读取目标机器的指令译码decode—— 根据目标机器的指令编码规则判断这条指令做什么执行execute—— 用 C 语言模拟出这条指令对寄存器、内存、标志位的影响更新 PC —— 进入下一条指令。这个过程在单个模拟周期内可能只需要几十行 C 代码但要让整套系统稳定运行需要多个基础设施组件的配合。SIMH 提供的基础设施包括sim_console控制台终端界面用于显示模拟机器的输出接收键盘输入。sim_clock模拟时钟机制用来调度外设事件、定时中断和实时行为。sim_activate/sim_cancel设备调度的核心函数可以安排在某个模拟时间点激活设备。设备控制系统Unit 系统描述磁盘、纸带机、终端等外设的注册、挂载、读写方法。命令行处理器负责解析启动配置、执行run、break、deposit等命令。打个比方SIMH 像是提供了一个“带水电管网的毛坯房”。你的任务是把 Mitra-15 这台“机器”搬进这个毛坯房接上电、接通水管、装上房间板然后让系统正常运转。毛坯房解决的是基础设施问题但房间怎么隔、线路怎么走完全取决于目标机器的真实结构。理解这个框架你就知道为什么“在 SIMH 中新增一台机器”不是从零写一个模拟器而更像“按照 SIMH 的接口规范实现一台目标机器的外设与 CPU 模型”。工作量大但不必重复造轮子。4. Mitra-15 模拟项目要啃的硬骨头标题里写了 Work in Progress说明这个项目还不是最终可用的状态。在复古计算圈子里“WIP”是一种诚实但常被误解的状态它意味着开发者的确已经把框架搭建到了一定程度但离“完整运行操作系统”还有距离。从工程经验看一个 WIP 模拟器项目通常会在以下几个层面卡住。第一硬件资料不完整。Mitra-15 是法国 CII 的产品很多技术手册是法文的而且散落在图书馆、档案馆和私人收藏者手里。要做一个高保真模拟器你需要拿到至少三类文档CPU 指令集手册每个操作码的编码格式、标志位影响、内存映射和外设寄存器手册每个 I/O 地址对应什么功能、以及外设接口手册纸带机、磁盘、终端等如何连接和驱动。没有这些文档模拟器只能靠猜测而猜测出来的运行结果不具可信度。第二引导流程还原困难。一台真实小型机的通电启动过程不是简单地把内存清零然后运行。它通常需要操作员在面板上手动输入引导程序bootstrap或者从一个专门的只读引导介质读取。Mitra-15 的引导过程依赖哪些开关、哪些面板操作、哪些启动地址这些细节在一般宣传材料里根本找不到只能从操作手册或操作员回忆里还原。第三I/O 系统的精确时序。CPU 指令执行顺序错了程序马上就会暴露出来但 I/O 时序不对系统可能“看起来正常”运行一段时间后才出现随机错误。工业控制型小型机尤其看重中断响应和外设交互如果模拟器里的纸带机读取速度比真实设备快十倍操作系统可能会误判超时。SIMH 的调度机制提供了基础的时序支持但每个外设的时间参数必须从真实硬件资料里推算。第四可用软件镜像短缺。模拟器的最终目标是跑真实软件而真实软件镜像的获取往往比模拟器本身还难。纸带过了几十年可靠性很低磁盘映像是私有的格式可能需要专门工具才能提取。如果找不到干净的 Mitra-15 引导程序和操作系统镜像即使模拟器逻辑完全正确也没有东西可以跑。如果只看表面很多人会以为“WIP”意味着模拟器不可用。更稳妥的判断是WIP 状态代表“CPU / 基本内存 / 控制台已经迈出了第一步但完整系统验证还没有完成”。这不代表项目失败而是模拟器开发的常态。真正成功的模拟器项目往往不是某个天才一夜之间写出来的而是多个人围绕资料、测试和调试持续迭代的结果。5. 在 SIMH 上新增一个系统的基础流程抛开 Mitra-15 的具体差异在 SIMH 框架中新增一台机器工程流程是有一定通用性的。下面这些步骤既适用于 Mitra-15也适用于任何其他历史小型机。我们在这里用通用流程来拆解具体的 Mitra-15 细节需要在真实手册的指导下继续填充。5.1 环境准备与前置条件要参与 SIMH 类项目首先要准备一个可以构建 C 代码的现代环境。操作系统方面Windows、Linux、macOS 都可以Linux 会更顺手一些尤其是做自动化测试的时候。工具链需要保证 C 编译器和 make 可用几个常用库如 curses也要装好因为控制台界面依赖它。版本方面的建议是不要刻意追求某个固定版本以项目当前实际维护的分支为准。模拟器项目最怕的是“用旧文档配新代码”尽量以官方仓库 README 描述为准。环境准备可以执行以下命令以 Ubuntu/Debian 系为例sudo apt update sudo apt install build-essential git libncurses5-dev git clone https://github.com/open-simh/simh.git cd simh不要小看这一步。很多新手在模拟器项目上卡住不是不会写模拟代码而是工具链不匹配缺少 curses 库导致编译失败或者 make 版本太旧导致构建选项不生效。先把构建环境弄干净后面才能专心解决目标机器的问题。5.2 创建目标机器模块SIMH 的源码目录里每个被模拟的机器有自己的一组源文件。通常来说一台机器需要至少一个.c文件描述 CPU 和设备以及若干头文件定义常量、结构体和外部接口。新增机器的第一步是在合适的位置创建一个新的机器目录例如mitra15/然后在里面放mitra15_cpu.cCPU 状态、指令执行逻辑。mitra15_sys.c控制台、内存映射、外围设备接线。mitra15_cfg.c设备注册和配置项解析。mitra15_defs.h寄存器定义、内存大小、设备数量等常量。这个划分不是必须的但推荐保持“CPU 逻辑 / 系统集成 / 配置解析”分离。模拟器会越来越复杂一开始就按模块拆分后面调试会轻松很多。5.3 定义 CPU 状态与内存模拟器的 CPU 状态本质上就是一堆变量程序计数器、累加器、变址寄存器、标志位、内存区域以及一些用于调试和统计的字段。然后把对真实硬件的读写映射到这个状态上。Mitra-15 的内存大小、寄存器数量必须依据真实手册确定。在不确定的情况下宁可先按一个保守的小内存模型来实现再把内存上限提上去。因为 CPU 逻辑一旦写错内存再大也没用。5.4 实现取指-译码-执行主循环这是模拟器的心脏。每个模拟周期的工作是从 PC 指向的内存地址读取指令字解析操作码和操作数然后执行。执行过程要处理内存读写、标志位更新、地址计算、跳转和 I/O 请求。在 SIMH 中这个循环还必须考虑设备事件比如某个外设要求定时器触发或者有数据要进入 CPU。因此主循环里需要同时考虑“CPU 空闲”和“设备就绪”的调度问题。设计得不好的话外设事件可能会被 CPU 的密集循环饿死。5.5 接入控制台与外设一个只能执行 CPU 指令、没有输入输出的模拟器很难用来跑真实操作系统。最简单的接入方式是控制台终端把模拟器接收到键盘输入送给目标机器的串口/终端设备同时把目标机器的字符输出显示到本机终端。这就是 SIMH 控制台组件的功能。再往后就是纸带机、磁盘、打印机等外设。接入顺序建议从简单到复杂先控制台再纸带再磁盘。越复杂的外设越容易在时序上踩坑。5.6 编译链接与启动配置当代码写到一定程度就可以尝试用 make 构建。构建目标必须包含新机器的可执行文件。然后创建一个 SIMH 启动脚本内容大致是sim set cpu modelmitra15 sim attach console console.log sim load bootstrap.tape sim run启动脚本的价值在于它把“模拟器的配置参数”和“目标机器要加载的软件介质”固化下来方便反复测试。后面所有回归测试都可以从这里开始。6. 代码骨架从 CPU 状态到模拟循环为了让前面讲的流程更具体下面给出一个简化的 C 代码骨架。这段代码不是某一台真实 Mitra-15 模拟器的完整源码而是说明“在 SIMH 框架下写一个新的 CPU 模型大概长什么样”。细节必须用真实手册填充。6.1 CPU 状态定义先定义一个结构体用来保存 CPU 的所有状态/* 文件路径mitra15/mitra15_defs.h */ #ifndef MITRA15_DEFS_H #define MITRA15_DEFS_H #define MITRA15_MEM_WORDS 32768 /* 示意值以真实内存配置为准 */ struct mitra15_cpu { uint16_t pc; /* 程序计数器 */ uint16_t acc; /* 累加器 */ uint16_t ix[4]; /* 变址寄存器组具体数量以手册为准 */ uint16_t sr; /* 状态寄存器 / 标志位 */ uint16_t mem[MITRA15_MEM_WORDS]; /* 内存数组 */ int trace; /* 调试是否打印指令迹 */ uint64_t cycles; /* 已执行的周期数 */ }; extern struct mitra15_cpu mitra15_cpu_state; #endif这里的重点不是具体变量名而是思路把目标机器的可见状态用 C 的结构体显式表达出来。模拟器本质上就是一个状态机所有寄存器、内存、标志位都是这个状态机的“当前状态”。调试时能随时打印和修改状态是模拟器的巨大优势。6.2 取指-译码-执行主循环下面用一个伪代码风格的 C 函数展示主循环的主体/* 文件路径mitra15/mitra15_cpu.c */ /* 示意代码opcode 定义必须来自真实 ISA 手册 */ enum { OP_ADD 0x00, /* 示意不代表真实编码 */ OP_SUB 0x01, OP_LDA 0x02, OP_STA 0x03, OP_JMP 0x04, OP_IO 0xff /* ... 以真实指令集为准 */ }; static int execute_instr(void) { uint16_t opcode; uint16_t operand; uint16_t addr; struct mitra15_cpu *cpu mitra15_cpu_state; cpu-cycles; /* 1. 取指 */ opcode cpu-mem[cpu-pc]; cpu-pc; /* 2. 译码简化假设一个内存字包含操作码和操作数 */ operand opcode 0x00ff; opcode (opcode 8) 0xff; switch (opcode) { case OP_ADD: addr cpu-ix[operand 0x03] cpu-mem[cpu-pc]; cpu-pc; cpu-acc cpu-mem[addr]; break; case OP_LDA: addr cpu-ix[operand 0x03] cpu-mem[cpu-pc]; cpu-pc; cpu-acc cpu-mem[addr]; break; case OP_STA: addr cpu-ix[operand 0x03] cpu-mem[cpu-pc]; cpu-pc; cpu-mem[addr] cpu-acc; break; case OP_JMP: cpu-pc (cpu-mem[cpu-pc]) 0x7fff; break; case OP_IO: /* 外设访问通常触发 SIMH 设备调度 */ break; default: /* 不建议直接 abort应该进入调试循环 */ return -1; } return 0; } void mitra15_run(void) { while (running) { if (execute_instr() ! 0) { /* 进入调试模式等待操作员输入命令 */ break; } /* 周期性地检查控制台 / 设备事件 */ sim_process_devices(); } }这里要特别解释几点。第一取指后必须立即更新 PC因为目标机器可能没有现代的流水线保护。第二标志位进位、零、负、溢出的更新规则必须严格按手册来这是最容易出错又最隐蔽的地方。第三I/O 指令不是简单地“读写一个寄存器”它往往需要唤醒一个模拟外设把请求交给设备进行处理。上面这段代码设计成“示意”是因为真实 Mitra-15 的编码格式和寄存器布局必须查手册。如果你照着编写代码请务必先拿到真实文档。6.3 SIMH 启动配置脚本写好了 CPU 循环还需要一个配置脚本告诉 SIMH 如何组织和启动这台机器。文件可以是文本形式; mitra15_simh.ini ; 启动脚本示例 set cpu enable set cpu idleyes set console telnet0 attach console console.out load mitra15_test.tape run其中load表示把一段程序镜像加载到内存中run表示开始执行。console.out是控制台输出日志文件。telnet0表示控制台使用本地终端模式而不是监听 telnet 端口。这里需要注意set cpu idleyes这种选项不是每个机器都有具体以目标模拟器的实现为准。配置脚本的语法虽然由 SIMH 解析但可用的配置项完全取决于模拟器作者在代码里实现了多少。6.4 编译与命令行验证构建新模拟器的命令通常是这样的cd simh make mitra15如果编译通过会生成一个mitra15可执行文件。运行方式./mitra15 mitra15_simh.ini然后你会在终端看到模拟控制台输出。如果一切正常可以看到目标机器的引导提示符。如果直接卡死或无输出优先检查加载的镜像是正确格式以及 PC 起始地址是否设置正确。从启动脚本到可执行文件这条链路是模拟器开发者每天都要走无数遍的路径。把编译和启动都写进脚本里会省很多时间。7. 如何验证模拟器真的“对”了模拟器开发的难点不是写出第一条能跑的指令而是证明它“无限接近真实硬件”。如果一个模拟器能把加载进内存的程序执行结果和真实硬件几乎完全一致那才有资格被称为仿真器。验证可以从以下五个层次推进。第一指令级验证。每个 CPU 指令都要单独测试给定一组寄存器和内存初始状态执行该指令检查寄存器、内存和标志位的最终结果是否与手册一致。这一步可以通过编写测试程序实现。手工测试很容易遗漏边缘情况所以推荐把指令测试做成自动化的回归测试程序。第二自举和引导流程验证。如果有一份真实的 Mitra-15 引导纸带或镜像可以做“从启动地址运行观察是否有预期输出”的测试。这不只是验证 CPU也是在验证内存初始状态、控制台输出设备、以及地址线宽度是否正确。第三操作系统级验证。当 CPU、内存、控制台、磁盘/纸带设备都接通后可以尝试加载 Mitra-15 的操作系统镜像观察系统是否能够初始化、显示提示符、执行命令。操作系统加载过程中对设备、中断、时钟的要求比普通测试程序苛刻得多。能跑起 OS意味着模拟器已经从“玩具”迈向了“实用”。第四时序验证。SIMH 可以记录模拟周期数。比较真实硬件上某个基准程序需要的周期数和模拟器报告的周期数可以发现外设调度或空闲等待逻辑是否异常。当然真实周期数未必容易获得但至少可以做到“模拟结果在合理范围内”。第五交叉验证与回归测试。模拟器项目要长期维护必须有自动化回归测试。每次修改 CPU 逻辑、外设模型或调度机制后运行同一组测试镜像对比新输出的哈希值与历史输出是否一致。这一步能让后续改动更安全。没有回归测试的模拟器项目越改越容易崩溃。8. 常见问题与排查方法模拟器开发中遇到的错误往往不是“编译失败”这么简单更多是“运行结果和期望不符”的玄学问题。下面按我的经验列出几个典型问题。问题现象可能原因排查方式解决方案程序执行到一半跑飞指令译码错误打开指令迹观察跳转地址是否异常对照手册逐个检查 opcode 和寻址方式控制台输出乱码字节序/字符编码处理不一致检查内存读写字节序、串口输出实现统一按目标机器的字节序处理标志位结果不对状态寄存器未按规则更新单条指令测试并对照手册真值表单独编写标志位测试用例外设无响应设备事件未被调度检查 sim_activate 调用和设备回调确认外设请求正确注册到调度队列程序运行速度异常快未模拟 CPU 空闲等待或外设延迟对比真实机器周期计数在无任务期间插入 idle 等待逻辑内存地址越界地址解析未做边界检查使用调试断言检查访存范围在内存读写函数中增加边界检查加载镜像后入口不正确引导地址设置错误确认镜像加载地址和 CPU 初始 PC在配置脚本中显式设置初始入口这里最想强调的排查工具是“指令迹”trace。在 CPU 的取指循环里记录 PC、操作码、操作数、寄存器变化当程序跑飞时翻到最后几十条指令就能看出问题在哪。现代调试器很多但历史模拟器最可靠的调试手段往往是这个朴素到极点的寄存器打印。另外如果程序“偶尔”跑飞而不是每次固定跑飞很可能是时序问题而不是指令逻辑问题。先去检查外设调度再用“同一输入跑多次”的方式判断复现性。9. 工程建议与后续方向最后这部分我想给愿意投入这类项目的读者一些工程建议。因为 Mitra-15 模拟器项目现在还是 WIP后续参与和维护的空间很大方法比代码更重要。第一从最小可运行系统开始。不要一上来就写完整指令集。先把“取指、执行一条最简单的算术指令、通过控制台打印一个字符”跑通。这个最小闭环一旦建立后续所有任务都有了测试载体。第二给每一个里程碑保留快照。模拟器改动频繁用 Git 打 tag用固定镜像做回归能避免“改好磁盘又弄坏内存”的尴尬。第三把资料归档成文档。你找到的每一份法文 PDF、每一个镜像是怎么提取的、哪一个寄存器定义来自哪一页统统写进项目的docs/目录。对后来者来说文档和代码一样重要。第四尽量自动化测试。至少准备一个make test目标把指令集测试、引导测试、OS 启动测试串起来。没自动化测试的模拟器只能靠肉眼观察迟早出问题。从技术发展角度看Mitra-15 这样的小型机在模拟器中的意义不只是“能开机”。它还能被当作学习实时系统设计的沙盒你在现代操作系统上看不到的中断嵌套、外设时序、引导加载过程在这台机器上都可以亲手摸一遍。如果未来这个 WIP 项目能把磁盘控制器和操作系统都跑起来它的教学和保存价值会远超一个个人玩具。如果你也想试试建议先在这个项目的社区或开源代码托管平台里看看目前的源码和 TODO 列表找一个小问题开始修复一个指令标志位、补一组指令测试、或者整理一份操作手册翻译。小型机的模拟器最缺的不是天才代码而是持续的、耐心的工程打磨。