6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Oct 06, 2024 8:34 pm

All times are UTC




Post new topic Reply to topic  [ 20 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: ADC and SBC confusion
PostPosted: Tue Jul 02, 2013 5:18 am 
Offline

Joined: Tue Jul 02, 2013 5:09 am
Posts: 2
Hey everyone,

I'm currently writing an NES emulator in C++ as a summer project. I'm working on the CPU core right now, so this seemed like to the best place to come with my questions.

I'm using this website for my opcodes. ADC and SBC are really confusing me, though. I'm using an unsigned char as my datatype for my accumulator and I'm having a hard time seeing how ADC works. So lets say the carry flag is set and I read a ADC operation. If the carry flag is already set, won't that mean I'll be doing:
Code:
accumulator = accumulator + memory value + 255


Won't that automatically just turn the carry bit back on because the result will obviously be bigger than eight bits? I'm having a hard time seeing how ADC allows a programmer to use numbers bigger than a byte like many documents say because of the size of the accumulator always being 8 bits.

For my second question, I'm wondering when signed values are used and when unsigned values are used. I've read tons of documentation that talks about both signed and unsigned values being used in ADC and SBC. How do I know if something is signed or unsigned? Or is everything signed?

Hopefully someone can clear this up for me :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 6:29 am 
Offline

Joined: Sat Jul 28, 2012 11:41 am
Posts: 442
Location: Wiesbaden, Germany
ADC is not complicated to understand at all. It allows you to add multiple byte numbers the same way as you learned adding multiple digit numbers in school.

You add numbers from right to left (low to high order). If the result is greater than 9 you carry the one to the next higher (left) digit.

ADC does the same - only the digit can now be 255. If the result is greater than 255 you carry a one to the next higher byte. Wether that is the next byte to the left or right depends on how numbers are represented as big or little endian.

SBC is an ADC with the memory operand complemented (turned into a negative number). Since you need a two's complement the meaning of the carry bit is reversed: borrow = no carry

In high level languages you know by its definiton wether a number is signed or not. In assembler (machine language) it depends on wether during an ADC or SBC of the highest byte the highest bit is part of the number (0 to 255) or the sign bit (-128 to 127). It makes no difference for the opcode itself, wether the number is signed or unsigned. However the overflow flag during ADC/SBC of the highest byte of a signed number replaces the carry flag to indicate wether the result is too big.

You may want to read:
http://en.wikipedia.org/wiki/Two's_complement
http://www.6502.org/tutorials/vflag.html

_________________
6502 sources on GitHub: https://github.com/Klaus2m5


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 7:26 am 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 674
What he said. Here's the same thing algorithmically:

Code:
Carry = 0 or 1
ADC:  .A = .A + mem + Carry
SBC:  .A = .A + (mem XOR 255) + Carry   ; 2's complement is fun!


Here's how to carry when adding 16-bit numbers via 8-bit addition. In normal longhand math, you add single digits and carry between them. But in adding bytes, consider that each 2-digit hex byte is "one" digit for that purpose.
Code:
$0280 + $03A0:

  $02 80
+ $03 A0
 -------
  $?? ??

Low bytes first:  80 + A0 = 120, or 20 & carry the 1

    1(carried)
  $02 80
+ $03 A0
 -------
  $?? 20

High bytes:  02 + 03 + 1(carried) = 06

    1(carried)
  $02 80
+ $03 A0
 -------
  $06 20

When adding 2 bytes, the maximum you can have is $FF + $FF = $1FE, so the carried value beyond 8 bits will never be larger than 1.

Just be thankful that in the NES you don't have to worry about supporting decimal mode. ;)

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 8:00 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8521
Location: Southern California
More examples:
Code:
C=0, A=$FE: ADC #1   gives $FF, and C remains clear because you did not exceed what you can represent in 8 bits.
C=1, A=$FE: ADC #0   gives $FF, and C  gets cleared because you did not exceed what you can represent in 8 bits.
C=1, A=$FE: ADC #1   gives $00; but since the answer is $100, the 1 goes to the C flag, so it is set.
C=0, A=$FE: ADC #$10 gives $0E; but since the answer is $10E, the 1 goes to the C flag, so it is set.

$FE above could be +254 or -2, and the answers are still correct. Whether it's signed or unsigned is just a matter of interpretation. The V flag tells if it got so positive or so negative that the sign became incorrect. For example $F0 plus $88 (both negative if you're doing signed numbers, so -$10 plus -$78), plus no carry, gives $78 (and C is set); but that's no longer negative, so the V flag is set to tell you the resulting bit 7 is not correct as a sign. But if you do 16-bit numbers, these would be $FFF0 plus $FF88. You still do the low byte first, getting $78; but the C flag is applied to the high bytes. $FF+$FF would normally give $FE, but the C flag brings it back up to $FF, and you have your answer, $FF78, which is -$88. (-$10 + -$78 = -$88.) (Hopefully I didn't make a dumb mistake here and confuse anyone.)

