【总线篇】物理秩序的建立:统一编址与多路复用译码的硬件法则

概要:在上一期清算出 1KB 内存的配置上限、并分析了堆栈与数据区可能发生的空间冲突后,我们直面了平坦寻址下的资源限制。为了在不改动已压测存储芯片的前提下实现空间的动态扩展,我们提出了“芯片不动,总线变轨”的硬件重解耦策略。
本篇我们将正式切入【总线篇】,深度拆解这台 4-bit 处理器的核心调度枢纽——Bus(总线)模块。
我们将从硬件层面清算直连片选方案中高昂的“引脚税”代价,剖析主板铜线引脚阵列与地址译码电路(如经典 74LS138)的多路复用分发逻辑。我们更将详细解构编址在 0x301 端口上的 Bank 控制寄存器,看总线如何利用地址拦截与片选切换,在 4 根信号线的物理总线限制下,实现内存空间的无损调度与多维扩容。
信号已对齐,通路已划分。这一次,我们从总线层面对系统秩序进行重新定义。
🗺️ 本章远征路线图

第一章:冯·诺依曼主板大联盟——统一编址接口协议

在编写这套仿真内核时,我们必须彻底洗掉现代高级语言那种“挥霍资源”的惯性,转而站在整机系统集成与芯片级级联(System Integration)的高度去思考。在物理世界中,处理器是一个瞎子。它不知道自己读到的是一段 ROM 里的机器码,还是一个 RAM 里的临时变量,抑或是 I/O 外设。在冯·诺依曼的宇宙里,万物皆为连续的物理内存。
1.1 统一编址协议(IDevice)的诞生
真实的芯片引脚极其冷酷。无论是多么高级的外部芯片,只要它想挂载到系统总线上,就必须对外暴露三组核心引脚:地址引脚、数据引脚、使能引脚 (OE/WE)。
在 C++11 的世界里,我们设计了整个仿真宇宙中最纯粹的一个虚基类接口——IDevice
class IDevice {
public:
    virtual ~IDevice() = default;

    // 读操作:映射芯片上读使能引脚(OE)在时钟脉冲时的电平变化
    virtual uint8_t read(uint16_t addr) = 0;

    // 写操作:映射芯片上写使能引脚(WE)在时钟脉冲时的电平变化
    virtual void write(uint16_t addr, uint8_t data) = 0;

    // 复位:对应芯片上的异步复位引脚(Reset Pin),强行将内部状态机瞬间清零
    virtual void reset() = 0;
};
通过这套协议,不管是静态随机存储器(Memory),还是未来处理输入的键盘控制器,在总线眼中都退化为了一个标准的、可以被肆意读写的“电平容器”。
1.2 物理领土的合拢:Memory 芯片的静态模拟
作为第一个实现 IDevice 接口的实体,Memory(存储阵列) 对应一颗静态随机存储芯片(SRAM),其行为由纯粹的组合与锁存逻辑驱动。在当前的基准测试阶段,我们暂且静态配置 1KB 的物理存储空间以满足测试代码的常态运行。

第二章:实战的梦魇——《俄罗斯方块》在 1KB 配置下的破产账本

在上一期中,我们共同清算了一笔残酷的“性能账本”:4-bit CPU 为了给坐标加个 1,竟然要在总线上连续折返跑、足足压榨出 26 个时钟周期。但那只是时间的灾难,接下来的空间死局才真正致命。
2.1 刚性游戏业务的物理负债计算
在统一编址的冯·诺依曼架构中,1KB 内存最大的噩梦,是数据流与控制流在物理空间上的“相向相撞”。我们让全局变量从低地址向高地址顺序生长;同时,让硬件堆栈(Stack)从内存最高处向下生长。
  • 数据区的步步紧逼:随着游戏开始,地图缓冲区(10×20个物理方块)、当前方块坐标、消除标志、得分计数器等全局数据开始从低地址向上蔓延。
  • 堆栈区的“向下狂飙”:为了让代码模块化,我们必须频繁调用子程序(检测消行->累加分数 -> 刷新屏幕)。
    • 函数嵌套的代价:按理说程序计数器(PC)是 12-bit 的,在物理堆栈里应占 3 个 4-bit 格位。但为了简化总线译码和弹栈时的移位拼接电路,我选择在执行压栈时,把 12 位地址按字节对齐(Byte-aligned),分两次强行写入。 其结果是,每执行一次 CALL,堆栈指针(SP)都会以 4 个物理格位的恐怖速度向下坍塌。
    • 按键中断的“背刺”:当玩家疯狂按下按键,或者定时器触发方块下落时,系统会瞬间激活硬件中断。CPU 必须立刻刹车,将当前的 A、B、X、Y 寄存器快照和 Flags 状态强行压入堆栈保存现场。
