I need more info about the 6502 CPU instruction set.

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

koitsu wrote:
timl132 wrote:Thanks. I will take a look, but I think I have enough for now.
Strongly urge you to read "Programming the 65816 (including the 6502, 65C02, and 65802)". It is 65816-focused (e.g. 16-bit CPU used in SNES and Apple IIGS), but it goes over 8-bit 6502 (NES etc.) and 65C02 (Apple IIE etc.) as well. Your questions like about ADC/SBC and signed numbers are covered in very great detail (several pages, with examples), as well as general nuances. There is a reason a CPU manufacturer boughts right to this book. You just have to learn how to "ignore" the 65816/16-bit-specific stuff. It's still a good manual for 6502 despite that.

Also, with regards to ADC/SBC, because you're writing an emulator: these two opcodes are still the top struggle point for emulator authors. Please see here for almost two decades of details. Edit: you're already posting about emulation problems with these opcodes and how overflow fits into the picture. Please see what I linked here. Take the 30+ minutes to read the threads/links, you will find code as well.
I'll see if I can find it.

Thanks for the link, will read it. I guess ADC and SBC are so hard because of how they are handled in programming languages?

Also, not sure if it will work, but I just got the idea of casting the numbers to 16 bit signed, then doing the addition, and then checking if it is higher than 255 or below 0, then set the carry bit accordingly.(then taking the last 8 bits and creating a uint8_t from it) Can anyone give input on if that would work?
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

lidnariq wrote:C/C++'s type promotions don't allow arithmetic on 8-bit types (everything must be promoted to signed ints first, before then being cast to the type it's stored in). You'll have to think through how 2's complement works.
What do you mean?
Also, just watched a video on 2's compliment. So 1 = 00000001 and -1 = 11111111. Adding those 2 together makes 00000000(which is correct). So I think I understand that.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: I need more info about the 6502 CPU instruction set.

Post by koitsu »

timl132 wrote:I'll see if I can find it.

Thanks for the link, will read it. I guess ADC and SBC are so hard because of how they are handled in programming languages?

Also, not sure if it will work, but I just got the idea of casting the numbers to 16 bit signed, then doing the addition, and then checking if it is higher than 255 or below 0, then set the carry bit accordingly.(then taking the last 8 bits and creating a uint8_t from it) Can anyone give input on if that would work?
1. Not much to find -- please *read* the wiki page I linked. There are PDF copies of the book available directly there.

2. ADC/SBC are "hard" because most people writing NES emulators (in my experience, e.g. IMO) do not understand two's complement (were not taught it, etc.). This is further compounded by 1) difficult-to-understand information about the 65xx CPUs on the web -- most of which do not go into great detail over how overflow works with these opcodes and 2) (IMO) most NES emulator authors not working with CPUs at the instruction/assembly level, instead used to PLs that deal with signed/unsigned numbers differently. Accurate emulation code for these instructions is as simple as this. I also urge reading Disch's posts in the same thread, which offer other approaches. Hopefully one of the answers will "click" and make the most sense to you.

The aforementioned WDC book covers this subject and these opcodes across several places: pages 17 to 19, and pages 127 to 138 (the good details are here, with examples, thought some of the examples are for 16-bit numbers/16-bit registers -- but the same exact premise applies to 8-bit numbers/registers. There is one 8-bit example though, IIRC).

I should add that the NES 6502 CPU lacks BCD (binary coded decimal) support, unlike other 6502 CPUs. It's one of the aspects that makes the CPU unique/different. So if/when reading 6502 documentation that discusses using it natively on the CPU, e.g. via sed (setting the D bit in P (CPU status register)), just know that the NES CPU lacks this feature (the bit does nothing). NES/Famicom programmers implemented it on a per-game basis, if needed, purely via software.

Overall, the CPU emulation will be the least of your concerns (read: fairly easy to implement), and there are some test ROMs that can help (nestest is a good one, but please see here). The PPU is where you'll be spending 95% of your time.
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

koitsu wrote:
timl132 wrote:I'll see if I can find it.

Thanks for the link, will read it. I guess ADC and SBC are so hard because of how they are handled in programming languages?

Also, not sure if it will work, but I just got the idea of casting the numbers to 16 bit signed, then doing the addition, and then checking if it is higher than 255 or below 0, then set the carry bit accordingly.(then taking the last 8 bits and creating a uint8_t from it) Can anyone give input on if that would work?
1. Not much to find -- please *read* the wiki page I linked. There are PDF copies of the book available directly there.

2. ADC/SBC are "hard" because most people writing NES emulators (in my experience, e.g. IMO) do not understand two's complement (were not taught it, etc.). This is further compounded by 1) difficult-to-understand information about the 65xx CPUs on the web -- most of which do not go into great detail over how overflow works with these opcodes and 2) (IMO) most NES emulator authors not working with CPUs at the instruction/assembly level, instead used to PLs that deal with signed/unsigned numbers differently. Accurate emulation code for these instructions is as simple as this. I also urge reading Disch's posts in the same thread, which offer other approaches. Hopefully one of the answers will "click" and make the most sense to you.

The aforementioned WDC book covers this subject and these opcodes across several places: pages 17 to 19, and pages 127 to 138 (the good details are here, with examples, thought some of the examples are for 16-bit numbers/16-bit registers -- but the same exact premise applies to 8-bit numbers/registers. There is one 8-bit example though, IIRC).

I should add that the NES 6502 CPU lacks BCD (binary coded decimal) support, unlike other 6502 CPUs. It's one of the aspects that makes the CPU unique/different. So if/when reading 6502 documentation that discusses using it natively on the CPU, e.g. via sed (setting the D bit in P (CPU status register)), just know that the NES CPU lacks this feature (the bit does nothing). NES/Famicom programmers implemented it on a per-game basis, if needed, purely via software.

Overall, the CPU emulation will be the least of your concerns (read: fairly easy to implement), and there are some test ROMs that can help (nestest is a good one, but please see here). The PPU is where you'll be spending 95% of your time.
Thanks for your reply!

I read the post you linked, if I'm correct I can make a 16 bit variable(call it temp), put my carry on the 9th bit, put register A in there, then add the argument to adc. Then I can read the 9th bit back into the carry flag, and convert my 16 bit var back to 8, and store it in a. Is that correct?
If so, I have one more question, do I use an unsigned var for my temp variable, or a signed variable. I think signed, but I'm unsure.(If it even matters at all)
I'm hoping you can answer that, so I know I'm on the right track.

Also, I think I understand 2's compliment, but I'll read the book tomorrow anyway.(Gotta sleep first)
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: I need more info about the 6502 CPU instruction set.

Post by Quietust »

timl132 wrote: I read the post you linked, if I'm correct I can make a 16 bit variable(call it temp), put my carry on the 9th bit, put register A in there, then add the argument to adc. Then I can read the 9th bit back into the carry flag, and convert my 16 bit var back to 8, and store it in a. Is that correct?
No, that's wrong - you take the A register, add the argument, then add the carry flag itself (i.e. as a 0 or 1) to get the result (and set the carry flag appropriately).

To repeat koitsu, read the wiki page.
timl132 wrote: If so, I have one more question, do I use an unsigned var for my temp variable, or a signed variable. I think signed, but I'm unsure.(If it even matters at all)
I'm hoping you can answer that, so I know I'm on the right track.

Also, I think I understand 2's compliment, but I'll read the book tomorrow anyway.(Gotta sleep first)
If you have to ask that question, then you don't understand two's complement yet - if you did, you would've known that the signed-ness of the variable in question does not matter at all, though for simplicity's sake you'll probably want to store the various registers as unsigned integers (so you don't have to worry about them getting sign-extended during promotion).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

Quietust wrote:
timl132 wrote: I read the post you linked, if I'm correct I can make a 16 bit variable(call it temp), put my carry on the 9th bit, put register A in there, then add the argument to adc. Then I can read the 9th bit back into the carry flag, and convert my 16 bit var back to 8, and store it in a. Is that correct?
No, that's wrong - you take the A register, add the argument, then add the carry flag itself (i.e. as a 0 or 1) to get the result (and set the carry flag appropriately).

To repeat koitsu, read the wiki page.
timl132 wrote: If so, I have one more question, do I use an unsigned var for my temp variable, or a signed variable. I think signed, but I'm unsure.(If it even matters at all)
I'm hoping you can answer that, so I know I'm on the right track.

Also, I think I understand 2's compliment, but I'll read the book tomorrow anyway.(Gotta sleep first)
If you have to ask that question, then you don't understand two's complement yet - if you did, you would've known that the signed-ness of the variable in question does not matter at all, though for simplicity's sake you'll probably want to store the various registers as unsigned integers (so you don't have to worry about them getting sign-extended during promotion).
Oh yeah, you are totally right. so I add the carry to the value(carry being a 0 or a 1 integer), and then when I set the carry, I fetch the 9th bit.

The reason I asked about the unsigned vs signed thing was because I read somewhere that something about it is different, but thinking of it, I don't actually know what. So I guess I was thinking of something else.

There is something else though, if I am correct, the ADC instruction also set's the V flag(overflow), which according to one site is "set if the sign bit is incorrect". How do I know if a bit is incorrect? NVM figured it out. I just have to set it if the operation overflows. Which I can just do by checking if the 16 bit result is over 255.

Thanks!

Edit:
<removed>
Last edited by timl132 on Wed Jul 24, 2019 1:49 am, edited 1 time in total.
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

Make my previous edits into a reply:
This is my completed ADC code, does it look right?

Code: Select all

uint16_t temp = AC + nesRam[PC+1] + getBit(SR, 0);// nesRam is an array of uint8_t's the size of 65536.
AC = temp;
setBit(SR, 0, getBit16(temp, 8));// Carry flag
setBit(SR, 1, temp == 0);// Zero flag
setBit(SR, 6, temp > 255);// Overflow flag
setBit(SR, 7, getBit16(temp, 7));// Negative flag
There is one thing I have noticed, if the argument provided to ADC is e.g. -4 and the value in A is 5, the result is 1(as expected), but the carry flag and overflow flag are set, is that correct behaviour? If I think 2's compliment style, then adding 5 and -4 should indeed overflow and therefore set the overflow and carry flag, but thinking numbers style it doesn't make sense. What is right?NVM figured it out again, it is correct behaviour.
Last edited by timl132 on Wed Jul 24, 2019 2:14 am, edited 1 time in total.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: I need more info about the 6502 CPU instruction set.

Post by koitsu »

I suggest using easy6502 and validating your results.
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

koitsu wrote:I suggest using easy6502 and validating your results.
Thanks!
That is a nice tool.
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

koitsu wrote:I suggest using easy6502 and validating your results.
I tried some things in that tool, and I am a bit confused.
I tried this:

Code: Select all

LDA #11
ADC #245
I'd expect this to set the carry bit, null bit and overflow bit. The carry and null bit are set, the overflow bit isn't.
but 11+245 is >255 and loops back to 0, so it overflows right?
Why doesn't it set the overflow bit?
NVM figured it out again. you are supposed to set the overflow bit/flag whenever 2 positive values make a neative value, or when 2 negative values make a positive value.

Thanks!
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: I need more info about the 6502 CPU instruction set.

Post by koitsu »

Yeah, you don't understand overflow or how this works. At all. I don't think you've read any of the materials I've given you, so I'm only going to say this once more. Respectfully, I must stress reading the WDC book I mentioned, specifically the pages I listed. Do not skim, do not skip around, do not go writing emulator code, READ. There is a point near the end where there is a "summary" given of how to understand the carry and overflow results. But you should read what is written entirely and not just that summary.

easy6502 does not support parsing negative numbers in immediates, but that doesn't matter (truly!). The below brk stops easy6502 from continuing to execute. See if you can figure out what is going on and why, *after* reading said book. I think these will make a lot more sense to you afterward. Have fun:

Code: Select all

lda #5
clc
adc #4
brk         ; result: n=0 v=0 z=0 c=0 A=$09

lda #5
clc
adc #252    ; same as adc #-4 or adc #$fc
brk         ; result: n=0 v=0 z=0 c=1 A=$01

lda #5
sec
adc #252    ; same as adc #-4 or adc #$fc
brk         ; result: n=0 v=0 z=0 c=1 A=$02

lda #11
clc
adc #245    ; same as adc #-11 or adc #$f5
brk         ; result: n=0 v=0 z=1 c=1 A=$00

lda #11
sec
adc #245    ; same as adc #-11 or adc #$f5
brk         ; result: n=0 v=0 z=0 c=1 A=$01

lda #130    ; same as lda #-126 or lda #$82
clc
adc #244    ; same as adc #-12 or adc #$f4
brk         ; result: n=0 v=1 z=0 c=1 A=$76

lda #130    ; same as lda #-126 or lda #$82
sec
adc #244    ; same as adc #-12 or adc #$f4
brk         ; result: n=0 v=1 z=0 c=1 A=$77

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #20
brk         ; result: n=0 v=0 z=0 c=1 A=$0A

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #254    ; same as adc #-2 or adc #$fe
brk         ; result: n=1 v=0 z=0 c=1 A=$F4

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #$81    ; same as adc #-127 or adc #129
brk         ; result: n=0 v=1 z=0 c=1 A=$77

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #$ff    ; same as adc #-1 or adc #255
brk         ; result: n=1 v=0 z=0 c=1 A=$F5
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

koitsu wrote:Yeah, you don't understand overflow or how this works. At all. I don't think you've read any of the materials I've given you, so I'm only going to say this once more. Respectfully, I must stress reading the WDC book I mentioned, specifically the pages I listed. Do not skim, do not skip around, do not go writing emulator code, READ. There is a point near the end where there is a "summary" given of how to understand the carry and overflow results. But you should read what is written entirely and not just that summary.

easy6502 does not support parsing negative numbers in immediates, but that doesn't matter (truly!). The below brk stops easy6502 from continuing to execute. See if you can figure out what is going on and why, *after* reading said book. I think these will make a lot more sense to you afterward. Have fun:

Code: Select all

lda #5
clc
adc #4
brk         ; result: n=0 v=0 z=0 c=0 A=$09

lda #5
clc
adc #252    ; same as adc #-4 or adc #$fc
brk         ; result: n=0 v=0 z=0 c=1 A=$01

lda #5
sec
adc #252    ; same as adc #-4 or adc #$fc
brk         ; result: n=0 v=0 z=0 c=1 A=$02

lda #11
clc
adc #245    ; same as adc #-11 or adc #$f5
brk         ; result: n=0 v=0 z=1 c=1 A=$00

lda #11
sec
adc #245    ; same as adc #-11 or adc #$f5
brk         ; result: n=0 v=0 z=0 c=1 A=$01

lda #130    ; same as lda #-126 or lda #$82
clc
adc #244    ; same as adc #-12 or adc #$f4
brk         ; result: n=0 v=1 z=0 c=1 A=$76

lda #130    ; same as lda #-126 or lda #$82
sec
adc #244    ; same as adc #-12 or adc #$f4
brk         ; result: n=0 v=1 z=0 c=1 A=$77

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #20
brk         ; result: n=0 v=0 z=0 c=1 A=$0A

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #254    ; same as adc #-2 or adc #$fe
brk         ; result: n=1 v=0 z=0 c=1 A=$F4

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #$81    ; same as adc #-127 or adc #129
brk         ; result: n=0 v=1 z=0 c=1 A=$77

lda #246    ; same as lda #-10 or lda #$f6
clc
adc #$ff    ; same as adc #-1 or adc #255
brk         ; result: n=1 v=0 z=0 c=1 A=$F5
I tried the examples you gave them and they all give correct output in my emulator. I understand what is happening, that is not the problem.(They make perfect sense to me) The problem is that I need some time adjusting to thinking in binary instead of numbers.(I keep mixing things up)

To be honest, I'm not really into reading books. You strongly recommend I read the book, does it contain anything else than above? Because I understand 2's compliment and how the carry and overflow works, and prefer not to read something if I already understand it. No offense, you have good intentions and you are very helpful.


Edit:
I actually did read a part of the book. I read the part about addressing modes, which was quite interesting. I also read the pages you linked. Tbh, I knew most of it already by googling, but it was still interesting to read.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: I need more info about the 6502 CPU instruction set.

Post by tepples »

Overflow (V flag) is set if and only if the sign bit of the output differs from the sign bit of both inputs. On 6502 or any other 8-bit architecture, the sign bit is bit 7.

