关于6502CPU Pstatus寄存器Carry位和Overflow位的简单笔记

写工程日记有很多好处,一个额外的好处是你能时不时想起多年以前你在做什么,会想到那些人,那些项目,以及那些糟糕的衣服和发型。

最近在写nes模拟器,其中SBC减法指令的模拟给我造成了不小的麻烦。尤其是减法计算对Pstatus寄存器中O和C位的影响。本文是如何模拟加法(adc)指令和减法(sbc)指令的总结。

众所周知,6502 CPU有一个Pstatus寄存器,简写为P。该寄存器大小只有单个字节,8位。

首先,这8位代表的含义分别如下:

位数 标志 名称 作用
第7位 N negative 正负
第6位 V overflow 借位/进位
第5、4位 B break 用于指示当前是否在处理中断以及是何种中断(php, brk或是nmi, irq)
第3位 D decimal 置位后,算术操作为BCD09+01=10,清除则二为补码
第2位 I 禁中断interrupt disable 置位,则禁中断
第1位 Z zero 结果为0
第0位 C carry 进位

接着说一下模拟加法指令和减法指令时要如何处理这两位。

计算Carry位

处理Carry位时,要牢记下面几点:

  1. 硬件没有减法,只有加法
  2. 硬件没有负数,如果是减法则用2的补码进行加法运算
  3. carry位只有一个含义即加法运算最高位是否会溢出

因此

  1. 对于加法指令ADC,相对简单,只用比较结果是否超过255(UByte/U8的最大值)即可。
  2. 对于减法指令SBC,4-3其实是4+0b1111_1101会溢出,而减法中的溢出表示的是没有borrow。而3-4其实是3+0b1111_1100,蛤不会溢出。所以减法计算是否减一的判断依据是C位是否为0,若为0则减一,反之不减。所以和加法相同,只用比较结果是否超过255即可。需要注意的是:Carry in的减一至少是可以用补码的加一抵消的,不需要对这个1用补码计算!

计算Overflow位

对于Overflow位的计算在第一篇参考的文章中通过枚举给出了计算公式,但是这个计算公式是针对加法的。公式如下:

  1. 加法的两个操作数符号相同
  2. 结果和任一操作数的符号不同

但是对于减法:

  1. 减法的两个操作数符号不同。但是计算结果时要将减数按位取反末尾加一(如果C位为0,正好抵消)
  2. 结果和第一个操作数不同

:无论是加法还是减法考虑第一点的时候我们不用考虑C位(即使第一点只是一个必要条件),C位的影响已经体现在2中了(加上第二点就是充要条件了)。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private fun adc(addressMode: AddressMode) {
    val addr = getOperandAddress(addressMode)
    val value = bus.readUByte(addr)
    val sum = a + value + if (PStatus.getStatus(Flag.CARRY)) 1u else 0u
    status.apply {
        setStatus(Flag.CARRY, sum > UByte.MAX_VALUE)
        setStatus(
            Flag.OVERFLOW,
            (value.toUInt() xor sum)
                    and (a.toUInt() xor sum) and 0b1000_0000u != 0u
        )
    }
    a = sum.toUByte()
    PStatus.updateZN(a)
}

private fun sbc(addressMode: AddressMode) {
    val addr = getOperandAddress(addressMode)
    val value = bus.readUByte(addr)
    val diff = a + (value xor 0xFFu) + if (PStatus.getStatus(Flag.CARRY)) 1u else 0u
    status.apply {
        setStatus(Flag.CARRY, diff > UByte.MAX_VALUE)
        setStatus(
                Flag.OVERFLOW,
                ((a xor value) and 0x80u.toUByte())
                    and ((a xor diff.toUByte()) and 0x80u.toUByte()) != 0u.toUByte()
        )
    }
    a = diff.toUByte()
    PStatus.updateZN(a)
}

参考