mirror of
https://github.com/pevans/erc-c.git
synced 2025-02-25 14:29:13 +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).
|
* (excepting mos6502.addr.c).
|
||||||
*/
|
*/
|
||||||
DECL_INST(adc);
|
DECL_INST(adc);
|
||||||
|
DECL_INST(adc_dec);
|
||||||
DECL_INST(and);
|
DECL_INST(and);
|
||||||
DECL_INST(asl);
|
DECL_INST(asl);
|
||||||
DECL_INST(bad);
|
DECL_INST(bad);
|
||||||
@ -227,6 +228,7 @@ DECL_INST(ror);
|
|||||||
DECL_INST(rti);
|
DECL_INST(rti);
|
||||||
DECL_INST(rts);
|
DECL_INST(rts);
|
||||||
DECL_INST(sbc);
|
DECL_INST(sbc);
|
||||||
|
DECL_INST(sbc_dec);
|
||||||
DECL_INST(sec);
|
DECL_INST(sec);
|
||||||
DECL_INST(sed);
|
DECL_INST(sed);
|
||||||
DECL_INST(sei);
|
DECL_INST(sei);
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
DEFINE_INST(adc)
|
DEFINE_INST(adc)
|
||||||
{
|
{
|
||||||
|
if (cpu->P & MOS_DECIMAL) {
|
||||||
|
mos6502_handle_adc_dec(cpu, oper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MOS_CARRY_BIT();
|
MOS_CARRY_BIT();
|
||||||
SET_RESULT(cpu->A + oper + carry);
|
SET_RESULT(cpu->A + oper + carry);
|
||||||
|
|
||||||
@ -23,6 +28,58 @@ DEFINE_INST(adc)
|
|||||||
cpu->A = result & 0xff;
|
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
|
* The cmp instruction will consider the difference of the accumulator
|
||||||
* minus the operand. It will then set the zero, negative, or carry bits
|
* minus the operand. It will then set the zero, negative, or carry bits
|
||||||
@ -145,9 +202,74 @@ DEFINE_INST(iny)
|
|||||||
*/
|
*/
|
||||||
DEFINE_INST(sbc)
|
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();
|
MOS_CARRY_BIT();
|
||||||
SET_RESULT(cpu->A - oper - carry);
|
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;
|
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);
|
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)
|
Test(mos6502_arith, cmp)
|
||||||
{
|
{
|
||||||
cpu->A = 5;
|
cpu->A = 5;
|
||||||
@ -123,3 +136,16 @@ Test(mos6502_arith, sbc)
|
|||||||
mos6502_handle_sbc(cpu, 8);
|
mos6502_handle_sbc(cpu, 8);
|
||||||
cr_assert_eq(cpu->A, 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…
x
Reference in New Issue
Block a user