概要: 仓库已焊接,通路已畅通,是时候为这台 4-bit 微机注入跳动的灵魂了。在上期用多态路由表完美复现 74LS138 译码逻辑、并攻克了 1KB 空间的换页救赎后,本篇我们将正式合拢整机控制流的关键拼图,起吊 CPU 内部的时序内核。

本文将深度解构 CPU 内部四大通用寄存器(A, B, X, Y)与程序计数器(PC)的代码状态模型。我们将拆解长达数百行的 switch-case 核心译码状态机,看 4-bit 机器如何在纳秒间将一个标准指令字节拦腰切断,在上半字节执行一级核心律令分发,并在下半字节引爆次级扩展套娃。这里不玩高级语言的过度抽象,每一次 CPU::step() 的步进,都是微观尺度下时钟脉冲对 D 触发器的冰冷鞭笞。

第一章:时间钟摆的物理账本——CPU::step() 与时序循环

在现代高层软件的开发逻辑里,一个循环通常以宿主机的极限算力在狂飙。但在仿真系统的底层,CPU 的生命是由一条条时钟周期(Clock Cycles)的铁律量化而成的。

为了证明我们此前清算出的那个“26时钟周期性能账本”不是纸上谈兵,CPU 核心必须建立起标准的取指-译码-执行(Fetch-Decode-Execute)时序模型。每一条指令对总线的轰击频次,都在这里转化为精确的物理损耗。我们要让模拟器的每一次单步步进(Step),都与那颗老式晶振的每一次摆动形成绝对的时序共振。

第二章:瞬时人格的锁存——寄存器堆与状态标志位【CPU.h】

  • 2.1 4-bit D 触发器的代码映射
    • 披露 CPU 核心的内部结构设计。看通用寄存器(A, B, X, Y)与程序计数器(PC)、堆栈指针(SP)如何在 4-bit 的硬上限下,通过位掩码 & 0x0F 维持其脆弱的物理尊严。
  • 2.2 标志位(Flags)的因果律
    • 解构 Zero(Z)与 Carry(C)状态寄存器的物理锁存逻辑。在硬件底层,标志位就是一排在时钟边沿捕获 ALU 瞬时电平输出的 D 触发器,它是后续 JZ(为零跳转)等分支指令能“记住”历史结果的唯一物理基础。
  • 2.4 瞬时人格的静态声明【CPU.h】

    在真实的硅基芯片上,通用寄存器和程序计数器是由一排排物理的 D 触发器(Flip-Flop)并联而成的。它们是整个系统里最忙碌的时序逻辑系统(Sequential Logic)。为了锁存处理器的“瞬时人格”,我设计了如下的 CPU 核心结构,它在软件层面上用位宽限位拉起了维持 4-bit 尊严的防火墙:

#ifndef CPU_H
#define CPU_H

#include <cstdint>
#include "includes/bus.h" // 接驳调度总线

// 状态寄存器标志位掩码映射(硬件状态引脚定义)
#define FLAG_Z (1 << 0)  // 0001b : Zero 标志位引脚
#define FLAG_C (1 << 1)  // 0010b : Carry 标志位引脚
#define FLAG_H (1 << 2)  // 0100b : Halt 停机控制引脚

class CPU {
private:
    Bus* bus; // 物理总线指针,CPU 探向外界的排线

    // 🔒 通用寄存器堆:映射 4 组 4-bit D 触发器阵列
    std::uint8_t A = 0; // 累加器 A (Accumulator)
    std::uint8_t B = 0; // 通用数据寄存器 B
    std::uint8_t X = 0; // 变址/计数寄存器 X
    std::uint8_t Y = 0; // 变址/计数寄存器 Y

    // 📏 控制流指针:
    std::uint16_t pc = 0; // 12-bit 程序计数器 (腿长 2KB 寻址)
    std::uint8_t  sp = 0; // 8-bit 堆栈指针 (栈顶初始值常驻物理高地址)

    // 🚦 状态寄存器:
    std::uint8_t flags = 0; // 锁存 ALU 轰击后的瞬时状态信号

    // 内部时序控制流组件
    void updateFlags(bool zero, bool carry);

public:
    CPU(Bus* system_bus);
    ~CPU() = default;

    void step(); // 时间钟摆的单步跳动(取指-译码-执行)
    void decodeAndExecute(std::uint8_t opcode);
};
#endif // CPU_H