Quote:
Just be thankful that in the NES you don't have to worry about supporting decimal mode. ;)

Fortunately there's almost never any real need for decimal mode, and it tends to get left out when people design their own 6502 versions in HDL. The common argument for decimal is that you can't get the accuracy for the cents in hex because you can't represent $.01 exactly. Hexadecimal works just fine though if you use fixed-point. A dollar is represented as 100 (64h), not 1.00, and a cent is just 1. If you need tenths of cents, then represent the dollar as 1,000 (3E8h) instead. It is exact. This does not keep you from converting to a more-normal representation (like $28.39) when it's time for human-readable output. In the internal representation, $.99 times two is still $1.98, $2.49 + $14.40 is still $16.89, etc..

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 5:19 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8415
Location: Midwestern USA
GARTHWILSON wrote:
Hexadecimal works just fine though if you use fixed-point...In the internal representation, $.99 times two is still $1.98, $2.49 + $14.40 is still $16.89, etc...

How does it work out with .01 × .02?

The one potential problem I do see with using scaled, fixed-point hex is range. Disregarding money computations for a moment, there are several situations that come to mind where precision right of the decimal point needs to be greater than two places. For example, an Informix SQL date number requires four-place precision to maintain some semblance of time accuracy (±5 seconds). Julian date numbers need at least six places, and eight are required to achieve sub-second accuracy. So to handle these numbers one must scale up by at least 10^5, 10^8 if going for higher precision. If a reasonable integer size is to be maintained then at least 48 bit quantities will be required to adequately represent floating point numbers.

Given that, I have to question just how much performance will be gained with scaled, fixed-point hex vis a vis floating point. Much of the "expense" in floating point is in conversion between ASCII and internal representation, not in actual manipulation. Of course, using a format such as IEEE-754 or excess-128 will result in ASCII to binary conversion errors, which are solvable with BCD. BCD, of course, incurs performance penalties with all operations other than unsigned addition or subtraction. Use of lookup tables with a fixed-point methodology can help in reducing real-time computations, but only at the expense of a large storage array. There is no free lunch with this, I'm afraid.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 7:06 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8521
Location: Southern California
BigDumbDinosaur wrote:
GARTHWILSON wrote:
Hexadecimal works just fine though if you use fixed-point...In the internal representation, $.99 times two is still $1.98, $2.49 + $14.40 is still $16.89, etc...

How does it work out with .01 × .02?

I'll assume only one of those is a dollar amount, so you don't have dollars-squared. The representation of the other one is idependent. In any case, the programmer keeps track of how many places to move the decimal point over (like we learned to do on paper in 3rd grade) when it's time for human-readable output, or, in the case of scaled-integer and not merely fixed-point, multiplying or dividing by a non-round number, like there being 10,430 counts in a radian angle measure or 182 counts in a degree if the whole circle is 65,536 counts which works out really nice for trig.

