javascriptemulation6502

6502 Emulation Proper Way to Implement ADC and SBC


I've been working on an emulator for the MOS 6502, but I just can't seem to get ADC and SBC working right. I'm testing my emulator with the AllSuiteA program loaded at 0x4000 in emulated memory, and for test09, my current ADC and SBC implementations just aren't getting the right flags. I've tried changing their algorithms countless times, but every time, the carry flag and overflow flag are just enough off to matter, and cause the test to branch/not branch.

Both of my functions are based off this.

memory[0x10000] is the Accumulator. It's stored outside of the memory range so I can have a separate addressing switch statement.

This is one of my implementations of these functions:

case "ADC":
    var t = memory[0x10000] + memory[address] + getFlag(flag_carry);
    (memory[0x10000] & 0x80) != (t & 0x80) ? setFlag(flag_overflow) : clearFlag(flag_overflow);
    signCalc(memory[0x10000]);
    zeroCalc(t);
    
    t > 255 ? setFlag(flag_carry) : clearFlag(flag_carry);
    
    memory[0x10000] = t & 0xFF;
break;

case "SBC":
    var t = memory[0x10000] - memory[address] - (!getFlag(flag_carry));
    (t > 127 || t < -128) ? setFlag(flag_overflow) : clearFlag(flag_overflow);
    
    t >= 0 ? setFlag(flag_carry) : clearFlag(flag_carry);
    signCalc(t);
    zeroCalc(t);
    
    memory[0x10000] = t & 0xFF;
break;

I'm all out of ideas at this point, but I did also run into the same problem with the data offered here. So it isn't just one implementation plan failing me here.


Solution

  • Welcome, brave adventurer, to the arcane halls of the 6502 'add' and 'subtract' commands! Many have walked these steps ahead of you, though few have completed the gamut of trials that await you. Stout heart!

    OK, dramatics over. In a nutshell, ADC and SBC are pretty-much the toughest 6502 instructions to emulate, mainly because they're incredibly complex and sophisticated little nuggets of logic. They handle carry, overflow, and decimal mode, and of course actually rely on what could be thought of as 'hidden' pseudo-register working storage.

    Making things worse is that a lot has been written about these instructions, and a good percentage of the literature out there is wrong. I tackled this in 2008, spending many hours researching and separating the wheat from the chaff. The result is some C# code I reproduce here:

    case 105: // ADC Immediate
    _memTemp = _mem[++_PC.Contents];
    _TR.Contents = _AC.Contents + _memTemp + _SR[_BIT0_SR_CARRY];
    if (_SR[_BIT3_SR_DECIMAL] == 1)
    {
      if (((_AC.Contents ^ _memTemp ^ _TR.Contents) & 0x10) == 0x10)
      {
        _TR.Contents += 0x06;
      }
      if ((_TR.Contents & 0xf0) > 0x90)
      {
        _TR.Contents += 0x60;
      }
    }
    _SR[_BIT6_SR_OVERFLOW] = ((_AC.Contents ^ _TR.Contents) & (_memTemp ^ _TR.Contents) & 0x80) == 0x80 ? 1 : 0;
    _SR[_BIT0_SR_CARRY] = (_TR.Contents & 0x100) == 0x100 ? 1 : 0;
    _SR[_BIT1_SR_ZERO] = _TR.Contents == 0 ? 1 : 0;
    _SR[_BIT7_SR_NEGATIVE] = _TR[_BIT7_SR_NEGATIVE];
    _AC.Contents = _TR.Contents & 0xff;
    break;