第三章:律令的手术刀——二级译码状态机【CPU.cpp 细节】

  • 3.1 单字节指令的“拦腰切断术”
    • 完整公开 CPU::decodeAndExecute 状态机的核心分发逻辑。
    • 技术细节剖析:总线抓回来的标准指令字节(8-bit)是如何对半切开的?高 4 位 D7 ~ D4 作为控制主操作码瞬间打入一级分发区;低 4 位D3 ~ D0 则根据控制流的意志,分饰立即数、寄存器编号或次级扩展码等多重角色。
  • 3.2 0x00 房间的“套娃机制”与单字节特权分发
    • 核心代码片段解剖:当核心操作码落在 case 0x00 时,总线硬件如何强行启动二级译码,从低 4 位中提取 NOPRET 以及负责内存压栈与弹栈的 PUSH/POP A。看这种操作码极限压榨如何为 CMP(影子比较)和 AND(碰撞雷达)腾出宝贵的头等舱位置。
  • 3.3 长腿控制流的物理跑飞与状态暴走
    • 剖析 case 0x0C (JMP) 与 case 0x1B (CALL) 的执行流。当 CPU 必须连续跑 3 趟总线去拼接一个 12 位的返回地址时,看向下坍塌的堆栈指针(SP)如何在这片 1KB 的赤字荒原上踩踏变量区,并最终将 PC 指针误导向真空深渊,引发仿真内核的致命“跑飞”死机。
  • 3.4 标志位(Flags)的因果律与锁存逻辑展开

当 ALU 完成一次算术运算后,它的输出引脚上会在万分之一微秒内建立起一组瞬时瞬态电平。如果 CPU 不及时抓住它,随着下一个周期的到来,这组代表着碰撞结果或算术溢出的信号就会在物理总线上彻底灰飞烟灭。

在电路里,标志位就是一排并联在 ALU 状态输出端的 D 触发器,它们在时钟上升沿到来的物理瞬时,像闸门一样“咔哒”一声将电平强行锁死。这就是为什么 JZ 或 JC 指令能依据上一次的计算结果来决定今天往哪跳。

请看我们在代码里是如何用位运算的因果律去仿真这一电平锁存过程的:

// CPU.cpp - 状态锁存与标志位引脚行为的内部细节
void CPU::updateFlags(bool zero, bool carry) {
    // 🚦 1. Zero 标志位锁存因果律
    // 当 ALU 的输出 Value 为全零时,触发器捕获高电平。
    if (zero) {
        this->flags |= FLAG_Z;  // 强行动火:将 flags 的 D0 位引脚拉高
    } else {
        this->flags &= ~FLAG_Z; // 强行拉低:剥离 Z 标志电平
    }

    // 🚦 2. Carry 标志位锁存因果律
    // 当算术加法溢出 (rawSum > 15) 或 移位指令导致 D3 坠落时,进位链输出激活。
    if (carry) {
        this->flags |= FLAG_C;  // 强行动火:将 flags 的 D1 位引脚拉高
    } else {
        this->flags &= ~FLAG_C; // 强行拉低:剥离 C 标志电平
    }
    
    // 💡 调试者直觉:这里的按位操作不是普通的软件赋值,
    // 它精准模拟了硬件在时钟下降沿来临前,寄存器引脚对 ALU 组合逻辑信号的原子性捕获。
}

🏁 结束语与下期预告

至此,【控制篇】的时间钟摆已经彻底在 CPU 核心内挂载完毕。

我们通过解构 CPU::step() 的时序循环、锁存寄存器人格的状态标志位,以及那个高频切分指令字节的二级译码状态机,终于给这台 4-bit 微机的冯·诺依曼主板注入了第一波跳动的比特流。当我们在 case 0x00 房间里用套娃机制压榨机器码、在 CALL 指令的压栈风暴中看着 SP 指针向下坍塌时,这台处理器的执行核心已经在纳秒间复现了晶体管连锁雪崩的物理惯性。

心跳已点燃,法典已闭环,运算与控制的基石至此全面合拢。

现在,存储芯片提供了格位,总线提供了通路,CPU 提供了微代码时序。整台原本冰冷的硬件大联盟,已经彻底在代码世界里宣告“上电复位”。

下一期,我们将迈入整个专栏最具视觉冲击力的终极一战——【实战篇】俄罗斯方块地摊机的整机联调与点火运行!

我们将首次把这颗跑通了全部 30 条指令集的大脑,连上我们专门定制的 10×20 段码屏显存区(VRAM),并挂载上读取玩家物理按键电平的 0x300 端口。我将手把手带大家见证,我们在这间 4-bit 逻辑地牢里肉搏出的每一条机器码,究竟是如何在 1KB 的赤字账本里闪转腾挪,并最终让那个永恒经典的方块世界,在我们的模拟器界面上平滑、丝滑地舞动起来的!

🔗 【交叉编译器大坑长线留存指令】

有朋友问,代码只剩 1260 格,手动写汇编怎么控制在 300 条指令内?这正是本大系列在整机跑通后,必须要搞的【终极收费大专栏:自研 4-bit 跨段调用高级语言交叉编译器】要解决的降维痛点。

想看软件最难的《编译原理》如何完美对接硬件最硬的《计算机系统结构》、让编译器自动识别函数跨段并自动插入 STA [0x301] 换页机器码的朋友,请继续在评论区长线留存、疯狂扣 1 催更!

数学骨架已成,代码蓄势待发。
接下来,这群顶点将跳上流水线,开启一场从 CPU -> 内存 ->  操作系统 -> 显卡显存的惊天大位移。
我们下一站:打破次元壁,看『数据大搬运』如何让方块真正降临!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