Got any tips for Early NES Emulator Development?

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

You're setting V incorrectly

!((CPU_A ^ Value)&0x80) <-- ensures CPU_A and Value have the same sign

!((CPU_A ^ CPU_TEMP)&0x80) <-- ensures A and Temp have the same sign


this translates to:

positive + positive = positive
negative + negative = negative

which is not quite how V works.

you want A and Val to have the same sign, but A and Temp to have opposing signs. So you're close... but you have a ! in there that you shouldn't:

Code: Select all

if(!((CPU_A ^ Value)&0x80) && !((CPU_A ^ CPU_TEMP)&0x80))
                              ^
                              |
                              |
                       remove that


if(!((CPU_A ^ Value)&0x80) &&  ((CPU_A ^ CPU_TEMP)&0x80))
That will ensure A,Val have the same sign, but A,Temp have opposing signs:

Positive + Positive = Negative
Negative + Negative = Positive

The rest looks good to me. Your "^ 0xFF" SBC trick will work exactly right.
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Well I fixed the overflow calculation. Thanks for that. Now I get error code 71 on immediate which is the first error code for SBC. Before it was 75 which was the last for immediate. So it suggests something is still wrong somewhere... I'll be going over my opcodes since now I believe every opcode is implemented in the new form. Maybe one of the opcodes is setting carry incorrectly which throws off the SBC.

I did try running some different games and they all do something. Some of them though end up looping with NMI disabled. Not sure why that is yet.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

To cover the obvious:

make sure that opcode $E9 is doing SBC immediate and not some other instruction/addressing mode?
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

I forgot to set N after ADC (and SBC). I get OKs on most tests now.

Indirect X, Indirect Y, and Zeropage X give errors but everything else says OK. =) Also, Donkey Kong no longer gets stuck in that endless loop with NMI disabled. Instead I can see donkey kong animating and such, till attract mode ends and goes back to the title. =)

Update: I fixed Zeropage indexed, I didn't realize quite how that worked and that it had to wrap. Now it's down to fixing the indirect x and y.

Update: Seems I had the same issue with Indirect X where I forgot I needed to wrap the Zeropage. It's down to Indirect Y address mode problems. I imagine I should look into zp wrapping problems. :p

Update: Indirect Y needed zp wrapping. Now I pass all the cpu tests. Time to refine a few things I suppose and then look at adding real graphics support.

Update: I figured out how I wanted to handle graphics (as far as CPU access and rendering access). I broke it into 1k chunks so I can be flexible enough. I've also put together a routine to decode and draw NES tiles. I can finally SEE the games running via drawing the Nametables. I'm quite happy with my progress since I have only been at this for a week in my free time.
mozz
Posts: 94
Joined: Mon Mar 06, 2006 3:42 pm
Location: Montreal, canada

Post by mozz »

MottZilla wrote:I believe the idea was that, if you have an 8bit value and you are adding a number to it, if the number you end up with is less than wht you started with, then you wrapped around. However when I went back to my original code which does it with an int and a >0xff, it is working again. I guess I was thinking by adding a bunch of 8bit values together it would wrap and never be greater than 0xff. I dunno. I'm tired I guess. :p Thanks for pointing that out. Anyway, I took my newer code and fixed that fuckup. Everything is fine now.
What happened to you there, is that in C/C++ when you do arithmetic on integer types smaller than int, they get promoted to int to do the arithmetic. So its exactly like hap said.

Code: Select all

 if( (CPU_A + Value + Carry ) < CPU_A)   // Check if Carry will Result
In this case, CPU_A, Value and Carry get promoted to ints. When it adds them together, it's adding three ints and the result is an int. Then its comparing int < CPU_A, again with int on both sides. To really compare an 8-bit value to CPU_A, you would have to cast the thing on the left to an 8-bit type. I think it would still get promoted back to int to do the comparison, but it would only have the bottom 8 bits set.

Its important to remember the rule about small integer types getting promoted to int before doing math on them, because you can get surprised if you do something like this:

Code: Select all

short X = (myShort1 + myShort2) >> 2;
If you expected it to add two 16-bit values and then SHR a 16-bit value and store it in the 16-bit variable... that's not what happens. Both the add and the shift promote their arguments to int, so any carry into bit 16 of the add will end up in bit 14 of your result.

[Edit: I found this PDF with google. It has some good slides describing how integer promotions work, and some surprising consequences. e.g. if you have an unsigned char C = 0x55, and you write ~C then what you actually get is a very large negative *signed* int! If you then assign it to an unsigned char variable, nothing bad will happen--but I wouldn't recommend doing arithmetic with that value unless you completely understand the integer promotion rules. :)]
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

It's a bad idea to assume the sizes of the integral types in C and C++. It's much better to use unsigned int with bit masks. For example:

(unsigned char) x -> x & 0xFF
~x -> x ^ 0xFF or x ^ 0xFFFF
x + y < x -> ((x + y) & 0xFF) < x

If you feel you must use integral types instead of masks, use uint8_t and uint16_t, accessible with #include <stdint.h>.
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Well it's good to know as prior to this project I've never had to worry much about size of variables. But I understand now why what I tried to do failed to work as intended. I'll read that PDF when I get the time. You're all so very helpful. ;)

