因为我对现有的“这是一些代码,你自己解决吧”的回答并不特别满意,所以回答有些晚了。
二进制模式的ADC和SBC很直接。对于ADC:
1. 计算A + 操作数 + 进位标志;
2. 根据结果设置负数和零标志;
3. 如果产生了不可能的符号结果(例如,两个正数相加得到负数,或者两个负数相加得到正数),设置溢出标志。虽然都没有逻辑意义,但由于有限的范围,这两种情况都是可能的;
4. 如果第7位有进位,设置进位标志。最简单的建议是以16位形式进行算术运算,并在最后检查第8位是否有内容。
对于SBC,只需在执行与上述相同的操作之前对操作数进行补码。
十进制模式的SBC更容易解释。
不需要解释的东西:
1. 计算二进制结果。这是SBC,所以是A + ~operand + carry。
2. 根据二进制结果设置所有标志位。
二进制半字节可以容纳的值的范围是0-15;BCD中使用的值的范围是0-9。BCD值始终为无符号。因此,你是从一个正数中减去另一个正数。
如果A中的值大于或等于被减数,你将得到所需的结果,例如0x8 - 0x2 = 0x6,就像8 - 2 = 6一样。
只有当A中的值小于被减数时,你才会遇到困难,例如0x2 - 0x8 = 0xA(限制在半字节范围内)。
这意味着只有在生成借位时,半字节才需要纠正为BCD,也就是说,当没有进位时,即A + ~operand。
所以,在普通的6502上:
3. 如果进位到第4位,从低四位减去6(或者如果你喜欢的话,加上10),不允许进一步的进位传播*;
4. 如果从第7位进位,再减去0x60,不会进一步影响进位标志。
* 因为当你在原始的8位加法中小于0时,进位已经传播了。所以你会允许相同的进位传播两次。
65C02稍微改变了一些东西,实际上,如果需要修复低四位,它会添加8位值
0xfa
。对于有效的BCD数字来说,这与添加
0xa
是相同的,因为
0xf
的高四位会取消你在低四位加上
0xa
时应该得到的额外进位。对于无效的原始BCD值来说,情况并不完全相同,因此65C02的
SBC
将产生不同的最终结果。
十进制模式下的ADC与之类似,但用于检测需要修正的数字的测试略有不同,并且标志位的设置时间也不同。
如果满足以下条件之一,则需要进行修正:
- 最后一个半字节不是有效的BCD值,例如0x5 + 0x5 = 0xA;
- 半字节有进位,例如0x9 + 0x9 = 0x2,带进位。
单独使用任何一个测试都不足以满足要求,正如示例所示。通过将结果加上6来完成修正。
除此之外,ADC与SBC的另一个不同之处在于,在修正低半字节但修正高半字节之前,它基于中间结果设置N和V标志位。
补充说明:
65C02与原始的6502不同之处在于,在操作的最后设置
N
和
Z
,以便它们反映最终的BCD结果。这会额外消耗一个周期。
为了解释已经发布在这里的其他代码:
(a ^ result) & (result ^ operand) & 0x80
是溢出,如本文所述;只有在结果的符号与原始累加器和操作数的符号不同时,它才为非零,这间接意味着累加器和操作数具有相同的符号。
(a ^ operand ^ result) & 0x10
是对第4位进位的测试,因为它在没有进位的情况下计算了该位的原始结果:
- 如果原始输入为0和0,或者1和1,则期望输出为0;
- 否则期望输出为1。
...然后检查这是否与实际的最终结果不同。只有在该位位置也有进位时,它才会不同。
我实际使用的十进制模式代码,考虑到被引用的Numeric::
模板所做的事情与名称所说的一样,而且我只是存储应该被评估为N或Z的结果,假设有人希望它们作为一种可忽略的惰性评估形式:
SBC
:
operand_ = ~operand_;
uint8_t result = a_ + operand_ + flags_.carry;
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
flags_.negative_result = result;
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
if(!Numeric::carried_in<4>(a_, operand_, result)) {
if constexpr (is_65c02(personality)) {
result += 0xfa;
} else {
result = (result & 0xf0) | ((result + 0xfa) & 0xf);
}
}
if(!flags_.carry) {
result += 0xa0;
}
a_ = result;
ADC
:
uint8_t result = a_ + operand_ + flags_.carry;
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
if(Numeric::carried_in<4>(a_, operand_, result)) {
result = (result & 0xf0) | ((result + 0x06) & 0x0f);
} else if((result & 0xf) > 0x9) {
flags_.carry |= result >= 0x100 - 0x6;
result += 0x06;
}
flags_.negative_result = result;
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
flags_.carry |= result >= 0xa0;
if(flags_.carry) {
result += 0x60;
}
a_ = result;