在 4-bit 的逻辑地牢里,如果说算术指令提供了“肌肉”,逻辑指令开启了“感官”,那么接下来我们要聊的,则是这台机器最细腻的形态手术。
如果说
AND/OR是在判定“存在”,那么NOT和移位指令(SHL/SHR)就是在重塑数据形态。特别是在 10x20 的段码屏与 4 位总线这种“先天不育”的配置下,每一比特的平移,都是一场关于精度与空间的博弈。本文将深入探讨 NOT 的镜像之美与 SHL/SHR 的平移艺术。我们将看清NOT指令如何在地牢里颠倒黑白,并利用补码逻辑模拟出消失的减法灵魂;更将解构SHL/SHR指令如何利用那枚 1-bit 的 Carry Flag 作为临时码头,接应从物理边界坠落的数字碎片,完成对 10 列像素显示区的空间缝合。在这场比特级别的微操中,每一位数据的进退,都是对 4 位总线极限的一次精准裁断。
NOT 指令:地牢里的“黑白镜像”
- 注记符: NOT
- 硬编码: 0x05(对应你代码中的 case 0x05)
- 类型: 逻辑运算
- 指令周期: 2(1周期取指 + 1周期内部反相)
- 影响标志: Zero (Z)
指令格式
在 4-bit ISA 体系中,NOT 指令通常采用 累加器寻址(Accumulator Addressing) 模式:NOT A
- Opcode (4-bit):
0x05标识这是一次逻辑取反操作。 - Operand: 无(隐式指向累加器 A)
指令分析:
在 4-bit 架构的严苛约束下,为了节省昂贵的专用减法器电路,我们必须充分挖掘现有指令的潜力。此时,NOT 指令便成了我们手中极具性价比的‘逻辑杠杆’。
在这一体系中,减法运算 (A - B) 并不是原生发生的。我们巧妙地利用补码逻辑,通过先加载操作数 (B) 至累加器并执行
NOT A(按位取反),随后配合前文提到的ADC A, #1(取反加一)操作,让原本仅具备加法逻辑的 ALU,在瞬间获得了处理减法运算的能力。此外,
NOT指令的物理特性也直接服务于《俄罗斯方块》的视觉呈现。在处理‘整行反色’或‘高亮闪烁’等动画特效时,NOT是最理想的优化手段——它无需复杂的逻辑分支判定,仅需 2 个指令周期即可完成 4 个比特位的瞬间电平翻转,实现了显示状态的高效切换。从硬件电路来看,NOT 是 ALU 内部响应最快的组件,因为它不需要进位链的漫长等待。虽然 NOT 只是翻转电平,但它依然会触发 Zero 标志位,在 4-bit 系统中,只有当 A 原本为 1111b (15) 时,执行 NOT 才会产生全零结果并点亮 Z 标志。这常被用来探测某个计数器是否达到了它的物理上限。在实现上寄存器 A 的输出端到总线之间,物理上排列着 4 个 NOT 门(反相器)。
我依然介绍一些实力场景:
1. 模拟减法:计算 (5 - 3)
; 目标:计算 A = 5 - 3 ; 4-bit 补码逻辑:A - B = A + (NOT B + 1) LDI A, 3 ; A = 0011 (操作数 B) NOT A ; A = 1100 (取反) CLC ; 清除进位标志(确保 ADC 的进位输入为 0) ADC A, 1 ; A = 1101 (此时 A 已经变成了 -3 的补码形式) MOV B, A ; 将 -3 暂存到寄存器 B LDI A, 5 ; A = 0101 (操作数 A) ADD A, B ; A = 0101 + 1101 = 10010 ; 硬件自动丢弃溢出的第5位,结果 A = 0010 (十进制 2)这一连串动作展示了NOT如何作为逻辑杠杆,在 2 个周期内将一个正数强行扭转为负数补码,从而绕过了对硬件减法器的依赖。
2. 视觉特效:LCD 段码屏的“整行反色”:
在《俄罗斯方块》中,当一行填满或者游戏结束时,我们需要让屏幕内容瞬间反转(黑变白,白变黑)。
; 假设内存 0x80 存放的是当前行第一个 Nibble 的显存数据 ; 原始数据假设为 1010 (两格亮,两格灭) LDA [0x80] ; A = 1010 NOT A ; A = 0101 (亮灭状态瞬间翻转) STA [0x80], A ; 写回显存,屏幕上该区域立刻实现“负片”效果在 4-bit 这种算力贫血的机器上,NOT的价值在于其非判断性。你不需要知道当前像素是亮还是灭,只需一次电平翻转,就能实现最纯粹的视觉冲击。
3. 状态判定:检测计数器是否达到上限 (15)
有时候我们需要判断一个 4-bit 计数器是否已经数到了头(1111b)。
; 假设 X 寄存器是我们的计数器 MOV A, X ; A = 1111 (如果计数器已满) NOT A ; A = 0000 JZ COUNTER_FULL ; 如果结果为 0 (Zero Flag = 1),说明原值是 15这是利用NOT触发 Zero Flag 的典型用法。这种“镜像判定”比复杂的比较指令更节省指令空间。
所以:在 4-bit 的逻辑地牢里,减法不是一种本能,而是一场骗局。 我们利用
NOT指令颠倒黑白,再通过ADC补上一枚比特,让只会加法的 ALU 在不知不觉中完成了对数值的削减。这种硬件级‘偷梁换柱’的智慧,是所有微型计算机生存的基础。
SHL / SHR 指令:比特的位移手术
- 注记符: SHL (左移) / SHR (右移)
- 硬编码: 0x08 / 0x09
- 类型: 位移运算
- 指令周期: 3
- 影响标志: Zero (Z), Carry (C)
指令格式
在 4-bit 架构中,移位指令是处理非对齐数据的唯一手段:
SHL A或SHR A
- Opcode (4-bit): 标识移位方向。
- 操作对象: 隐式指向累加器 A,将 4 位数据整体向左或向右推移 1 bit。
指令解析:
这两条位移指令在 4-bit 架构中占据着极其关键的地位。由于我们的显示区域设定为 10 列像素,而数据总线位宽仅为 4 位,导致一行像素数据被迫“碎裂”,跨地址分布在三个 Nibble 单元中采用 (4+4+2) 的内存布局。
在这种物理限制下,平移方块本质上是一场比特级别的“跨栏接力”:
- 坠落与暂存:我们首先对第一个内存单元执行
SHL(左移)。此时,溢出的最高位(\(D_{3}\))并不会直接消失,而是如同“坠落”一般,被锁存在 Carry Flag(进位标志位)中。- 缝合与重组:随后,我们对相邻的第二个内存单元执行带进位的循环移位,将 Carry 标志位中暂存的那枚比特“缝合”回新单元的最低位(\(D_{0}\))。
如果没有这一套位移指令,方块在 10 列屏幕上的移动将只能以 4 个像素为步长进行生硬的“瞬移”。SHL/SHR 的存在,是 4 位机展现平滑动画效果的唯一技术屏障。
硬件层面的连锁反应
在仅有的 3 个指令周期内,CPU 内部经历了一场微观的电平风暴:数据从寄存器流出,经过移位矩阵的物理偏转,最终将溢出位精准锁存至 Carry 引脚。
- SHL(逻辑左移):数据向高位挤压,原 \(D_{3}\) 位的电平信号被直接导向状态寄存器的 Carry 输入端。
- SHR(逻辑右移):原 \(D_{0}\) 位的信号线在位移瞬间被 Carry 捕获,高位则由硬件自动补零。
技术总结:跨维度的“避风港”
至此,我们利用 Carry Flag 成功构建了一个跨越地址维度的“临时避风港”,用来接应那些从物理边界坠落的数字碎片。这种在 4 根信号线里闪转腾挪的艺术,不仅解决了 10 列像素的对齐死局,更是在这片资源荒原上,为图形学实现了最基础的“运动自由”。
实战1:10列像素的“跨地址平移”
这是解决 10 列像素(跨 3 个 Nibble)水平移动 1 像素的核心算法。我们将展示如何利用Carry Flag像接力棒一样传递比特。
; 场景:将一行 10 位数据(存储在 0x80, 0x81, 0x82 三个地址)整体左移 1 位 ; 内存布局:[0x80: bits 0-3], [0x81: bits 4-7], [0x82: bits 8-9] ; --- 处理第一个单元 (低 4 位) --- LDA [0x80] ; 加载第一个 Nibble SHL A ; 执行左移。此时 D3 溢出,坠落并锁存到 Carry Flag STA [0x80], A ; 写回,此时 bit 0 空出,原 bit 3 暂存在 Carry 中 ; --- 处理第二个单元 (中 4 位) --- LDA [0x81] ; 加载第二个 Nibble ; 这里假设你实现了一条指令叫 ROL (Rotate Left through Carry) ; 如果没有,逻辑如下: SHL A ; bit 7 坠入新的 Carry,旧的 Carry 还在等待“缝合” ; [此处需逻辑干预:将旧 Carry 补回当前 A 的 D0 位] ; 这展示了为什么 4-bit 程序员极其渴望硬件支持“带进位循环移位” STA [0x81], A ; --- 处理第三个单元 (高 2 位) --- LDA [0x82] SHL A ; 延续接力过程 STA [0x82], A实战2: 利用位移替代“昂贵”的乘除法
在 4-bit ALU 中,没有乘法器。要计算方块坐标或得分偏移,位移指令是性能最高的操作。
场景:将寄存器 A 的值快速乘以 2(例如计算内存偏移)
; 目标:计算 A = A * 2 LDA #0011b ; A = 3 SHL A ; A = 0110b (结果为 6) ; 指令周期:3如果用加法ADD A, A可能需要更多的取指周期。而SHL直接在 ALU 内部通过布线完成,速度极快。
实战3: 提取特定的“像素位”
; 目标:检查 A 的 D2 位 LDA [0x20] ; 假设 A = 0100b SHR A ; A = 0010b, Carry = 0 (D0坠落) SHR A ; A = 0001b, Carry = 0 (D1坠落) SHR A ; A = 0000b, Carry = 1 (D2坠落) JC BIT_IS_ONE ; 如果 Carry=1,说明原 D2 位是 1这种通过 “连续坠落” 到 Carry 的方式,是 4-bit 机器在没有BIT测试指令时,探测任意比特位的标准手段。
至此那个被我称为‘码头’的 Carry Flag,在这些例子里不停地忙碌着。 它一会儿要接住左移溢出的像素,一会儿要捕获右移坠落的状态位。这种在时间轴上拼凑空间完整性的做法,正是 4-bit 程序员在资源地牢里的日常。在这里,代码不是写出来的,是缝出来的。
至此,【运算篇】的所有武器库——从跨维接力的 ADD/ADC,到逻辑审判的 AND/OR/XOR,再到比特缝合的 NOT/SHL/SHR,均已悉数列阵。
规则已经订立,但这仅仅是‘纸上谈兵’。下一期,我们将撕开逻辑的表象,把这套 4-bit 模拟器的具体代码框架完整地推向台前。 我们不再满足于抽象的指令推演,而是要开启真实的指令集压力测试。看这些在‘地牢’里博弈出的指令,如何在真正的 C++ 代码运行环境中,跳动出第一波比特流。