[This Documentation put together by Luis Huang. NES programming help taken from nesdev http://nesdev.parodius.com/NESprgmn.txt and nBasic programming taken from Bob Rost http://bobrost.com/nes/ He also has nBasic reference manual there. http://bobrost.com/nes/files/nbasic_manual.html Also there some really good stuff at Nerdy Nights http://www.nintendoage.com/forum/messageview.cfm?catid=22&threadid=7155&highlight_key=y&keyword1=nerdy%20nights Additionally, I will make references to Felix Chou's documentation. My own emphasis made with asterisks*****) SOME BASICS ________________________ First some basic overview of the NES architecture (you will need to understand this to work effectively with nBasic) (For more I refer you to Nerdy Nights) ROM - Read Only Memory, holds data that cannot be changed. This is where the game code or graphics is stored on the cart. RAM - Random Access Memory, holds data that can be read and written. When power is removed, the chip is erased. A battery can be used to keep power and data valid. PRG - Program memory, the code for the game CHR - Character memory, the data for graphics CPU - Central Processing Unit, the main processor chip PPU - Picture Processing Unit, the graphics chip APU - Audio Processing Unit, the sound chip inside the CPU We're now ready to take a quick look at a sample code. Codes usually start with HEADER (Information about the ROM) Number of PRG banks (16k each) Number of CHR banks (8k, 2 tables each) Mirroring Type (0 = horizontal, 1 = vertical) Memory Mapper (0 = none, 4 = MMC3) NES can only address 64k of memory, and only 32k for ROM (code and data), large games need to be able to swap areas of memory in and out. Usually have a16k bank for code and swap 16k of level data. MMC3... (see Rost Feb 18) Here's an example: //this is C++-style comment (see Bob Rost nBasic reference manual) asm .inesprg 1 ;//one PRG bank .ineschr 1 ;//one CHR bank .inesmir 0 ;//mirroring type 1 (vertical mirroring) .inesmap 0 ;//memory mapper 0 (none) .org $8000 .bank 0 endasm //asm signals coding in assembly //to end, you need endasm //.INESPRG - Specifies the number of 16k prg banks. //.INESCHR - Specifies the number of 8k chr banks. //.INESMAP - Specifies the NES mapper used. //.INESMIR - Specifies VRAM mirroring of the banks. // //bank 1 - usually needed or NESASM gets cranky? Okay, one little thing before coding. The code must '.org' at $8000 or $C000 and it must be in bank 0. I'm pretty sure this is right. All my stuff is going to use bank 0 and $8000 but that's just me. ***In ROM source, you may use assembly to declare bank boundaries, usually base address at $8000*** The Vector table are a set of three addresses that are stored in a certain memory location in the NES ROM($FFFA to be exact). Here's an example: .bank 1 ;needed or NESASM gets cranky?? .org $FFFA ;ditto .dw ;NMI_Routine($FFFA) .dw ;Reset_Routine($FFFC) .dw ;IRQ_Routine($FFFE) ***NMI_Routine means Non-Maskable Interrupt. This address is called different times on different systems(PAL/NSTC). Its called every VBlank(screen refresh). Nesticle does trace this when using its code trace. Choose CPU->Debug->Vint to jump to the NMI code. The Reset_Routine is jumped to when the game is powered up and when it is reset. When the game is reset, the NES does not clear the memory, nor the registers. I guess so programmers can count how many times the game has been reset. On a more serious note, this can could cause problems if you're not careful with how you store and read data. Finally, the ***IRQ_Routine is the address that is jumped to when a BRK instruction is hit. I have no idea how you could use this. Maybe for error checking or something(shrug). You don't need to have actual values for these addresses. Well, you do for address $FFFC since it does tell the NES were your code is. If you don't use it, turn NMI($FFFA) off(more on that later) and put a 0 as the address. You don't even need to put a value in $FFFE, just make sure there are no BRK instructions. ***These will be important when we need to load sprites onto our system*** ------------------------ Code Comments Comments are created by using any of several common conventions. A comment may begin at any location in a line, and the rest of teh line is ignored by the compiler. // this is a C++-style comment # this is a shell-style comment ; this is a nesasm-style comment (for inline assembly) ;// this nesasm-style comment shows up colored in my C++ editor HOW A GAME WORKS ________________________ (See Bob Rost Lecture Jan 28) -Initialization -set up graphics -set initial values -The Game Loop -Draw the screen -Get player input -Calculate the next frame Example nBasic game: start: gosub init_screen gosub init_vars mainloop: gosub vwait //wait for next frame gosub draw_stuff //draw the new frame gosub handle_joystick //get input gosub calculate_things //game logic gosub update_sound //make some noise goto mainloop ***Notes: The NES has BASIC-like program flow. It relies on goto, gosub and return for most execution flow. We first begin at "start" where we initialize initial values and set up graphics. Then we go to the "mainloop" where we set our main commands (in this case we go to subroutines that draws frame, gets input, makes noise, etc.) gosub and goto commands are nBasic commands. Program glow is controlled by jumps, which allow your program to move the execution point to a label. A goto will simply move the current execution point, as it does in BASiC. A gosub is also just like in BASIC; it moves the current execution point, but it also saves the previous execution point on the stack. The return statement will return execution to just after the point from which the jump was mmade. Note that if you call gosub rampantly and do not have a return for each one, your program will overflow the 256 byte stack, which will crash the game. my_label: gosub my_function goto my_label my_function: return COMPILING AND ASSEMBLING (taken from Bob Rost) ________________________ Let's talk a little about compiling in nBasic (with Windows) For this step, you will need to use the command line. Many Windows machines have a command prompt available from the Start Menu / Programs / Accessories. Another way to open a command is to go to the Start Menu, and select the "Run..." command, or hit the Window+R key. From the Run menu, enter command (for Windows 95/98), or cmd (for Windows NT/2000/XP). Once you have the command prompt open, change to the directory where you unzipped the assignment. If you unzipped to the Desktop and it created a "nes1" folder, you will want to change to the nes1 folder. On most Windows machines, you can type "cd " (with a space) into the command line and then drag the nes1 folder into the command line to complete the line. Then hit enter. If this doesn't work, try one of the following commands, or panic. cd "desktop\nes1" cd "\windows\desktop\nes1" cd "\documents and settings\Luis Huang\desktop\nes1" # ****** Make sure nbasic.exe, nesasm.exe, cygwin1.dll, and all the .bas/.chr files are in the same folder # Run the following commands from the command prompt (you can also create a .bat file for these commands) * nbasic main.bas common.bas footer.bas -o main.asm (this will generate main.asm) * nesasm main.asm (this will generate main.nes) *****The main.bas is where you your code originates. you can name it anything, just include .bas after it. Larger games usually includes common.bas footer.bas. Include if you have these. Command followed by -o. name.asm is the resulting file. 2nd line of command generates the NES file from the ASM file. New NES file should appear. If no problems, "Compile completed sucessfully" and "pass 1" and "pass 2" should appear.********* ***To try this out yourself, please refer to Felix Chou on how to do Assignment 1 (Hello World) of Bob Rost's class. The last step to this is to get your NES and test it with an emulator. ****Find a NES emulator and try it out. I suggest Jnes or FCU Ultra for Windows. (For more: http://bobrost.com/nes/assignments.php) GRAPHICS ________________________ Palette ------------------------ Showing graphics on the NES is composed of CHR-ROM, PPU control, the palette(the colors! THE COLORS!!!!), and some other fun things. ***The first thing to learn about using graphics is how the system uses colors. There is a NES color palette. There are two separate palettes, each 16 bytes. One palette is used for the background, and the other for sprites. The byte in the palette corresponds to one of the 64 base colors the NES can display. (Please refer to color attached color palette.) Note, however, make sure not to use $0D (bad color). Colors are not exact and will look slightly different depending on your emulators or TVs. Next let's talk about loading the background palettes using a sample code: For the NES, the background palette has PPU addres $3F00, and foreground palette address $3F10. We need to write te global palette indices to PPU memory Sample code: load_palette: //set the PPU start address (background color 0) set $2006 $3f set $2006 $00 set $2007 $0e //set base color black 0e set $2007 $30 //set the PPU start address (foreground color 1) set $2006 $3f set $2006 $11 set $2007 $30 //set fg color 1 white return ****This subroutine allows us to set the background and foreground color palette. Here we set background to black (0E) and foreground to white (30). As we can see, the PPU address port $2006 is used. Sprites ------------------------ Anything that moves separately from the background will be made of sprites. A sprite is just an 8x8 pixel tile that the PPU renders anywhere on the screen. Generally objects are made from multiple sprites next to each other. Examples would be Mario and any of the enemies like Goombas and Bowser. The PPU has enough internal memory for 64 sprites. This memory is separate from all other video memory and cannot be expanded. There are two methods for drawing sprites in nBasic. Standard (slow) method, and the apparently much more awesome (aptly named) awesome (DMA) method. First, the standard boring slow method... Standard Method Works for small numbers of sprites. Start by writing the starting sprite memory byte to $2003 (sprite number * 4). Then you start writing sprite memory to $2004 as internal pointer auto-increments. Example code: start: go sub vwait set $2000 %00000000 set $2001 %00011100 //sprites and bg visible, no sprite clipping gosub init_vars gosub vwait gosub load_palette mainloop: gosub joy_handler gosub vwait gosub drawstuff goto mainloop init_vars: set a_pressed 0 set b_pressed 0 set a_inc 0 set b_inc 0 set spritenum 65 set spritex 128 set spritey 120 return //after we set our initial variables, we load palette (which we discussed //previously) next we have the subroutine to draw a sprite drawstuff: set $2003 0 //sprite memory loc 0 set $2004 spritey //y set $2004 spritenum //tile number set $2004 0 //attrib set $2004 spritex //x return (TOO MUCH INFO. TO BE CONT.) Adding CHR-ROM (In ASM) ---------------- Okay, now that you know about using the PPU, its time to learn how to add CHR-ROM so you have something to look at. Looking at the header, we see: .inesprg 1 .ineschr 0 .inesmir 1 .inesmap 0 Now, that 0 is telling NESASM that we have 0 banks of CHR-ROM. Every time you add a bank of CHR-ROM, you need to increase that value by one. Since we're only going to have 1 bank of data just change that 0 to a 1. .inesprg 1 .ineschr 1 .inesmir 1 .inesmap 0 We're not done yet. Now we have to add the actual CHR-ROM to the rom and load it into the pattern table. Which is easily added like this: .bank 2 .org $0000 .incbin "test.chr" ;gotta be 8192 bytes long Since there is only one chunk of CHR-ROM, we place it at address $0000. Address $0000 is where the NES starts to look for character data. CHR-ROM goes from $0000 to $2000 in the NES ram and is called the pattern table. Hence the 8192 byte length of test.chr. I'm not sure how to switch CHR-ROM from different banks without using a mapper. Checkout y0shi's old NES doc or \Firebug\'s mapper.nfo doc. Good stuff. I did come up with a code workaround though. It will be discussed later. But let's look at the code we got going so far: ----------------------------------------- asm .inesprg 1 .ineschr 1 ;// sets chr data .inesmir 1 .inesmap 0 .org $8000 .bank 0 Start: ;//this setups the PPU lda #%00001000 sta $2000 lda #%00011110 sta $2001 Loop: jmp Loop .bank 1 .org $FFFA .dw 0 ;(NMI_Routine) .dw Start ;(Reset_Routine) .dw 0 ;(IRQ_Routine) .bank 2 .org $0000 .incbin "test.chr" ;gotta be 8192 bytes long --------------------------------------------------------------- We're not done yet though. If you run this code, you'll get nothing but a blank screen. That's because we haven't told the NES about the palette. Which brings us to our next section! VWAIT ________________________ We've seen vwait command several times now, we might as well talk about it a little bit. As a rule, it is generally good to wait 2 vblanks at the start start: set a 0 set $2000 a set $2001 a //turn off the PPU asm sei ;//disable interrupts endasm gosub vwait //***** gosub vwait //*****here it's good to wait 2 vblanks at the start asm ldx #$ff txs ;//reset the stack endasm set $2000 %10101000 //NMI, 8x16 sprites, bg 0, fg 1 set $2001 %00011000 //show sprites, bg, clipping gosub load_palette gosub init_vars vwait: gosub vwait_start gosub vwait_end return //wait until start of vertical retrace vwait_start: asm lda $2002 bpl vwait_start endasm return //wait until end of vertical retrace vwait_end: asm lda $2002 bmi vwait_end endasm //set scroll and PPU base address set a 0 set $2005 a set $2005 a set $2006 a set $2006 a return SCROLLING (from nesdev) ______________ the current information on background scrolling is sufficient for most games; however, there are a few that require a more complete understanding here are the related registers: (v) vram address, a.k.a. 2006 which we all know and love. (16 bits) (t) another temp vram address (16 bits) (you can really call them 15 bits, the last isn't used) (x) tile X offset (3 bits) the ppu uses the vram address for both reading/writing to vram thru 2007, and for fetching nametable data to draw the background. as it's drawing the background, it updates the address to point to the nametable data currently being drawn. bits 0-11 hold the nametable address (-$2000). bits 12-14 are the tile Y offset. --------- stuff that affects register contents: (sorry for the shorthand logic but i think it's easier to see this way) 2000 write: t:0000110000000000=d:00000011 2005 first write: t:0000000000011111=d:11111000 x=d:00000111 2005 second write: t:0000001111100000=d:11111000 t:0111000000000000=d:00000111 2006 first write: t:0011111100000000=d:00111111 t:1100000000000000=0 2006 second write: t:0000000011111111=d:11111111 v=t scanline start (if background and sprites are enabled): v:0000010000011111=t:0000010000011111 frame start (line 0) (if background and sprites are enabled): v=t note! 2005 and 2006 share the toggle that selects between first/second writes. reading 2002 will clear it. note! all of this info agrees with the tests i've run on a real nes. BUT if there's something you don't agree with, please let me know so i can verify it. (more notes on ppu logic) you can think of bits 0,1,2,3,4 of the vram address as the "x scroll"(*8) that the ppu increments as it draws. as it wraps from 31 to 0, bit 10 is switched. you should see how this causes horizontal wrapping between name tables (0,1) and (2,3). you can think of bits 5,6,7,8,9 as the "y scroll"(*8). this functions slightly different from the X. it wraps to 0 and bit 11 is switched when it's incremented from _29_ instead of 31. there are some odd side effects from this.. if you manually set the value above 29 (from either 2005 or 2006), the wrapping from 29 obviously won't happen, and attrib data will be used as name table data. the "y scroll" still wraps to 0 from 31, but without switching bit 11. this explains why writing 240+ to 'Y' in 2005 appeared as a negative scroll value. NES MUSIC (Taken from Felix Chou and Bob Rost) __________________ 6 Octave range 4 Music Channels 0:Square wave 1 1:Square wave 2 2:Triangle wave 3:Noise channel Steps: 1. Write music.mus 2. Use nesmus to compile music.dat 3. Input music.dat and music.asm to nesasm 4. Use nesasm to generate music.nes ***find nesmus on Bob Rost's website REFERENCES ________________________ http://www.nintendoage.com/forum/messageview.cfm?catid=22&threadid=7155&highlight_key=y&keyword1=nerdy%20nights http://bobrost.com/nes/ http://nesdev.parodius.com/NESprgmn.txt http://sourceforge.net/search/?words=nes+emulator&sort=score&sortdir=desc&offset=10&type_of_search=soft&pmode=0