logo
 drop

Main

Community

Submissions

Help

71509599 visitors

Author Topic: NES programming help  (Read 1901 times)

Tomato

  • Full Member
  • ***
  • Posts: 226
    • View Profile
    • Legends of Localization
NES programming help
« on: March 25, 2012, 07:37:27 pm »

For a lark, I thought I'd look into how to program NES stuff from scratch. I learned a lot about this sort of stuff while working on Mother 3 and Mother 1+2 and want to maybe make some simple NES games someday.

Anyway, after looking very briefly at some of the early Nerdy Nights stuff ( http://www.nintendoage.com/forum/messageview.cfm?catid=22&threadid=7155 ) I have a simple program where you can move a tomato sprite around the screen, with some simple border-checking stuff so the sprite won't go off-screen. Here are the files for reference:

http://tomato.fobby.net/junk/sprites.nes (the compiled ROM)
http://tomato.fobby.net/junk/sprites.asm (the source code)
http://tomato.fobby.net/junk/sprites.asm.zip (all the above plus the CHR file)

First, is there a recommended emulator to use for homebrew NES stuff? I've been using Nestopia but it doesn't seem to have a debugger or anything like that, so I've also started using Mednafen.

Second, if you press up against the top boundary or move left against the left boundary, sometimes the sprite will move one pixel in the wrong direction. And sometimes the sprite will even suddenly jump a couple dozen pixels in the opposite direction when pushing against one of those two boundaries. Is there a reason for this? I really have no idea what I'm doing, so this is puzzling to me. It's hard to explain without seeing it in action too. My guess is it has something to do with interrupts but I really have no idea  :-[

Azkadellia

  • Submission Reviewer
  • Hero Member
  • *****
  • Posts: 761
  • Location: Georgia (the state, not the Soviet Country).
  • あなたの愛人に屈する!
    • View Profile
    • Princess Translations
Re: NES programming help
« Reply #1 on: March 25, 2012, 09:20:40 pm »
FCEUX is very robust for NES homebrew and hacking.
#princesstrans on irc.synirc.net - Official IRC channel for Princess Translations.
Current Projects: Nurvus: In progress; Princess Maker 2 Refine: 45%
I do the Twitter thing now: https://twitter.com/MistressSaeko (expect lots of game streaming announcements)

snarfblam

  • Submission Reviewer
  • Sr. Member
  • *****
  • Posts: 426
  • Location: romhacking.net
  • Personal Text
    • View Profile
    • snarfblam
Re: NES programming help
« Reply #2 on: March 25, 2012, 10:18:15 pm »
If you're looking to homebrew, NesDev is a great resource (http://nesdev.parodius.com/). You might want to check out NESICIDE. It's an NES IDE with an integrated emulator with debugger. There is also a build of Nintendulator that supports source-code debugging, both C and ASM.

Kinda funny, I just wrote a practically identical NES program (following the same tutorial), to test out an NES hacking/homebrew IDE I'm working on, but my own IDE is not finished :/. It generates debug output that works with FCEUX.

Tomato

  • Full Member
  • ***
  • Posts: 226
    • View Profile
    • Legends of Localization
Re: NES programming help
« Reply #3 on: March 25, 2012, 10:39:35 pm »
Cool, I'll check those out. Also, I wasn't aware there were C compilers for NES code, neat!

rveach

  • Newbie
  • *
  • Posts: 22
    • View Profile
Re: NES programming help
« Reply #4 on: March 26, 2012, 11:47:32 am »
Here are some things I noticed on your code, some minor, some important.
All your controller codes are basically similar, so I will only point to one and you should get the idea.

Code: [Select]
  LDA $0000
  CMP #$07
  BEQ check_down
  LDA $0000

You don't need to load $0000 twice. The value of A doesn't change on the CMP and BEQ, so you could remove the second and save 3 bytes space (further down the road you will probably need every space you can get).
Second, why don't you use "LDA $00" instead of "LDA $0000"? Again, another space saver and just as easy to read.
Third, using BEQ checks is just asking for trouble later. If, for whatever reason, the value is 1 off from some mistake, your sprite will go flying through the screen and come out on the other side. Since your already doing a CMP, I would check the N/C flag instead to create a "A > Value" or "A < Value".

Code: [Select]
  LDA $0000
  SBC #1
  STA $0000
  STA $0000

First, why are you doing a double store?
Second, I think you are missing a CLC or SEC before the SBC. If you don't set it right, you'll get the value from the CMP and this could be why you are seeing a 1 pixel jump sometimes.

Tomato

  • Full Member
  • ***
  • Posts: 226
    • View Profile
    • Legends of Localization
Re: NES programming help
« Reply #5 on: March 26, 2012, 04:48:21 pm »
Thanks for looking into my code, as you can tell it's very rough and ugly, mostly because I was trying to figure out what was going on  :P

You don't need to load $0000 twice. The value of A doesn't change on the CMP and BEQ, so you could remove the second and save 3 bytes space (further down the road you will probably need every space you can get).

I had read that the comparisons are done by subtracting the value given, so to make super duper sure the accumulator wasn't going to be changed I re-loaded it. It sounds like it doesn't get changed though, which is nice.

Quote
Second, why don't you use "LDA $00" instead of "LDA $0000"? Again, another space saver and just as easy to read.

I'm still not used to the memory mapping of NES stuff, I was under the impression that you have bytes 000-200 available for RAM, so I went with 4 digits. Does it make a difference during assembly?

Quote
Third, using BEQ checks is just asking for trouble later. If, for whatever reason, the value is 1 off from some mistake, your sprite will go flying through the screen and come out on the other side. Since your already doing a CMP, I would check the N/C flag instead to create a "A > Value" or "A < Value".

I definitely agree, I was just a little thrown off because I'm used to things like BGT and BLT from GBA THUMB programming. I gotta get used to these less-intuitive-sounding commands.

Quote
Code: [Select]
  LDA $0000
  SBC #1
  STA $0000
  STA $0000

First, why are you doing a double store?
Second, I think you are missing a CLC or SEC before the SBC. If you don't set it right, you'll get the value from the CMP and this could be why you are seeing a 1 pixel jump sometimes.

The double store was due to my theory that some interrupt somewhere was maybe messing with what was in the accumulator, I forgot I left that in there, heh. I had no idea about CLC/SEC stuff, thanks. I did study a variant of the 6502 like 15 years ago, looks like I've forgotten everything  :o

Thanks for the tips, I'll give them all a shot!

rveach

  • Newbie
  • *
  • Posts: 22
    • View Profile
Re: NES programming help
« Reply #6 on: March 26, 2012, 05:05:21 pm »
Quote from: Tomato
I had read that the comparisons are done by subtracting the value given

It does that somewhere else (like in its head), it won't touch either memory contents. Your A and memory will stay the same.

Quote from: Tomato
I'm still not used to the memory mapping of NES stuff, I was under the impression that you have bytes 000-200 available for RAM, so I went with 4 digits. Does it make a difference during assembly?

It makes no difference when running. "LDA $EF" and LDA $00EF" have the exact same results. The only difference is the size of the opcode (2 versus 3 bytes) and the range of memory that the instruction can reach. Obviously, since the first one has only a 1 byte memory address, it is limited from 00 to FF (aka 0000 to 00FF), while the other can access anywhere in memory, from 0000 to FFFF.
The only reason I really point this out is to save space. NES is very limited in space, you will see this when you start really coding your game. A few bytes here and there will add up in the long run. They may be the difference from a perfect fit in a single bank, to taking stuff out and adding a bank swap.
And because memory addresses 00-FF use less opcode space, these addresses are used for things that are used multiple multiple times. Most games use like 00 - 0F as temporary values for additions/subtractions/multiplications/divisions/bit-shifting.

Quote from: Tomato
I had no idea about CLC/SEC stuff, thanks. I did study a variant of the 6502 like 15 years ago, looks like I've forgotten everything

I would look over the opcode instructions, especially CMP/ADC/SBC. Look at what registers/values they use and change. If your going to code your game in complete assembly, you need to know these things like the back of your hand.

EDIT:

Quote from: Tomato
I was under the impression that you have bytes 000-200 available for RAM

Actually this is false. 100 - 1FF is your stack, so its not really available.
So you really have 00 - FF, and 200 - 7FF.
« Last Edit: March 26, 2012, 05:19:13 pm by rveach »

snarfblam

  • Submission Reviewer
  • Sr. Member
  • *****
  • Posts: 426
  • Location: romhacking.net
  • Personal Text
    • View Profile
    • snarfblam
Re: NES programming help
« Reply #7 on: March 26, 2012, 06:44:03 pm »
The only difference is the size of the opcode (2 versus 3 bytes) and the range of memory that the instruction can reach.
There is another important difference: zero-page instructions also take fewer CPU cycles to execute. This helps speed things up with frequently used variables or when you want to make the most of vblank.

Actually this is false. 100 - 1FF is your stack, so its not really available.
Many games use a portion of stack space for variable storage. Of course, you have to be careful not to let the stack overflow into your variables, and there is absolutely no reason to store variables in the stack space unless you're out of RAM.

Tomato

  • Full Member
  • ***
  • Posts: 226
    • View Profile
    • Legends of Localization
Re: NES programming help
« Reply #8 on: March 26, 2012, 07:57:41 pm »
Okay, after cleaning up the code and running into some more issues, here's where I'm at now:

ROM: http://tomato.fobby.net/junk/sprites/2/sprites.nes
Source: http://tomato.fobby.net/junk/sprites/2/sprites.asm

Here's the main loop now:

Code: [Select]
Forever:

controller_check:
  LDA #$01 ; get ready to read controllers
  STA $4016
  LDA #$00
  STA $4016
  LDA $4016 ; read A button on controller 1
  LDA $4016 ; read B button on controller 1
  LDA $4016 ; read Select button on controller 1
  LDA $4016 ; read Start button on controller 1

check_up:
  LDA $4016 ; read Up button on controller 1
  AND #1
  BEQ check_down        ; if not pressed, move on

  LDA $00               ; if y >= 7 y--
  CMP #08
  BCC check_down
  SBC #1
  STA $00

check_down:
  LDA $4016 ; read Down button on controller 1

check_left:
  LDA $4016 ; read Left button on controller 1
  AND #1
  BEQ check_right       ; if not pressed, move on

  LDA $01               ; if x >= 8 x--
  CMP #09
  BCC check_right
  SBC #1
  STA $01

check_right:
  LDA $4016 ; read Right button on controller 1

check_end:
  LDA $00
  STA $0200             ; set sprite 0's x coordinate
  LDA $01
  STA $0203             ; set sprite 0's y coordinate



  JMP Forever     ; jump back to Forever, infinite loop

I took out the code for the right button and the down button right now because (for whatever reason) they were the only ones that acted as expected.

So what happens right now is that as soon as the ROM loads, the tomato slowly moves up and left on its own. I spent a lot of time trying to figure out why - and clearing/setting various stuff (like CLC, SEC) - but all I've figured out is that somehow, every once in a while it breaks through the checks for the button presses.

Stranger still is that Nestopia and FCEUX exhibit this behavior, while Mednafen acts exactly as I would've hoped. It makes me wonder how it works on the real deal, but I get the feeling Nestopia and FCEUX are showing the correct behavior.

I can't work on this any more tonight, but do you guys have any more insight? It most likely IS just me screwing up some carry bit stuff or decimal mode/binary mode stuff, so I'll study that more too when I can.

March 26, 2012, 11:11:29 pm - (Auto Merged - Double Posts are not allowed before 7 days.)
After some quick fiddling around I figured out that if I take the big loop out of the "Forever" loop and stick it in the NMI section (which I'm not really sure what it is) that it all works super nicely, exactly as planned :D If anyone's interested in controlling a little tomato, here you go. It's the greatest game ever, we can end the industry now.

http://tomato.fobby.net/junk/sprites/3/sprites.nes

What's most interesting though is how different emulators handled my weird program differently. I wonder which way is the correct way, and if it somehow produced some bizarre test case never seen in professional releases. Oh well, on to more learning!
« Last Edit: March 26, 2012, 11:11:29 pm by Tomato »

rveach

  • Newbie
  • *
  • Posts: 22
    • View Profile
Re: NES programming help
« Reply #9 on: March 27, 2012, 09:05:57 am »
What's most interesting though is how different emulators handled my weird program differently. I wonder which way is the correct way, and if it somehow produced some bizarre test case never seen in professional releases.

If you want to emulate a game that closely matches what a real NES would behave as, Nintendulator is probably the most accurate emulator.


I'm glad you figured out the wonders of NMI.
You were basically trying to emulate it with that "CMP #120", which as you found out isn't accurate. I was getting a controller read multiple times in a frame which actually caused the tomato to move more than 1 pixel per frame, no matter how lightly I touched the controller.

Someone else can probably explain it better, but basically the NMI is an interrupt that interrupts your main program and tells you when you hit a frame. So naturally all frame logic goes in it (drawing, reading controller, etc).

Edit:

Now it is time to blow your mind:

Code: [Select]
C0CF C9 09 CMP #$09
C0D1 90 06 BCC $C0D9
C0D3 38 SEC
C0D4 E9 01 SBC #$01

You notice you have a BCC after the CMP? BCC is "Branch on Carry Clear". So that means when you drop down to the next statement, your Carry must be set. So there is no point to set the Carry again with "SEC" :).
I know I told you that you would probably need the "SEC"s and "CLC"s with your "SBC"/"ADC" before, but that was when you were using "BEQ", and unless your sure what state your Carry is in, you can't assume it will be set or not.

Edit 2:

NMI Update

Quote
NMI (Non-Maskable Interrupt) is an interrupt generated by the PPU (if you tell it to) when it has finished drawing the current display frame. This is when the so-called "Vertical Blanking" period, or VBlank, starts. This is critical time because it is the only period in which the game can write data to the PPU without risk of causing graphical glitches.
« Last Edit: March 27, 2012, 09:54:56 am by rveach »

Nightcrawler

  • Hero Member
  • *****
  • Posts: 5734
    • View Profile
    • Nightcrawler's Translation Corporation
Re: NES programming help
« Reply #10 on: March 27, 2012, 10:17:01 am »
You notice you have a BCC after the CMP? BCC is "Branch on Carry Clear". So that means when you drop down to the next statement, your Carry must be set. So there is no point to set the Carry again with "SEC" :).
I know I told you that you would probably need the "SEC"s and "CLC"s with your "SBC"/"ADC" before, but that was when you were using "BEQ", and unless your sure what state your Carry is in, you can't assume it will be set or not.

Although it is technically redundant in this case, it's not a terrible idea to put it in during development so you don't end up introducing a bug later when you need to change the branch to operate differently and then don't remember you then need to also add the SEC three months from now. So long as it's there, it's going to work just fine regardless of your chosen branch structure. Sometimes saving headaches is worth more than saving an instruction. Depends on how good your memory is. ;)
TransCorp - Over 15 years of community dedication.
Dual Orb 2, Wozz, Emerald Dragon, Tenshi No Uta, Herakles IV SFC/SNES Translations

Tomato

  • Full Member
  • ***
  • Posts: 226
    • View Profile
    • Legends of Localization
Re: NES programming help
« Reply #11 on: March 27, 2012, 05:50:31 pm »
rveach: Many, many thanks for all your help and tips! That's a good point about the BCC, I hadn't thought it through, I guess I was just happy to see the program actually behaving properly for once, heh.

In dabbling with this simple program, I think what's piqued my interest most is working with all these limitations. So seeing how little RAM there is and how wacky it is to deal with multiple banks is really cool. But what's craziest of all is that programmers back in the 80s were working with a lot less help/documentation/tutorials and still achieved so much - especially after it took me like days to properly move a sprite on a screen :P

KingMike

  • Forum Moderator
  • Hero Member
  • *****
  • Posts: 4727
  • *sigh* A changed avatar. Big deal.
    • View Profile
Re: NES programming help
« Reply #12 on: March 27, 2012, 11:59:00 pm »
Code: [Select]
Forever:

controller_check:
  LDA #$01 ; get ready to read controllers
  STA $4016
  LDA #$00
  STA $4016
  LDA $4016 ; read A button on controller 1
  LDA $4016 ; read B button on controller 1
  LDA $4016 ; read Select button on controller 1
  LDA $4016 ; read Start button on controller 1

check_up:
  LDA $4016 ; read Up button on controller 1
  AND #1
  BEQ check_down        ; if not pressed, move on

  LDA $00               ; if y >= 7 y--
  CMP #08
  BCC check_down
  SBC #1
  STA $00

check_down:
  LDA $4016 ; read Down button on controller 1

check_left:
  LDA $4016 ; read Left button on controller 1
  AND #1
  BEQ check_right       ; if not pressed, move on

  LDA $01               ; if x >= 8 x--
  CMP #09
  BCC check_right
  SBC #1
  STA $01

check_right:
  LDA $4016 ; read Right button on controller 1

check_end:
  LDA $00
  STA $0200             ; set sprite 0's x coordinate
  LDA $01
  STA $0203             ; set sprite 0's y coordinate



  JMP Forever     ; jump back to Forever, infinite loop

I think games would typically read the gamepad ports during the NMI routine, and copy the value to a single RAM byte.
Something like
Code: [Select]
LDX #$08
Loop:
LDA $4016
LSR  ;rotate bit 0 (button status) into Carry bit
ROL $RAMAddress  ;shift bit into RAM byte containing button-press status
DEX
BNE Loop
I know other games have some fancy code to create a further variable to detect which buttons are pressed that frame whereas the above code would only detect buttons held.
But I don't have an example of that.

Almost, if not all, NES games use DMA to write to the sprite RAM.
You reserve a full page of RAM (such as $0200-02FF) for the sprite table, and then (usually in the NMI routine) write the page number (in that case, $02) to $4014.
From what I've heard, the NES sprite RAM is not automatically refreshed and so over time the data would be corrupted. Using the DMA every NMI solves that problem.
Quote
Sir Howard Stringer, chief executive of Sony, on Christmas sales of the PS3:
"It's a little fortuitous that the Wii is running out of hardware."

Bregalad

  • Hero Member
  • *****
  • Posts: 1595
  • Location: Jongny VD, Switzerland
    • View Profile
Re: NES programming help
« Reply #13 on: March 28, 2012, 02:29:01 am »
Quote
Although it is technically redundant in this case, it's not a terrible idea to put it in during development so you don't end up introducing a bug later when you need to change the branch to operate differently and then don't remember you then need to also add the SEC three months from now. So long as it's there, it's going to work just fine regardless of your chosen branch structure. Sometimes saving headaches is worth more than saving an instruction. Depends on how good your memory is. ;)
   
that's what comments are for. Just use a commented sec and so this will "remember" you the following instruction needs a set carry.