2.2 物理相撞与系统毁灭
当消行函数嵌套过深,恰逢玩家疯狂按键触发多重中断,向下坍塌的堆栈与向上蔓延的数据区将在某一个物理格位上迎头相撞!
向下压栈的返回地址,会无情地覆写掉原本存放方块坐标的数据;或者反过来,坐标数据的更新污染了返回地址。当子程序执行完毕调用 RET 时,CPU 抓出了一个被扭曲的伪地址。程序计数器(PC)被导向了一片未知的空白物理区,整台地摊机瞬间陷入花屏、死机或逻辑暴走。

第三章:物理破产的终极救赎——总线变轨与 Bank 切换

仅靠优化软件算法根本无法根治内存的贫血,我们唯一的生路,就是借用一间 1KB 大小的“实体公共魔术方块”(物理换页窗口),在背地里调包不同的存储芯片(Banks),实现空间的无中生有。
这种在不改变硬件的前提下,通过总线协议对物理引脚进行极限复用的设计,被称为 Bank Switching(段切换技术)。
3.1 空间变轨的物理拓扑图
第三章:物理破产的终极救赎——总线变轨与 Bank 切换
仅靠优化软件算法根本无法根治内存的贫血,我们唯一的生路,就是借用一间 1KB 大小的“实体公共魔术方块”(物理换页窗口),在背地里调包不同的存储芯片(Banks),实现空间的无中生有。
这种在不改变硬件的前提下,通过总线协议对物理引脚进行极限复用的设计,被称为 Bank Switching(段切换技术)。
3.1 空间变轨的物理拓扑图
sbt

第四章:终极纠偏:别用现代常识误读 4-bit 宇宙的“格子”

现代程序员有一个根深蒂固的常识:1 个内存地址 = 1 个字节(8 位)。如果带着这个直觉,你会在硬件译码中陷入严重的思维踩踏。请死死记住以下两条物理铁律:
  • 💾 数据的世界(4-bit Data Bus):
    数据总线、通用寄存器全部只有 4 位宽度。CPU 访问任意一个内存地址,一次只能掏出一个最大到 15 的半字节(Nibble)。
  • 📏 空间的换算(1KB 内存到底有多少格子?):
    在标准国际单位里,1KB = 1024 x 8 bit  = 8192  根电平物理信号线。
    在 8-bit 机器里,1KB 空间对应 1024 个格子。
    但在我们的 4-bit 宇宙里,每个抽屉只能塞进 4 位,要想容纳 1KB 的物理容量,整整需要  8192 / 4 = 2048  个地址格子!
这就是我们目前 1KB 静态 RAM 的真实轮廓:它从 0x000 延伸到 0x7FF,已经彻底把 12 位 PC 理论上一半的寻址大盘(2KB)给榨干了。

第五章:跨越“陷阱”:为什么是 0x301 与物理真空的沉默?