As for my emulator, last night I added sprites and with that and drawing the first name table it was enough to play Donkey Kong and some other games. But strangely Mario Bros exhibits some interesting behavior. When the player starts, he falls to the very bottom of the screen. He cannot move. The enemys come out and also fall to the bottom but they CAN move and you die. :p

If anyone has a guess as to what would cause this let me know. I'll look into it when I get back to work on this but I think it's an interesting problem. I don't think it could be CPU core related as I passed all the tests, so I was thinking it was related to the NES. I don't think I emulate cpu reading from video memory yet, so possibly that or many other things perhaps.
mattmatteh
Posts: 345
Joined: Fri Jul 29, 2005 3:40 pm
Location: near chicago

Post by mattmatteh »

i agree with what blargg said about the data types. i use stdint.h and most of my data types are uint8_t, uint16_t, that way you know exactly what types they are.

matt
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

I fixed Mario Bros and Pac-Man. They needed to read from the PPU. I think that's strange though to read from the nametables to handle collision. But either way, that fixes any game that needs to read from VRAM now. =)
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

MottZilla wrote:I think that's strange though to read from the nametables to handle collision.
I doubt either game does that. SMB reads from CHR-ROM because that's where it's title screen arrangement is stored. Early games stored data in CHR-ROM once they ran out of space in PRG.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

blargg wrote:It's a bad idea to assume the sizes of the integral types in C and C++.
Two specifications impose constraints on a C compiler: the C standard and each platform's application binary interface (ABI). In C, char means byte, and C guarantees that char is always at least 8 bits (CHAR_BIT >= 8).[1] The ABIs of the most popular platforms (x86, PowerPC, ARM) guarantee that CHAR_BIT == 8, making unsigned char and uint8_t equivalent.

If you do choose to rely on an aspect of your platform's ABI, there are methods to perform assertions at compile time. (Caution: a #if block isn't always the best choice, as it doesn't work for sizeof and other things that are evaluated after preprocessing is done.) I use code similar to this:

Code: Select all

#define CTASSERT(name, condition) \
extern const char name[(condition) ? 1 : -1];

CTASSERT(char_is_8_bits, CHAR_BIT == 8)
CTASSERT(int_is_4_bytes, sizeof(int) == 4)
If an ABI assertion fails, the compiler will fail and issue a diagnostic about an array with negative size. That's a lot better than compiling a binary whose behavior isn't well defined.
If you feel you must use integral types instead of masks, use uint8_t and uint16_t, accessible with #include <stdint.h>.
Not all compilers have been updated with <stdint.h>, which C99 introduced. Sometimes you have to rely on "config.h" or ABI assertions on platforms where you don't have a good C99 compiler. But in this specific case, I think masks should be a better choice.
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

More straight-forward to verify the ranges of the types, which is ultimately what you would be relying on:

#include <limits.h>
#if UCHAR_MAX != 0xFF || USHORT_MAX != 0xFFFF || UINT_MAX != 0xFFFFFFFF
#error "unsigned char must be 8 bits, unsigned short must be 16 bits, and unsigned int must be 32 bits"
#endif
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Disch wrote:
MottZilla wrote:I think that's strange though to read from the nametables to handle collision.
I doubt either game does that. SMB reads from CHR-ROM because that's where it's title screen arrangement is stored. Early games stored data in CHR-ROM once they ran out of space in PRG.
Oh, so it's more likely they stored an array in CHRROM of the collision map? Either way, Pac-Man and Mario Bros has no collision data if you aren't responding to VRAM read requests.

Anyway, my emulator has been coming along. I've added sprite support. Sprite DMA Transfers, OAM I/O, 8x8 Sprites, 8x16 Sprites, Sprite Flipping, Sprites in their correct palettes. After that before working on rendering the screen properly I wanted to work on attribute tables for the background tile pallettes. Right now I've just be rendering NT#0 at VBlank with sprites so I could test simple games before working on a real renderer.

I never quite understood with my attempts at NES homebrew how anyone could work with the attribute table. It seemed like such a bastard to me. But I knew there was a way to calculate it, and I did with success. So now the emulator is doing pretty well I think. =) I even cheated to make SMB run by setting sprite 0# hit after a certain amount of cycles. The background color is correct too, which was the main reason I wanted to run it right now anyway since I'd heard many people make an error in mirroring which causes the background to be black.

About variable sizes, I don't have a problem with them, I just never knew about what you were working with when adding various types together. :p
User avatar
Dwedit
Posts: 4470
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Super Mario Land on the GB used the tilemap in VRAM for collisions and identifying tiles.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

But then the VRAM on Game Boy and Game Boy Color is mapped into CPU space (even if not dual-ported), so that was more likely.
Post Reply