Closed bonimy closed 6 years ago
I know the decimal mode operations are wrong, but nothing uses them, so I didn’t feel much obligation to fix them. See the tests at https://snescentral.com/article.php?id=1118 If you want, try to get those tests passing. The expected behavior is documented therein as well. The example you posted is 32 bit, which disregarding undocumented behavior wouldn’t work anyway. I assume you adapted it to 16 before trying it?
I didn't know there were tests. Awesome!
Hey @bearoso do you know the validity of those BCD tests? It seems he just tests against this addition function:
const int n80 = 0x80;
const int v40 = 0x40;
const int d08 = 0x08;
const int z02 = 0x02;
const int c01 = 0x01;
int adc16( int a, ref int p, int operand )
{
int carry = p & c01;
p &= ~(n80 | v40 | z02 | c01);
int result;
if ((p & d08) == 0)
{
result = a + operand + carry;
}
else
{
result = (a & 0x000F) + (operand & 0x000F) + carry;
if (result > 0x0009)
result += 0x0006;
carry = (result > 0x000F) ? 1 : 0;
result = (a & 0x00F0) + (operand & 0x00F0) + (result & 0x000F) + carry * 0x10;
if (result > 0x009F)
result += 0x0060;
carry = (result > 0x00FF) ? 1 : 0;
result = (a & 0x0F00) + (operand & 0x0F00) + (result & 0x00FF) + carry * 0x100;
if (result > 0x09FF)
result += 0x0600;
carry = (result > 0x0FFF) ? 1 : 0;
result = (a & 0xF000) + (operand & 0xF000) + (result & 0x0FFF) + carry * 0x1000;
}
// signs of a and operand match, and sign of result doesn't
if ((a & 0x8000) == (operand & 0x8000) && (a & 0x8000) != (result & 0x8000))
p |= v40;
if ((p & d08) != 0 && result > 0x9FFF)
result += 0x6000;
if (result > 0xFFFF)
p |= c01;
if ((result & 0x8000) != 0)
p |= n80;
if ((result & 0xFFFF) == 0)
p |= z02;
return result & 0xFFFF;
}
However, this comes back to the original question of how do we know this is accurate? I wasn't able to find any technical references that say why they are right.
Either way, if you did want snes9x to pass the ADC and SBC tests, this would be the implementation to do it.
That’s the reference implementation. The expected results come from hardware, so the behavior is correct.
Ah alright. I'll make a PR when I have some time.
I changed the Snes9x version to the reference version to pass the tests. Your optimized version wouldn't work because we can't assume the inputs are proper BCD values.
Yeah I think we'll have to use the C code the tests use. They're pretty ugly-looking but they're accurate.
Since we're passing the tests now, despite the convoluted code to accurately reflect the hardware, I'm going to close this.
Background
Addition operations in SNES's 65816 processor can work in a base-10 binary coded decimal (BCD) mode, where addition operations give results like
Question
What is the expected outcome if we were to add invalid numbers in decimal mode (i.e. numbers with digits not between 0 and 9)? A good example is
0xFFFF + 0xFFFF
.Steps to reproduce
The
0xFFFF + 0xFFFF
operation can be triggered in snes9x withwhose machine code equivalent is
C2 38 38 A9 FF FF 69 FF FF
and theA
register will have the result.Current implementation
The current snes9x code for BCD addition is located at cpumacro.h:318:
This code is a little messy and can be improved.
Proposed change
I wanted to propose the following change:
where the function
bcd_add(int, int)
does the actual BCD addition. Note that we have to do a second separate addition for the carry bit in the event thatRegisters.A
ends with a 9.The BCD add function I wanted to use (stolen from this article) is
This has the benefit of using less operations and having no cyclomatic complexity.
Issue
While this produces the expected output for valid BCD integers, the two implementations diverge for invalid values. For example, assuming the carry bit is set, then
0xFFFF + 0xFFFF
will be0x5555
using the snes9x code and0x6665
using the implementation I found.So the question then becomes which one would the SNES actually do in this scenario?