如果你最近在关注复古计算(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 CII's 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 是 CII(Compagnie internationale pour l'informatique,国际信息处理公司)在 1970 年代初期推出的一款 16 位小型计算机。要理解 Mitra-15,不能只把它看成一台普通小型机,它的背景非常特殊。
1966 年,法国政府启动“计算计划”(Plan Calcul),目标是扶植本国的计算机工业,减少对美国厂商的依赖。CII 就是在这个背景下成立的,它被赋予了生产大型商用机和科学计算机的任务。后期 CII 也进入小型机领域,Mitra 系列就是面向实时控制和数据处理的小型机产品线,Mitra-15 是其中很有代表性的一档。
从定位上看,Mitra-15 属于“实时控制小型机”。它被大量用在工业过程控制、实验室数据采集、教学实验、以及某些军事与航天配套场景中。这个定位决定了它的设计思路:不需要非常高的主频,但需要可靠的 I/O 处理能力、中断响应能力和模块化扩展能力。这类机器往往更看重“能不能稳定响应外部事件”,而不是“每秒钟能算多少次”。
从技术特征看,Mitra-15 的公开资料显示,它是一款 16 位字长的机器,采用中小规模 TTL 逻辑电路实现。它的指令系统不是微处理器式的一长串复杂指令,而是典型的小型机设计:有基本的算术逻辑指令、访存指令、跳转指令、以及用于输入输出的指令。寄存器结构里包含程序计数器、累加器、以及若干用于地址计算和循环控制的变址寄存器。内存容量在当时的工业小型机中处于主流水平,可以通过扩展模块增加容量。考虑到资料大多数是法文,能拿到手并准确翻译成工程模型,本身就是一件很耗时的工作。
下面把 Mitra-15 和当时常见的美国小型机做一个粗略对比,注意这里的对比是“定位层面”的,不是精确的跑分对比:
| 项目 | Mitra-15 | PDP-8 | PDP-11 | Data General Nova |
|---|---|---|---|---|
| 厂商 | 法国 CII | DEC | DEC | Data 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.c:CPU 状态、指令执行逻辑。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 model=mitra15 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 idle=yes set console telnet=0 attach console console.out load mitra15_test.tape run其中load表示把一段程序镜像加载到内存中,run表示开始执行。console.out是控制台输出日志文件。telnet=0表示控制台使用本地终端模式,而不是监听 telnet 端口。
这里需要注意:set cpu idle=yes这种选项不是每个机器都有,具体以目标模拟器的实现为准。配置脚本的语法虽然由 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 列表,找一个小问题开始:修复一个指令标志位、补一组指令测试、或者整理一份操作手册翻译。小型机的模拟器最缺的不是天才代码,而是持续的、耐心的工程打磨。