几个关于主板物理布线与体系结构的设计死角,在底层硬件的眼里,是稍有不慎就会引发整机烧毁的“物理陷阱”。我们必须跳入陷阱深处,把这两个谜底彻底揭开:
5.1 为什么低寻址空间(0x000 ~ 0x3FF)被设计为“ROM区 + 物理真空”?
这并非我的主观偏好,而是为了同时满足微处理器赖以生存的两大硬件刚需:“上电冷启动的硬律令” 与 “地址译码引脚的成本折中”。
在这台处理器的 12-bit 地址线 A_11 ~  A_0 大盘中,最高位地址引脚  A_11 是一根物理分水岭:当 A_11= 0 时,CPU 激活低 1KB 寻址区(0x000 ~ 0x3FF)。
  • 🔌 0x000 起始的 ROM 引导区:当按下电源键的那一微秒,主板上的复位电路会强行将程序计数器清零(PC = 0x000)。CPU 睁开眼抓取的第一条指令必须不能因为掉电而消失。因此,线性地址最前端(0x000 ~ 0x0FF)必须挂载一块只读存储器(ROM)芯片来存放引导机器码。
  • 🔌 0x100 ~ 0x3FF 之间的物理真空区:为了省下地址解码门电路的硬件成本,我们采用了“稀疏编址(Sparse Addressing)”策略:干脆在这段铜线上不挂载任何物理芯片。当 PC 指针意外踩进该区域时,由于没有任何硬件引脚响应片选,数据总线上产生“浮空电平”(仿真中表现为返回 0),不仅节约了布线成本,更作为一道物理缓冲带隔离了引导区。
5.2 物理陷阱解密:为什么换页控制端口偏偏是 0x301
在冯·诺依曼统一编址(MMIO)的语境下,0x301 这个所谓的“端口”,根本不是独立的硬件,它就是“换页寄存器”在总线线性地址上的物理投影。
我之所以把它紧紧锁在键盘输入地址 0x300 的身旁,是为了让主板上的译码电路最大化共享高位地址的拦截晶体管。我们将 0x301 还原为 12 位二进制引脚状态 A_11 ~ A0
键盘输入端口 (0x300):   [0 0 0] [1 1 0 0] [0 0 0 0]  <-- 对应引脚 A0 = 0
换页控制端口 (0x301):   [0 0 0] [1 1 0 0] [0 0 0 1]  <-- 对应引脚 A0 = 1
                          ^       ^         ^       ^
引脚物理命名 (Pins):   A11~A9   A8~A5     A4~A1    A0
                        └─────── 共享高位拦截线 ──────┘
在主板的物理布线上,总线译码器做完高 11 位 A_11 ~ A_1 的大门判定后,剩下的全靠 A_0  这一根引脚铜线 在 0 与 1 之间横跳:
  • 当 A_0 = 0 时(CPU 访问 0x300):激活输入外设,读取玩家的按键状态。
  • 当 A_0 = 1 时(CPU 访问 0x301):高电平直接轰向换页控制寄存器的锁存端(LE/CLK),将当前数据总线上的数字锁存为最新的 Bank 段号。这让主板上用于拦截 I/O 地址的晶体管数量暴降近 40%。
5.3 现场时序复盘:微观电平的连锁雪崩
弄清了这根隐秘的 A_0 引脚,我们终于可以现场复盘一次当程序执行 STA [0x301], A(向换页端口砸入新的段号)时,主板内部经历的微观时序雪崩:
  • 📡 电平建立:12 位地址线 A_11 ~ A0  瞬间建立起 0011 0000 0001 的物理电平。
  • 🚪 拦截开闸:总线译码器判定高 11 位完美符合,大门电路在纳秒间瞬间导通,释放选通信号。
  • 引脚轰击:与此同时,最低位地址引脚 A_0 = 1  的高电平 畅通无阻地轰向了外部寄存器的时钟锁存端(LE / CLK)。
  • 🔒 瞬时死锁:锁存端被强行开闸,寄存器瞬间抓取了当前数据总线 D_0 ~ D_3 上的新段号并强行锁死。
  • 🔄 变轨完成:总线内部的多态路由瞬间完成了“芯片片选信号对调”。
在下一个时钟周期,当 CPU 去访问 0x500(第 1280 个格子)时,眼前的抽屉已经从 Bank 0 的显存,无缝变成了 Bank 1 的游戏地图。
 

第六章:历史的万流归宗——从 4-bit 换页术到现代 x86 MMU 页表机制

