Understanding overflow flag for ADC on the 6502

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Understanding overflow flag for ADC on the 6502

Post by Petruza »

obelisk.demon.co.uk/6502 says 'Overflow Flag Set if sign bit is incorrect' which is vague and I really don't see clearly what means that 'sign bit is incorrect'.

The 6502 documentation by _Bnu, in the other hand, is very specific, it says that the overflow flag after an ADC without decimal mode is this:

Code: Select all

(!((AC ^ src) & 0x80) && ((AC ^ temp) & 0x80))
(AC is accumulator, src is a value from memory, temp is AC + src + carry, and 0x80 is bit 7 set )

Ok, I understand bitwise operations, but I don't get the logic behind this expression.

So the ultimate question is, conceptually, when should the overflow flag be set for ADC and when should it be unset? (what is a correct sign?)

I could just paste that expression in my code and it may work perfectly, but I'd like to understand what my code is doing.

PS: the carry flag is set when the addition overflows. So the overflow flag doesn't actually mean overflow, it means something else.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

Carry indicates unsigned overflow
Overflow indicates signed overflow.

When you figure that $81 can be -127 and $FF can be -1..

When adding two unsigned numbers results in > $FF, C is set
When adding two signed numbers results in > 127 ($7F) or < -128 ($80), V is set


In emulation, this can be easily checked by looking at the high bits. Basically:

Overflow is set if:
Positive + Positive = Negative
or
Negative + Negative = Positive

Overflow is cleared in all other instances.

Here's example code:

Code: Select all

// A = Accumulator before the addition
// v = the value adding to the accumulator
// s = the sum of the addition (A+v+C)

if( (A ^ s) & (v ^ s) & 0x80 )
  Set_V();
else
  Clear_V();
EDIT:

err... duh, you already posted example code.

Anyway as for how the code works...

Pos+Pos=Neg and Neg+Neg=Pos

Both conditions have the "sum" as the odd one out. So if the sign of the sum matches either the sign of A or the sign of v, then you don't overflow.

So in my example code:

Code: Select all

if( (A ^ s) & (v ^ s) & 0x80 )
All we're really interested in is the high bit... so ignore all the other bits in A,v,s.

A^s will have the high bit set if the signs mismatch
ditto for v^s

so... (A^s) & (v^s) ... the AND operation check to make sure both of those conditions had the high bit set (ie: s high bit didn't match A or v). If the AND results in the high bit set, the we know that we have overflow

The following & 0x80 just extracts the high bit.
Last edited by Disch on Tue Apr 27, 2010 8:22 pm, edited 1 time in total.
User avatar
tokumaru
Posts: 12476
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

As I understand it, the carry flag can detect overflows and underflows of unsigned numbers, while the V flag is used for signed numbers.

When you add 1 to 127, the result is 128, which is correct if you treat the numbers as unsigned, but in signed numbers 128 is actually -128, so that's an overflow. Same goes for underflows: if you subtract 1 from -128 the result is -129, which doesn't fit in a byte. Crossing between 0 and 255 (-1) should be OK though, if the numbers are signed.

IIRC, there is a simple rule for setting the V flag, but I can't remember what it is (I program games, not emulators!) so I'll let someone else give it to you.

EDIT: Too late! =)
User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

Thanks! great answers!
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

This has been discussed many times before, with lots of useful explanations and code. I highly recommend you seek out these previous discussions.
User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

I actually made some calculations and both algorithms throw different results.
Which one is right? ( Disch's or _Bnu's )

Image

Reference:
A: Accumulator
M: Value from memory
C: Carry
ADC: Result of A+M+C
ADC8: ADC truncated to 8 bits
V: Overflow flag from Disch's algorithm
V2: Overflow flag from _Bnu's algorithm
+: sign bit 7 is set
-: sign bit 7 is reset

Algorithms used: ( if I coded them wrong, sorry! )

Disch's:

Code: Select all

$v = (int) (bool) ( ( $a ^ $adc ) & ( $m ^ $adc ) & 0x80 );
_Bnu's:

Code: Select all

(int) (bool) ( ( ( $a ^ $m ) & 0x80 ) && ( ( $a ^ $adc ) & 0x80 ) );

blargg wrote:This has been discussed many times before, with lots of useful explanations and code. I highly recommend you seek out these previous discussions.
I'm sorry, you're right. But either I suck at forum searching or some posters suck at writing meaningful thread titles. I suspect a little of both ;D


Edit: Disch: I see that you already posted the same algorith, so it's most likely the right one. I'll go with that.
ReaperSMS
Posts: 174
Joined: Sun Sep 19, 2004 11:07 pm

Post by ReaperSMS »

Your table calculations are wrong, as you forgot the ! in Bnu's.
Disch's, in english, is (overflow if the sign of the result does not match the sign of either input), Bnu's is (overflow if the sign of the inputs are the same, and do not match the sign of the result).

for the case of FF+00+0, result is 0, FF. A & 80 = 1, M & 80 = 0, R & 80 = 1. !(A^M) & (A^R) & 80 = !1 & 0 = 0 & 0 = 0
A^R & M^R & 80 = 0 & 1 = 0
FF+00+1: A=1, M=0, R=0
!(A^M) & A^R = !1 & 0 = 0
A^R & M^R = 1^0 & 0^0 = 1 & 0 = 0

etc.
User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

Oh, I didn't see that bastard `!`
Thanks!
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Just be glad the NES doesn't have a decimal mode. The flag setting for that differs between the 6502, 65C02, and 65816. It's a nightmare.
Hamburgler
Posts: 36
Joined: Wed Jul 04, 2007 8:40 am

Post by Hamburgler »

To stir the pot more: Which one is faster? :)
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Post by koitsu »

Thing to note: _Bnu did not write that document, he simply corrected all the spelling errors. So if there's mistakes in the technical aspects, don't hold him responsible. :-)
User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

blargg wrote:Just be glad the NES doesn't have a decimal mode. The flag setting for that differs between the 6502, 65C02, and 65816. It's a nightmare.
Halleluya!
koitsu wrote:Thing to note: _Bnu did not write that document, he simply corrected all the spelling errors. So if there's mistakes in the technical aspects, don't hold him responsible. :-)
And who wrote that document? there has to be someone I can sue for that!
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Hamburgler wrote:To stir the pot more: Which one is faster? :)
To counter that stirring, ADC and SBC are not instructions that are used very often.
User avatar
Petruza
Posts: 308
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

I wouldn't care too much about the speed of a few bitwise operations, I'll surely have worst bottlenecks in other parts of the emulator.
User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

i guess the clearest way to set or reset the V flag in ADC and SBC instructiones is by testing if the result is major than 0x7f. it works in both instructions.

ADC Immediate:

Code: Select all

auxbyte = ACC;
ACC += (Mem[++PC] + C_FLAG);
if(auxbyte + Mem[PC] + C_FLAG > 127) V_FLAG = 1;
else V_FLAG = 0;
SBC Immediate:

Code: Select all

auxbyte = ACC;
ACC -= (Mem[++PC] - C_FLAG);
if(auxbyte - Mem[PC] - C_FLAG > 127) V_FLAG = 1;
else V_FLAG = 0;
of course this is not the faster way, but is the easiest way to the understand how V flag works
sorry about my english, i'm from argentina...

http://nestate.uuuq.com
Post Reply