That matter of the angles in particular is a situation where it doesn't matter if you treat the numbers as signed or unsigned, because for example 359° is the same as -1°.

Quote:
The one potential problem I do see with using scaled, fixed-point hex is range.

You could scale it in floating point too, still representing a cent by 1 instead of .01, to avoid the accuracy problem.

As for the big numbers (Julian dates etc.), adding bytes for more precision, Dartht33bagger, is easy in integer work, using the C flag. (I had to get back to the topic! :lol: ) The real-time clock (RTC) on my workbench computer is run in software, on interrupts (NMI), and I keep a 32-bit count of centiseconds, giving me 10ms resolution with a count that rolls over in a little over 16 months which is no problem since I've never needed it powered for more that a couple of weeks continuously. If I wanted it to go thousands of years, I would have to use 48 bits. (There's also another set of bytes that I make roll over at 100 for centiseconds, 60 for seconds and minutes, and 24 for hours, and then the right number of days for each month.)

If you want to keep track of tenths of a penny on something like the national debt though, you'll need at least 17 decimal digits any way you approach it.

Quote:
There is no free lunch with this, I'm afraid.

As is always the case.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 02, 2013 7:30 pm 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 674
Using "..." to mean "some other ignored digits", 0.01... * 0.02... = 0.00... If the problem you see is that it clamps to zero, well that's the same issue you get when LSR'ing in order to divide by 2 in integer math. Integer math is actually a special case of fixed-point math, so it's got the same underflow and overflow limitations. Fixed point does not add any more problems to pre-existing integer byte math, but does add the benefit of fractional amounts. In any case (integer or fixed point), you declare your range and precision, but you can easily intermix different fixed-point formats, too.

As far as expense of fixed vs float goes, floating point has to do a lot of shifting and alignment for normalization even in basic addition. Fixed point avoids all of that, given that the radix point is always aligned. Even adding 1.0 + 2.0 in floating point requires shifting of an entire mantissa to line up before addition.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Wed Jul 03, 2013 7:05 am 
Offline

Joined: Tue Jul 02, 2013 5:09 am
Posts: 2
I'll be giving the ADC opcode a try tomorrow. I think I understand it now, but I'll report back if I have more issues.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 04, 2013 1:52 pm 
Offline

Joined: Sat Dec 13, 2003 3:37 pm
Posts: 1004
ADC is very simple, it's the FLAGs that are complicated. Specifically Overflow.

Here is my binary add routine. Like White Flame mentions above, I also use it in SBC.

It's Java, but close enough to C to likely not matter.

This is for a 6502, not 65C02, it handles the flags differently in Decimal mode, whereas the 6502 only deals with N and Z. I have read as much of the treatises as I could find on Overflow, I THINK I know how it works, but I can't really explain how the exclusive ORs arrive at the solution.

This code passes the CPU test that's been floating around.
Code:
    private void performBinaryAdd(int value, int carryValue) {
        int result = acc + value + carryValue;

        if (!isDecimal()) {
            status &= ~(CARRY_MASK + ZERO_MASK + OVERFLOW_MASK + NEGATIVE_MASK);
            if (result > 0xFF) {
                status |= CARRY_MASK;
            }

            if ((((acc ^ result) & (value ^ result) & 0x80) != 0)) {
                status |= OVERFLOW_MASK;
            }
        }

        result = result & 0xff;
        setFlagsNZ(result);

        acc = result & 0xff;
    }


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 04, 2013 4:37 pm 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 674
Speaking of which, is there any high-level language that exposes the underlying Carry flag to the programmer? It always bugs me that using over-sized ints and masking is always required to perform carry-like operations, even though there's direct hardware support.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 04, 2013 6:45 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8521
Location: Southern California
White Flame wrote:
Speaking of which, is there any high-level language that exposes the underlying Carry flag to the programmer? It always bugs me that using over-sized ints and masking is always required to perform carry-like operations, even though there's direct hardware support.

