Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 414
NAME__ Avatar asked Mar 22 '15 10:03

NAME__


2 Answers

(I forgot about Decimal mode -- which the NES's 6502 lacks -- when writing the answer below. I'll leave it anyway as it may be useful to people writing NES emulators.)

SBC is easy to implement once your emulator has ADC. All you need to do is to invert the bits of the argument and pass that to the ADC implementation. To get an intuitive idea of why this works, note that inverting all the bits of arg produces -arg - 1 in two's complement, and work through what happens when the carry flag is and isn't set.

Here's the complete source for SBC in my emulator. All flags will be set correctly too.

static void sbc(uint8_t arg) { adc(~arg); /* -arg - 1 */ }

The trickiest part when implementing ADC is the calculation of the overflow flag. The condition for it to get set is that the result has the "wrong" sign. Due to how the ranges work out, it turns out that this can only happen in two circumstances:

  1. Two positive numbers are added, and the result is a negative number.
  2. Two negative numbers are added, and the result is a positive number.

(1) and (2) can be simplified into the following condition:

  • Two numbers that have the same sign are added, and the result has a different sign.

With some XOR trickery, this allows the overflow flag to be set as in the following code (the complete ADC implementation from my emulator):

static void adc(uint8_t arg) {
    unsigned const sum = a + arg + carry;
    carry = sum > 0xFF;
    // The overflow flag is set when the sign of the addends is the same and
    // differs from the sign of the sum
    overflow = ~(a ^ arg) & (a ^ sum) & 0x80;
    zn = a /* (uint8_t) */ = sum;
}

(a ^ arg) gives 0x80 in the sign bit position if the a register and arg differ in sign. ~ flips the bits so that you get 0x80 if a and arg have the same sign. In plainer English, the condition can be written

overflow = <'a' and 'arg' have the same sign> &  
           <the sign of 'a' and 'sum' differs> &  
           <extract sign bit>

The ADC implementation (as well as many other instructions) also uses a trick to store the zero and negative flags together.

My CPU implementation (from an NES emulator) can be found here by the way. Searching for "Core instruction logic" will give you simple implementations for all instructions (including unofficial instructions).

I've run it through plenty of test ROMs without failures (one of the upsides of NES emulation is that there's plenty of great test ROMs available), and I think it should be pretty much bug free at this point (save for some extremely obscure stuff involving e.g. open bus values in some circumstances).

like image 87
Ulfalizer Avatar answered Sep 22 '22 16:09

Ulfalizer


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;
like image 23
Eight-Bit Guru Avatar answered Sep 23 '22 16:09

Eight-Bit Guru