当我们用 3 根控制线在 4-bit 总线上玩弄“虚拟窗口”的更替、用软件指令频繁手动切换片选电平时,你或许会觉得这是一种在资源匮乏年代被逼出来的、极其原始的“软件外挂”。
但体系结构的迷人之处在于,历史总是在最高处万流归宗。
如果你翻开现代 x86 架构的内核开发手册,去解构操作系统最核心的 虚拟内存(Virtual Memory) 与 MMU(内存管理单元)页表机制,你会外露地发现其底层哲学与我们的 Bank 换页术完全共享着同一套基因。
6.1 概念的物理对齐
  • 4-bit 地牢中的 [0x400 ~ 0x7FF] 窗口 -> 在 x86 体系中被称为虚拟地址空间(Virtual Address)。
  • 那 4 块物理 SRAM 芯片(Bank 0 ~ 3)-> 在现代 OS 中被称为物理页帧(Physical Page Frame)。
  • 我们编址在 0x301 的 Bank 寄存器 -> 在 x86 处理器中叫做 CR3 控制寄存器。
6.2 物理映射的无缝咬合
在现代 Linux 操作系统中,当一个应用进程进行内存访问时,CPU 伸出的寻址长腿其实和我们的 4-bit 机器一样尴尬——它看到的地址是虚幻的:
现代 x86 MMU 工作流:
[应用虚拟地址] ---> [ 硬件 MMU (翻看 CR3 页表) ] ---> 重新变轨 ---> [ 真实物理内存颗粒 ]

自研 4-bit 处理器工作流:
[ CPU 虚拟窗口 ] ---> [ 逻辑总线 (翻看 0x301 寄存器) ] -> 物理选片 ---> [ 某一块 Bank 芯片 ]
两者的唯一断层,仅仅在于执行者的身份:在我们的 4-bit 地牢里,由于硬件过于贫血,我们必须充当“手动挡司机”,由汇编程序员在每一行代码间痛苦地用 STA [0x301] 去手动拨动开关完成变轨。而在现代 x86 架构中,这一切换逻辑被固化在了 CPU 内部的 MMU 硬件电路中。
无论现代操作系统的多任务机制多么宏大,当它在每一个微秒进行内存页面的换入换出时,它都在向这台只能数到 15 的 4-bit 机器 致以最深沉的硬件敬意。
🏁 秩序的建立:统一编址与多路复用译码的硬件法则
至此,我们在 4-bit 的逻辑地牢里,不仅完成了 NOT 的黑白颠倒与 SHL/SHR 的空间缝合,更通过一笔残酷的“1KB 赤字资产负债表”,彻底看清了由函数嵌套与高频中断引发的“堆栈下穿、数据踩踏”的物理梦魇。为了给这台原本盲目的机器建立起确定性的物理秩序,我们提出了“芯片不动,总线变轨”的自救指南。我们用 4096 个“4-bit抽屉”重新定义了格子的容积,更抓出了 0x301 端口里那根决定两界生死的 A_0 物理引脚。
下一期,我们将正式迈进【总线源码篇】的暴风眼,首次完整公开调度万物的幕后功臣——Bus 类的 C++11 核心源码!
我们将不再满足于抽象的电路图,而是带大家去解构:C++ 的虚函数多态反而是如何完美映射硬件上那一条条铜线总线的?看代码里那看似平淡的线性路由,又是如何在底层完美复现 74LS138 译码门电路的片选(Chip Select)逻辑的?
📌 【铁粉专享:现代 x86 MMU/页表机制硬核番外预告】
响应很多深度底层读者的强烈呼声,在这篇总线代码跑通测试后,我将专门开辟一期 【技术番外篇】。
我们将彻底撕开高级语言的黑盒,以这台 4-bit 机器的 Bank 切换为显微镜,去死磕 Linux 内核最核心的内存虚拟化。我将大家逐行拆解:现代 x86 处理器内部的 MMU 硬件电路 到底是如何自动翻看 CR3 寄存器(页表基址) 的?多任务操作系统在切换进程的微秒间,又是如何用和这台地摊机完全相同的“变轨外挂”,去实现 TB 级内存空间的无损对冲与无中生有?
那将是一场跳出 4-bit 牢笼、直击现代多核 CPU 心脏的体系结构远征。
地牢的围墙已经加固,赤字的死局已经布好。下一站,我们去解密总线源码,看 C++ 如何指挥第一波电平信号各就各位!
通道已开,时序将动。我们下一站:总线源码篇,见!

posted on 2026-05-21 15:25  P_P_thoughts  阅读(17)  评论(0)    收藏  举报

导航