My '816 Forth leaves the C flag alone between primitives. (A primitive is a word--basically a routine--defined in assembly language). But if you want precision beyond the single precision (16-bit in 6502 Forth) and double precision (32-bit in 6502 Forth) which are always included as standard, then instead of writing other primitives that use the C flag so you can piece together greater precisions in high-level definitions, it would be better to just write other primitives to directly handle as much precision as you want all at once. Unlike the situation in BASIC and many other languages, dropping into assembly whenever you want is trivial in Forth; and whether you define the word in assembly or high-level does not affect how other words use it. And yes, you can define your own operators. For example, + and D+ are always standard for single- and double-precision add, and you can define your own T+ and Q+ for triple- and quad-precision addition (or call them anything you like).

I do have a small collection of others' high-level definitions for math components in triple and quad precision, but the girations they have to go through to do it that way do have an impact on performance compared to what you get if you just re-write them as primitives (ie, define them in assembly). There's always this push for portability, and of course assembly is not portable. I've seen discussions and contests to see how few primitives you could get away with in making a kernel (the minimum seems to be about 30), and it gets pretty ridiculous as they throw efficiency out the window. Fortunately 6502 assembly is easy and there's hardly a reason to avoid it for certain things.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 04, 2013 8:49 pm 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 674
The situations where I was begging for the carry flag to be exposed were in detecting numeric overflow to handle exceptional circumstances, and shifting in & out of bytes when compacting variable-sized bit fields in file formats.

Unlike addition, this is where the carry flag would be directly handled in the high level language for any purpose, not just for a numeric chain. These problems are still quite solvable without carry access, just more of a pain.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 05, 2013 5:08 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8415
Location: Midwestern USA
White Flame wrote:
As far as expense of fixed vs float goes, floating point has to do a lot of shifting and alignment for normalization even in basic addition. Fixed point avoids all of that, given that the radix point is always aligned. Even adding 1.0 + 2.0 in floating point requires shifting of an entire mantissa to line up before addition.

Shifting to normalize the coeeficient (mantissa) is fairly trivial compared to the actual conversion between ASCII and internal FP format. In fact, I have a BCD algorithm that does it with alacrity on the 65C816. Aah, the joys of 16 bit operations! :lol:

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 05, 2013 10:54 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10949
Location: England
I would guess that the normalisation needed for FP routines might halve the performance compared to integer routines - setting aside the question of converting to and from ASCII.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jul 05, 2013 1:33 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8415
Location: Midwestern USA
BigEd wrote:
I would guess that the normalisation needed for FP routines might halve the performance compared to integer routines - setting aside the question of converting to and from ASCII.

It would depend a lot on just how far out of nomalization the number is at the time. In my routines, it's all a bunch of rotates and with the accumulators on direct page, it goes quick enough. Since the 65C816 can rotate 16 bits at a time, with only a one clock penalty per rotate, normalization is less a drag on performance than one might think. I haven't done a cycle-exact count on it but I do know that most operations of this type with the '816 take about 60 percent of the execution time as compared to a 65C02.

None of it, of course, gets away from the fact that FP is going to be slow when done in software. Perhaps, some day, WDC or someone else will develop a 65xx bus-compatible FP co-processor.

In my (slowly developing) 816NIX filesystem code, a bunch of calculations have to be made to determine filesystem geometry for any given number of filesystem blocks. I do all of this using 64 bit (binary) integer functions. Some multiplication and division functions can be done purely as shifts and rotates, as they involve exact powers of 2. Others have to be done the "hard" way, using the common shift-and-add or shift-and-subtract algorithms. Again, with the '816 it can be done 16 bits at a time, so performance is pretty good.

One of these days I'll devise a means of testing these algorithms to determine just how much a performance gain is achieved with 16 bit vs. 8 bit operation.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 20 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 24 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: