It is currently Fri Aug 23, 2019 3:06 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 37 posts ]  Go to page Previous  1, 2, 3  Next
Author Message
PostPosted: Tue Jul 23, 2019 2:08 pm 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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?


Top
 Profile  
 
PostPosted: Tue Jul 23, 2019 2:13 pm 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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.


Top
 Profile  
 
PostPosted: Tue Jul 23, 2019 3:15 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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.


Top
 Profile  
 
PostPosted: Tue Jul 23, 2019 4:29 pm 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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)


Top
 Profile  
 
PostPosted: Tue Jul 23, 2019 5:27 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1473
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.


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 12:55 am 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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.

Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 1:49 am 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
Make my previous edits into a reply:
This is my completed ADC code, does it look right?
Code:
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.

Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 2:13 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
I suggest using easy6502 and validating your results.


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 2:14 am 
Offline

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

Thanks!
That is a nice tool.


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 2:30 am 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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:
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!


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 3:20 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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:
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


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 3:37 am 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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:
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.


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 5:56 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21560
Location: NE Indiana, USA (NTSC)
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

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 6:07 am 
Offline

Joined: Tue Jul 23, 2019 10:41 am
Posts: 31
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:
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?


Top
 Profile  
 
PostPosted: Wed Jul 24, 2019 7:20 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21560
Location: NE Indiana, USA (NTSC)
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:

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 37 posts ]  Go to page Previous  1, 2, 3  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 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:  
Powered by phpBB® Forum Software © phpBB Group