S-100 总线(IEEE-696)诞生于 1974 年,最初作为 MITS Altair 8800 的内部扩展方案,因其开放的 100 针被动背板架构迅速成为 1970-80 年代微型计算机的行业标准。Cromemco、IMSAI、CompuPro、Morrow 等厂商在此总线上构建了从单用户 CP/M 工作站到多用户银行交易系统的完整生态。构建一个浏览器端的 S-100 虚拟工作台,本质上是在 Web 环境中重建一段以总线为中心的硬件协作体系 —— 这不是简单的单芯片模拟,而是需要模拟多个独立插槽之间的仲裁、数据流向与中断传播。以下从架构分层、总线信号实现、CP/M 环境配置与调试工作流四个维度,展开工程层面的参数化讨论。
一、仿真架构分层模型
在设计浏览器端 S-100 仿真时,推荐采用四层分层模型,自底向上依次为:物理信号层、总线仲裁层、设备插槽层和应用交互层。
物理信号层负责维护 100 针总线的状态向量。在 JavaScript/WebAssembly 环境中,建议使用 SharedArrayBuffer 实现跨线程的状态共享 —— 主线程运行 UI 与调试器,后台线程执行 CPU 模拟与总线仲裁。状态向量应包含 16 条地址线(A0-A15)、8 条输出数据线(DO0-DO7)、8 条输入数据线(DI0-DI7)以及 40 余条控制信号线。每条信号线建模为布尔状态,并记录其上升 / 下降时间戳,用于后续的边沿检测与总线仲裁决策。内存模型建议采用分页机制:将 64KB 地址空间划分为 256 个 256 字节页面,每个页面可独立映射到任意插槽的内存区域,这样可以实现内存分页板卡(如 CP/M 下的 PAGE ZERO)的高效模拟。
总线仲裁层是整个系统的核心。在真实的 S-100 系统中,多个插槽可能同时尝试控制地址或数据总线(如 DMA 传输期间),因此需要实现总线授予(Bus Grant)与总线占用(Bus Request)机制。建议采用令牌环仲裁算法:每个插槽分配一个 0-15 的优先级编号,当前持有令牌的插槽在完成当前总线周期后,将令牌传递给下一个更高优先级的插槽。在仿真中,这个过程以 100ns 为单位步进 —— 对于 8MHz 的 IEEE-696 总线而言,单个总线周期约为 125ns,因此 100ns 的仲裁步进可以在保证信号完整性的前提下,实现足够细腻的时序模拟。
设备插槽层模拟实际的插件卡功能。最基础的插槽配置应包括:CPU 插槽(运行 Intel 8080 或 Zilog Z80 模拟器)、内存插槽(提供 32KB-64KB 的 DRAM 模拟)、串行 I/O 插槽(连接终端仿真)以及磁盘控制器插槽(加载 CP/M 格式的磁盘镜像)。每个插槽应实现标准的插槽接口,包括初始化(init)、读周期(read)、写周期(write)与中断响应(interrupt)四个回调函数。这种接口设计允许热插拔虚拟插槽 —— 用户可以在运行时动态添加或移除设备,模拟真实的系统配置变更。
二、关键总线信号的实现参数
S-100 总线的 100 针信号中,有 20 余条为核心控制信号,需要在仿真中精确建模。以下是工程实现中的关键信号与推荐参数。
首先是时序基准信号。phi1(Pin 25)与 phi2(Pin 24)是从外部晶振引入的相位时钟信号,原始 Altair 系统使用 2MHz 晶振,生成两相非重叠时钟。在仿真中,建议将 phi1 与 phi2 建模为周期 500ns 的方波,占空比各为 50%,且两相信号相位差为 180 度。PSYNC(Pin 76)在 phi2 的上升沿同步启动一个新的总线周期,PDBIN(Pin 78)在整个周期内指示数据输入方向。SMEMR(Pin 47)与 MWRT(Pin 68)分别指示内存读与内存写操作,其建立时间(setup time)应不小于 30ns,保持时间(hold time)不小于 20ns—— 这对应于 8080 CPU 的数据手册规范。
中断信号体系包含三个层次。六个向量中断输入 VI0-VI7(Pin 4-11)用于 8080 模式下的向量中断寻址,每个向量中断对应固定的内存地址(通常为 8 字节对齐的向量表)。SINTA(Pin 96)是中断响应信号,在 CPU 接受中断后拉低,通知设备将中断向量置于数据总线上。在 IEEE-696 扩展中,/NMI(Pin 12)作为不可屏蔽中断引入,直接触发 CPU 的 NMI 引脚,与 VI 信号独立。仿真实现中,建议为每个中断源维护独立的中断挂起队列,并按优先级(VI0 > VI1 > ... > VI7 > NMI)顺序服务。
直接内存访问(DMA)通道由 /DMA0-/DMA3(Pin 55-57)请求信号与 PHLDA(Pin 26)握手信号协同工作。当任意插槽拉低 DMA 请求信号时,总线仲裁层在当前指令周期结束后授予总线控制权,被授予的插槽通过 DO 数据线输出 16 位地址。建议将 DMA 请求建模为异步事件 —— 在每个指令周期结束时检查所有 DMA 请求线的状态,如果存在请求则在下一个总线周期开始前执行仲裁。这种异步建模符合真实硬件的行为,因为 DMA 通常由外设发起(如磁盘控制器读取数据),其时序与 CPU 指令执行无固定相位关系。
三、CP/M 环境的配置与磁盘子系统
CP/M 2.2 是 S-100 系统上最广泛部署的操作系统,其运行环境配置是虚拟工作台的核心功能。CP/M 的三层架构(BDOS、CCP、BIOS)要求仿真环境精确实现 BIOS 函数接口。
BIOS 层至少需要实现以下 18 个函数:BOOT(冷启动)、WBOOT(热启动)、CONST(控制台状态)、CONIN(控制台输入)、CONOUT(控制台输出)、LIST(打印机输出)、PUNCH(纸带穿孔)、READER(纸带读取)、HOME(磁头归零)、SELDSK(选择磁盘驱动器)、SETTRK(设置磁道)、SETSEC(设置扇区)、SETDMA(设置 DMA 地址)、READ(读扇区)、WRITE(写扇区)、LISTST(列表状态)以及两个辅助函数 SECTRN(扇区转译)与 SETDMA 默认地址设置。在实现中,建议将磁盘参数表(DPB)与物理扇区布局分离 ——DPB 定义每磁道扇区数、块屏蔽、目录项数等元数据,扇区转译函数负责将逻辑扇区号映射为物理磁道 / 扇区地址。这种分离使得同一套 BIOS 代码可以兼容不同的磁盘控制器硬件。
磁盘控制器插槽的仿真,建议采用与真实硬件类似的缓冲区管理机制。S-100 磁盘控制器通常使用双缓冲区:前台缓冲区接收来自主机的写命令,后台缓冲区将数据写入磁盘镜像文件。当仿真器检测到写扇区操作时,应将数据写入后台缓冲区,并通过 setTimeout 将实际的文件 I/O 操作延迟 16ms—— 这模拟了真实软盘驱动器的寻道与写入延迟。对于读取操作,则从磁盘镜像文件预读整个磁道(通常为 26 个扇区,容量 26×128=3328 字节)到前台缓冲区,以减少后续连续读取的访问延迟。
CP/M 的内存布局有严格的约定:最低 256 字节(0000H-00FFH)为向量与 BIOS 工作区,CCP(命令行处理器)占用 256 字节(DC00H-E3FFH),BDOS 占用 1536 字节(E400H-F47FH),用户程序从 F480H 开始向上延伸。调试工作台应提供内存映射可视化,允许用户实时查看各内存区域的占用状态 —— 这对排查程序崩溃与覆盖错误尤为关键。
四、调试工作流与监控参数
虚拟工作台的调试能力决定了其在固件开发中的实用价值。建议实现三类调试工具:前端面板模拟器、内存检查窗口与断点管理系统。
前端面板模拟器复现了早期 S-100 系统(如 IMSAI 8080)标志性的人机交互界面。面板应包含 16 位地址开关(用于手动输入内存地址)、数据开关(用于输入要写入的数据)、运行 / 停止 / 单步控制开关以及 16 位地址 LED 显示与 8 位数据 LED 显示。在仿真中,每个 LED 状态对应内存中的特定位向量,地址 / 数据开关的状态存储在 S-100 总线的 /ADDDSB(Pin 22)与 /DODSB(Pin 23)禁用信号生效时由前端面板驱动。这种模拟不仅有怀旧价值 —— 对于调试裸机固件(如在没有操作系统环境下直接访问硬件)而言,前端面板是唯一的调试手段。
断点管理系统应支持三种断点类型:执行断点(当 PC 到达指定地址时暂停)、内存访问断点(指定地址被读或写时触发)以及 I/O 端口断点(访问特定端口地址时触发)。断点表的数据结构建议采用红黑树实现,查找复杂度为 O (log n),这在调试大型固件(可能需要设置数百个断点)时仍能保持响应速度。每个断点条目应记录:类型、地址范围、触发条件、命中计数与触发时的完整 CPU 状态快照(包括所有寄存器、A/F 标志位与栈指针)。
监控参数方面,建议在仿真器运行时持续采集以下指标:每秒指令数(IPS)、总线利用率(活跃周期占比)、中断服务延迟(从中断请求到第一指令执行的时间)以及内存访问命中率(用于评估缓存策略)。这些指标通过 Web Worker 中的性能计数器实现 —— 每次执行 1000 条指令后,计算平均 IPS 与总线利用率,并将结果通过 postMessage 推送至主线程的仪表盘面板。合理的目标阈值:对于 Z80 @ 4MHz 全速模拟,IPS 应不低于 150000;在浏览器环境下,总线利用率通常在 60%-80% 之间波动,低于 50% 表明存在显著的调度开销。
五、实施建议与边界条件
在浏览器中运行 S-100 仿真,性能与兼容性之间存在不可忽视的张力。建议将 CPU 模拟核心以 WebAssembly(C++ 或 Rust)实现,以获得接近原生的执行效率 —— 纯 JavaScript 编写的 8080/Z80 模拟器在复杂固件(如带有密集 I/O 操作的磁盘驱动程序)下可能无法达到实时要求。Emscripten 是将 C/C++ 代码编译为 WebAssembly 的成熟工具链,核心模拟器代码应封装为独立的 WASM 模块,由 JavaScript 层调用其 run (cycles) 接口实现周期级步进控制。
边界条件方面,需要特别处理插槽冲突与电源异常。多个插槽同时驱动同一信号线(如 DO 数据总线)时,应采用线或(wire-OR)逻辑 —— 只有当所有驱动器均释放总线时,该信号线才回到高阻态。在仿真中,这可以通过维护一个驱动器计数表实现:每条信号线记录当前正在驱动它的插槽集合,只有当集合为空时才将信号线置为默认状态。电源引脚(Pin 1/51 的 +8V、Pin 2/52 的 +18V、Pin 50/100 的 GND)在仿真中不产生电气影响,但应在插槽初始化时检测电压配置是否匹配 —— 插入受电设备时若检测到电压不匹配,应向用户抛出警告而非静默运行,以防止不可预期的行为。
此外,S-100 总线的物理层特性(如 .140 英寸与 .250 英寸两种引脚间距)在仿真中可忽略,但插槽机械参数(板卡宽度 5 英寸、高度 10 英寸)在 UI 层面应有所体现 —— 这有助于用户在配置系统时建立直观的空间感。
S-100 总线承载了微型计算机革命的关键十年,其开放架构启发了整个行业。借助现代 Web 技术重建这一环境,不仅是对计算历史的致敬,更是为嵌入式开发与操作系统教学提供了一件可触手可及的实验工具。
参考资料:
- S-100 bus overview and history: Wikipedia entry on the S-100 bus.
- Herb Johnson's S-100 documentation with detailed pin lists and signal descriptions.
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。