-72 + 80 = 8
10111000 + 01010000 = 00001000
Input bit 7: 1, 0; output bit 7: 0, matching second input; not overflow

7 + 70 = 77
00000111 + 01000110 = 01001101
Input bit 7: 0, 0; output bit 7: 0, matching first and second input; not overflow

-7 + (-70) = -77
11111001 + 10111010 = 10110011
Input bit 7: 1, 1; output bit 7: 1, matching first and second input; not overflow

72 + 80 = 152
01001000 + 01010000 = 10011000
Input bit 7: 0, 0; output bit 7: 1, matching neither; this is overflow
It's overflow because 72 and 80 are both positive, but 152 wraps to -104 when interpreted as an 8-bit signed two's complement number, and -104 is negative unlike both inputs.

In C, C++, or Python, this can be expressed as vflag = (in1 ^ out) & (in2 ^ out) & 0x80
  • in1 ^ out means all bits that differ between the first input and the output
  • in2 ^ out means all bits that differ between the second input and the output
  • & 0x80 means ignore all bits other than bit 7
timl132
Posts: 35
Joined: Tue Jul 23, 2019 10:41 am

Re: I need more info about the 6502 CPU instruction set.

Post by timl132 »

tepples wrote:Overflow (V flag) is set if and only if the sign bit of the output differs from the sign bit of both inputs. On 6502 or any other 8-bit architecture, the sign bit is bit 7.

-72 + 80 = 8
10111000 + 01010000 = 00001000
Input bit 7: 1, 0; output bit 7: 0, matching second input; not overflow

7 + 70 = 77
00000111 + 01000110 = 01001101
Input bit 7: 0, 0; output bit 7: 0, matching first and second input; not overflow

-7 + (-70) = -77
11111001 + 10111010 = 10110011
Input bit 7: 1, 1; output bit 7: 1, matching first and second input; not overflow

72 + 80 = 152
01001000 + 01010000 = 10011000
Input bit 7: 0, 0; output bit 7: 1, matching neither; this is overflow
It's overflow because 72 and 80 are both positive, but 152 wraps to -104 when interpreted as an 8-bit signed two's complement number, and -104 is negative unlike both inputs.

In C, C++, or Python, this can be expressed as vflag = (in1 ^ out) & (in2 ^ out) & 0x80
  • in1 ^ out means all bits that differ between the first input and the output
  • in2 ^ out means all bits that differ between the second input and the output
  • & 0x80 means ignore all bits other than bit 7
Hey thanks for your reply.

I had already figured it out, however, your way of implementing it is way better, I implemented it like this:

Code: Select all

bool twosComplimentAddOverflowCheck(uint8_t a, uint8_t b)
{
    bool v = false;
    if (getBit(a, 7) && getBit(b, 7))
    {
        v = !getBit(a+b, 7);
    }
    else if (!getBit(a, 7) && !getBit(b, 7))
    {
        v = getBit(a+b, 7);
    }

    return v;
}
I think I need to learn more about c++ bit wise operators, as I don't really understand them thatt well.

You say & 0x80 ignores all bits other than the 7th one, does this mean it outputs a bool, or a byte with only one bit set or clear? Also, & means and right? Just like && for if? So if both bit 7 in a and bit 7 in b are true, the output would be bit 7 would be true as well?
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: I need more info about the 6502 CPU instruction set.

Post by tepples »

In C, C++, Java, and Python:
  • & means bitwise AND, which considers each bit of the input values separately and outputs an integer with bits set where both corresponding input bits were set.
  • && means logical AND, which considers the input values as a whole: true if both are nonzero and false otherwise. In C and C++, the output value is 1 for true or 0 for false.
3 & 6 evaluates to 2, based on the binary values 0011 and 0110.
3 && 6 evaluates to 1, as both sides are nonzero.
2 & 5 evaluates to 0, based on the binary values 0010 and 0101.
2 && 5 evaluates to 1, as both sides are nonzero.

In addition, && doesn't even try to evaluate its second input if the first input is zero. This behavior is called "short-circuiting." So 0 & (1 / 0) is undefined behavior because of the division by zero, but 0 && (1 / 0) evaluates to zero because the division is never reached.

Further reading:
Post Reply