6502 Emulation Proper Way to Implement ADC and SBC

(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).


The following is the snippet of code of the 6502 core of my C64, VIC 20 and Atari 2600 emulators (http://www.z64k.com) which implements decimal mode and passes all of Lorenzes, bclark and asap tests. Let me know if you need me to explain any of it. I also have older code that still passes all the test programs but splits the instructions and the decimal modes of the instructions into separate classes. It might be simpler to understand my older code if you prefer to decipher that. All my emulators use the attached code to implement the ADC, SBC and ARR(Full Code of ARR not included) instructions.

public ALU ADC=new ALU(9,1,-1);
public ALU SBC=new ALU(15,-1,0);
public ALU ARR=new ALU(5,1,-1){
    protected void setSB(){AC.ror.execute();SB=AC.value;}
    protected void fixlo(){SB=(SB&0xf0)|((SB+c0)&0x0f);}
    protected void setVC(){V.set(((AC.value^(SB>>1))&0x20)==0x20);C.set((SB&0x40)==0x40);if((P&8)==8){Dhi(hb);}}
};
public class ALU{
protected final int base,s,m,c0,c1,c2;
protected int lb,hb;
public ALU(int base,int s,int m){this.base=base;this.s=s;this.m=m;c0=6*s;c1=0x10*s;c2=c0<<4;}
public void execute(int c){// c= P&1 for ADC and ARR, c=(~P)&1 for SBC, P=status register
    lb=(AC.value&0x0f)+(((DL.value&0x0f)+c)*s);
    hb=(AC.value&0xf0)+((DL.value&0xf0)*s);
    setSB();
    if(((P&8)==8)&&(lb&0x1f)>base){fixlo();}//((P&8)==8)=Decimal mode
    N.set((SB&0x80)==0x80);
    setVC();
    AC.value=SB&0xff;
}
protected void setSB(){SB=hb+lb;Z.set((SB&0xff)==0);}
protected void fixlo(){SB=(hb+c1)|((SB+c0)&0x0f);}
protected void Dhi(int a){if((a&0x1f0)>base<<4){SB+=c2;C.set(s==1);}}
protected void setVC(){V.set(((AC.value^SB)&(AC.value^DL.value^m)&0x80)==0x80);C.set(SB>=(0x100&m));if((P&8)==8){Dhi(SB);}}

}


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;