mirror of
https://github.com/pevans/erc-c.git
synced 2024-11-27 20:51:17 +00:00
Add support for decimal mode ADC/SBC.
This also corrects a bug where SBC set carry incorrectly in binary mode.
This commit is contained in:
parent
2669460c6d
commit
0d1e22a348
@ -175,6 +175,7 @@ DECL_ADDR_MODE(zpy);
|
||||
* (excepting mos6502.addr.c).
|
||||
*/
|
||||
DECL_INST(adc);
|
||||
DECL_INST(adc_dec);
|
||||
DECL_INST(and);
|
||||
DECL_INST(asl);
|
||||
DECL_INST(bad);
|
||||
@ -227,6 +228,7 @@ DECL_INST(ror);
|
||||
DECL_INST(rti);
|
||||
DECL_INST(rts);
|
||||
DECL_INST(sbc);
|
||||
DECL_INST(sbc_dec);
|
||||
DECL_INST(sec);
|
||||
DECL_INST(sed);
|
||||
DECL_INST(sei);
|
||||
|
@ -15,6 +15,11 @@
|
||||
*/
|
||||
DEFINE_INST(adc)
|
||||
{
|
||||
if (cpu->P & MOS_DECIMAL) {
|
||||
mos6502_handle_adc_dec(cpu, oper);
|
||||
return;
|
||||
}
|
||||
|
||||
MOS_CARRY_BIT();
|
||||
SET_RESULT(cpu->A + oper + carry);
|
||||
|
||||
@ -23,6 +28,58 @@ DEFINE_INST(adc)
|
||||
cpu->A = result & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a number to the accumulator using Binary-Coded Decimal, or BCD.
|
||||
* Some things still work the same--for instance, if carry is high, we
|
||||
* will still add one to A+M. The V flag doesn't make any sense in BCD,
|
||||
* and apparently ADC's effects on V in decimal mode is "undocumented"?
|
||||
* The N flag is also a bit odd, and the general wisdom seems to be that
|
||||
* you should use multiple bytes if you want to represent a negative,
|
||||
* and just use the sign bit on the MSB.
|
||||
*/
|
||||
DEFINE_INST(adc_dec)
|
||||
{
|
||||
MOS_CARRY_BIT();
|
||||
|
||||
// Determine the most and least siginificant digits of A and oper.
|
||||
int a_msd = cpu->A >> 4,
|
||||
a_lsd = cpu->A & 0xf,
|
||||
o_msd = oper >> 4,
|
||||
o_lsd = oper & 0xf;
|
||||
|
||||
// If any of these are greater than 9, then they are invalid BCD
|
||||
// values, and we give up.
|
||||
if (a_msd > 9 || a_lsd > 9 || o_msd > 9 || o_lsd > 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sum is built using the decimal senses of the msd/lsd variables;
|
||||
// carry is also a factor.
|
||||
int sum =
|
||||
((a_msd * 10) + a_lsd) +
|
||||
((o_msd * 10) + o_lsd) + carry;
|
||||
|
||||
// But ultimately, one byte can only hold $00 - $99
|
||||
int modsum = sum % 100;
|
||||
|
||||
// And the final result has to be ported back into a "hexadecimal"
|
||||
// number; you see, BCD values are not just literally decimal, they
|
||||
// are decimal in hexadecimal form.
|
||||
SET_RESULT(((modsum / 10) << 4) | (modsum % 10));
|
||||
|
||||
// Because the rules for carry is a bit different, we'll handle it
|
||||
// here.
|
||||
cpu->P &= ~MOS_CARRY;
|
||||
if (sum > 100) {
|
||||
cpu->P |= MOS_CARRY;
|
||||
}
|
||||
|
||||
// We'll check zero, but not negative or overflow.
|
||||
mos6502_modify_status(cpu, MOS_ZERO, cpu->A, sum);
|
||||
|
||||
cpu->A = result;
|
||||
}
|
||||
|
||||
/*
|
||||
* The cmp instruction will consider the difference of the accumulator
|
||||
* minus the operand. It will then set the zero, negative, or carry bits
|
||||
@ -145,9 +202,74 @@ DEFINE_INST(iny)
|
||||
*/
|
||||
DEFINE_INST(sbc)
|
||||
{
|
||||
// Jump into the binary coded decimal world with us! It's... funky!
|
||||
if (cpu->P & MOS_DECIMAL) {
|
||||
mos6502_handle_sbc_dec(cpu, oper);
|
||||
return;
|
||||
}
|
||||
|
||||
MOS_CARRY_BIT();
|
||||
SET_RESULT(cpu->A - oper - carry);
|
||||
|
||||
mos6502_modify_status(cpu, MOS_NVZC, cpu->A, result);
|
||||
// Carry is handled slightly differently in SBC; it's set if the
|
||||
// value is non-negative, and unset if negative. (It's essentially a
|
||||
// mirror of the N flag in that sense.)
|
||||
cpu->P |= MOS_CARRY;
|
||||
if (result >= 0x80) {
|
||||
cpu->P &= ~MOS_CARRY;
|
||||
}
|
||||
|
||||
mos6502_modify_status(cpu, MOS_NVZ, cpu->A, result);
|
||||
cpu->A = result & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pretty similar to the code we're doing in adc_dec; we are here
|
||||
* performing a subtraction in binary coded decimal.
|
||||
*
|
||||
* Note: a lot of this code is lifted from adc_dec; it's probably the
|
||||
* only other time we will need to use this specific code, so I'm doing
|
||||
* the Martin Fowler thing and waiting for a third occasion to arise
|
||||
* before refactoring this into its own function.
|
||||
*/
|
||||
DEFINE_INST(sbc_dec)
|
||||
{
|
||||
MOS_CARRY_BIT();
|
||||
|
||||
// Determine the most and least siginificant digits of A and oper.
|
||||
int a_msd = cpu->A >> 4,
|
||||
a_lsd = cpu->A & 0xf,
|
||||
o_msd = oper >> 4,
|
||||
o_lsd = oper & 0xf;
|
||||
|
||||
// If any of these are greater than 9, then they are invalid BCD
|
||||
// values, and we give up.
|
||||
if (a_msd > 9 || a_lsd > 9 || o_msd > 9 || o_lsd > 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sum is built using the decimal senses of the msd/lsd variables;
|
||||
// carry is also a factor.
|
||||
int diff =
|
||||
((a_msd * 10) + a_lsd) -
|
||||
((o_msd * 10) + o_lsd) - carry;
|
||||
|
||||
// Force C to high to begin with
|
||||
cpu->P |= MOS_CARRY;
|
||||
|
||||
// If diff is negative, we need to "overflow" it back to a
|
||||
// positive number by adding 100. We also need to unset the C flag.
|
||||
if (diff < 0) {
|
||||
diff += 100;
|
||||
cpu->P &= ~MOS_CARRY;
|
||||
}
|
||||
|
||||
// And the final result has to be ported back into a "hexadecimal"
|
||||
// number; you see, BCD values are not just literally decimal, they
|
||||
// are decimal in hexadecimal form.
|
||||
SET_RESULT(((diff / 10) << 4) | (diff % 10));
|
||||
|
||||
mos6502_modify_status(cpu, MOS_ZERO, cpu->A, result);
|
||||
|
||||
cpu->A = result;
|
||||
}
|
||||
|
@ -17,6 +17,19 @@ Test(mos6502_arith, adc)
|
||||
cr_assert_eq(cpu->A, 73);
|
||||
}
|
||||
|
||||
Test(mos6502_arith, adc_dec)
|
||||
{
|
||||
cpu->P &= ~MOS_CARRY;
|
||||
cpu->A = 0x05;
|
||||
mos6502_handle_adc_dec(cpu, 0x10);
|
||||
cr_assert_eq(cpu->A, 0x15);
|
||||
|
||||
// Test that A + M + 1 works for carry
|
||||
cpu->P |= MOS_CARRY;
|
||||
mos6502_handle_adc_dec(cpu, 0x13);
|
||||
cr_assert_eq(cpu->A, 0x29);
|
||||
}
|
||||
|
||||
Test(mos6502_arith, cmp)
|
||||
{
|
||||
cpu->A = 5;
|
||||
@ -123,3 +136,16 @@ Test(mos6502_arith, sbc)
|
||||
mos6502_handle_sbc(cpu, 8);
|
||||
cr_assert_eq(cpu->A, 8);
|
||||
}
|
||||
|
||||
Test(mos6502_arith, sbc_dec)
|
||||
{
|
||||
cpu->P = 0;
|
||||
cpu->A = 0x15;
|
||||
mos6502_handle_sbc_dec(cpu, 0x6);
|
||||
cr_assert_eq(cpu->A, 0x9);
|
||||
|
||||
cpu->P |= MOS_CARRY;
|
||||
cpu->A = 0x12;
|
||||
mos6502_handle_sbc_dec(cpu, 0x2);
|
||||
cr_assert_eq(cpu->A, 0x9);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user