Skip navigation
Welcome, Guest! Please Login or Join

Loading...

Advanced Nerdy Nights #1 CHR Bank switching

Mar 12, 2009 at 7:01:26 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
To do the advanced lessons you should have already finished Pong.

This Week: 
As you complete a full game you may find the NROM memory limits to be too small. To enable more ROM on carts many forms of "bank switching" were used. This article deals with just one type of CHR switching, used on CNROM carts. CNROM is easy to use and very cheap to manufacture. The ReproPak, PowerPak, and PowerPak Lite all support CNROM completely so it is easy to get your code running on real hardware. If you are using donor carts you can look up games that use CNROM at BootGod's NES Cart Database.


CHR Bank Switching
Bank switching is exchanging one chunk of ROM for a different chunk, while keeping everything in same address range. It is not making a copy, so it happens instantly. You can switch between different banks whenever you want. The size and memory range of the banks depends on the mapper. For the CNROM mapper used in this article the bank size is 8KB of CHR ROM. The whole 8KB range of PPU memory $0000-1FFF is switched at once. This means the graphics for all background tiles and sprite tiles will be swapped. In your game you may have some tiles duplicated in multiple banks so they do not appear to change on screen. PRG is not bank switched, so it remains at the NROM limit of 32KB.


Set Mapper Number
The first part of adding bank switching is changing the mapper number your .NES file uses. At the top of your code has previously been:

  .inesmap 0 ; mapper 0 = NROM, no bank swapping


The new line is:

  .inesmap 3 ; mapper 3 = CNROM, 8KB CHR ROM bank swapping


This line in the header just tells the emulator to use CNROM to play your game. A list of other iNES mapper numbers can be seen at the wiki at http://nesdevwiki.org/....


Set CHR Size
The next part is to increase the size of your CHR ROM. Change the .ineschr value from 1 to 2, showing that there are now two 8KB banks. CNROM can handle 32KB of CHR ROM or four 8KB banks but this example will only use two.


Add CHR Data
The third part adds the data for the next bank into your game. Just make a new .bank statement below your current one for CHR, giving it the next sequential number. In your code when you set which bank to switch to this is the number used. PRG bank numbers are ignored so your original CHR bank will be #0 and the new one will be #1.


Bank Switching Code
The final part it to write your bank switching code. This subroutine will take a bank number in the A register and switch the CHR bank to it immediately. The actual switch is done by writing the desired bank number anywhere in the $8000-FFFF memory range. The cart hardware sees this write and changes the CHR bank.


... your game code ...

  LDA #$01 ;;put new bank to use into the A register

  JSR Bankswitch ;;jump to bank switching code

... your game code ...


Bankswitch:

  STA $8000 ;;new bank to use

  RTS



Bus Conflicts
When you start running your code on real hardware there is one catch to worry about. For basic mappers, the PRG ROM does not care if it receives a read or a write command. It will respond to both like a read by putting the data on the data bus. This is a problem for bank switching, where the CPU is also trying to put data on the data bus at the same time. They electrically fit in a "bus conflict". The CPU could win, giving you the right value. Or the ROM could win, giving you the wrong value. This is solved by having the ROM and CPU put the same value on the data bus, so there is no conflict. First a table of bank numbers is made, and the value from that table is written to do the bank switch.

... code ...

  LDA #$01 ;;put new bank to use into A

  JSR Bankswitch ;;jump to bank switching code

... code ...


Bankswitch:

  TAX ;;copy A into X

  STA Bankvalues, X ;;new bank to use

  RTS


Bankvalues:

  .db $00, $01, $02, $03 ;;bank numbers


The X register is used as an index into the Bankvalues table, so the value written by the CPU will match the value coming from the ROM.



Putting It All Together

Download and unzip the chrbanks.zip sample files. This set is based on the previous Week 5 code. Make sure that file, mario0.chr, mario1.chr, and chrbanks.bat is in the same folder as NESASM3, then double click on chrbanks.bat. That will run NESASM3 and should produce chrbanks.nes. Run that NES file in FCEUXD SP to see small Mario.

Inside the LatchController subroutine a new section is added to read the Select and Start buttons from the controller. The Select button switches to CHR bank 0, and the Start button switches to CHR bank 1. Graphics of CHR bank 1 have been rearranged so Mario will change to a beetle.  The tile numbers are not changed, but the graphics for those tiles are.

Open the PPU Viewer from the Tools menu in FCEUXD SP and try hitting the buttons.  You can see all the graphics changing at once when the active bank switches.


Edited: 03/13/2009 at 09:06 AM by bunnyboy

Mar 12, 2009 at 7:16:26 PM
MODERATOR
KHAN Games (88)
avatar
(Kevin Hanley) < Master Higgins >
Posts: 7684 - Joined: 06/21/2007
Florida
Profile

Mar 12, 2009 at 7:58:43 PM
Sivak (40)
avatar
(Sivak -) < Kraid Killer >
Posts: 2343 - Joined: 05/04/2007
Ohio
Profile
I'm using UOROM, so it's all PRG for me. This is nothing new.

I am curious, however... Is there a different value to use if, say, you had both PRG and CHR ROM like with MMC1 or MMC3?

-------------------------
My website: Here

Battle Kid 2 demo videos: Playlist
Battle Kid demo videos: Playlist

Check out my current: Want list
Check out my current: Extras list

Mar 12, 2009 at 9:13:05 PM
arch_8ngel (66)
avatar
(Nathan ?) < Mario >
Posts: 31830 - Joined: 06/12/2007
Virginia
Profile
Awesome. I was wondering if these were ever going to continue.

-------------------------
 

Mar 12, 2009 at 9:59:49 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
This was a requested topic for a game in development If anyone else is working on something and needs help I can cover other things like 16 bit math, more collisions, fading, etc.

Mappers like MMC1 and MMC3 use more than a single write to do bank switching. They also have more logic so the ROM doesn't respond to a write command, meaning no bus conflicts.

Mar 13, 2009 at 1:14:08 AM
MetalSlime (0)
avatar
(Thomas Hjelm) < Crack Trooper >
Posts: 140 - Joined: 08/14/2008
Japan
Profile
Originally posted by: bunnyboy

Bank Switching Code
The final part it to write your bank switching code. This subroutine will take a bank number in the x register and switch the CHR bank to it immediately. The actual switch is done by writing the desired bank number anywhere in the $8000-FFFF memory range. The cart hardware sees this write and changes the CHR bank.


... your game code ...

LDA #$01 ;;put new bank to use into X register

JSR Bankswitch ;;jump to bank switching code

... your game code ...


Bankswitch:

STA $8000 ;;new bank to use

RTS





Nice to see Nerdy Nights back.

I found a little typo in the above part.  You talk about using the X register (both in the paragraph and in the code comments) but you do everything with A.  I don't think anyone who has made it far enough to complete pong will get confused by that, but for completeness' sake, you might want to fix it

-------------------------
MetalSlime runs away

My nesdev blog: http://tummaigames.com/blog...

Mar 13, 2009 at 9:07:19 AM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
Fixed! Thanks!

Mar 13, 2009 at 1:11:50 PM
MODERATOR
KHAN Games (88)
avatar
(Kevin Hanley) < Master Higgins >
Posts: 7684 - Joined: 06/21/2007
Florida
Profile
I saw it. Just wanted to keep bunnyboy on his toes.

Okay, I didn't see it.

Mar 13, 2009 at 1:41:04 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
Nobody should read comments anyways, they are for the weak minded. Don't ban me.

Mar 14, 2009 at 12:01:13 PM
Stan (81)
avatar
(Demonologist and Linguist Supreme) < Ridley Wrangler >
Posts: 2753 - Joined: 12/31/2006
Virginia
Profile
Excellent, been looking for some good info on banking. Great job man, very helpful.

Apr 17, 2009 at 7:23:56 PM
Rachel (10)
avatar
(Player of Games, Killer of Threads) < Eggplant Wizard >
Posts: 334 - Joined: 05/24/2008
Texas
Profile
Thank you! I'll be trying this out on my game tonight!

-------------------------
Resident collector of and expert on vintage girly games and consoles--especially rare stuff! 

Currently playing: Miitomo (iOS), Yoshi's Woolly World (WiiU)

May 1, 2009 at 1:29:23 PM
Cthulhu32 (0)
avatar
(Luke Arntson) < Tourian Tourist >
Posts: 47 - Joined: 06/24/2008
Colorado
Profile
Originally posted by: bunnyboy

Bus Conflicts
When you start running your code on real hardware there is one catch to worry about. For basic mappers, the PRG ROM does not care if it receives a read or a write command. It will respond to both like a read by putting the data on the data bus. This is a problem for bank switching, where the CPU is also trying to put data on the data bus at the same time. They electrically fit in a "bus conflict". The CPU could win, giving you the right value. Or the ROM could win, giving you the wrong value. This is solved by having the ROM and CPU put the same value on the data bus, so there is no conflict. First a table of bank numbers is made, and the value from that table is written to do the bank switch.

... code ...

  LDA #$01 ;;put new bank to use into A

  JSR Bankswitch ;;jump to bank switching code

... code ...


Bankswitch:

  TAX ;;copy A into X

  STA Bankvalues, X ;;new bank to use

  RTS


Bankvalues:

  .db $00, $01, $02, $03 ;;bank numbers


The X register is used as an index into the Bankvalues table, so the value written by the CPU will match the value coming from the ROM.




Quick clarification on this code: is the reason you do a TAX is because that will set the $0000 CPU to the same value that is being set at $8000? And you are storing A into BankValues because BankValues is a .db rom value, which means its at $8000?

Does this also mean you don't need the extra offset at all, you JUST need the TAX to force the same value thats going into $8000 to be in $0000?

For example:

Bankswitch:
  TAX                    ;;set the CPU to A ?
  STA $8000       ;;set the ROM to A ?
  RTS

The way I came to this conclusion is because if Bankvalues was the only .db in the file, Bankvalues would be $8000, which means a step along the assembler compilng, STA Bankvalues, X would become STA $8000, X. 

And because the NES cpu doesn't care if you set $8000, $8001, $8002, or $8003 with the map, you could just simplify this to always setting $8000.

I am probably missing a step here in how you write the bankvalues, so any clarifications are greatly appreciated Thanks for the awesome tutorials!


-------------------------
 

May 7, 2009 at 2:30:17 AM
frantik (12)

(fr antik) < Eggplant Wizard >
Posts: 316 - Joined: 04/05/2009
California
Profile
.db causes the bytes to be written at that point in the code. data doesn't automatically start at $8000 or anything.. even your program code won't start at $8000 unless you tell the assembler to do so with an org command. (well the pointers won't start at $8000)

you could do something like

LDA #$03
STA Bankvalue + #$03

if you knew you wanted to switch to bank 3.. but it's better to reuse the general bank switching function.. that way if you end up switching mappers you only ahve to change one area of code

-------------------------
Super Mario Unlimited - My epic SMB hack
My custom NES Arcade joystick

May 7, 2009 at 12:05:26 PM
Cthulhu32 (0)
avatar
(Luke Arntson) < Tourian Tourist >
Posts: 47 - Joined: 06/24/2008
Colorado
Profile
Originally posted by: frantik

.db causes the bytes to be written at that point in the code. data doesn't automatically start at $8000 or anything.. even your program code won't start at $8000 unless you tell the assembler to do so with an org command. (well the pointers won't start at $8000)

you could do something like

LDA #$03
STA Bankvalue + #$03

if you knew you wanted to switch to bank 3.. but it's better to reuse the general bank switching function.. that way if you end up switching mappers you only ahve to change one area of code

Ahh okay, so when you LDA #$03 you're putting 3 into the A register (CPU), and then storing it into the BankValue (ROM) which signals the bank switch? This way the CPU & ROM match?

I think I understand Bunnyboy's code now as well, the reason why he has bankvalues laid out as .db $00,$01,$02,$03 is because you're literally writing the same value on top of a ROM (which just triggers a bank switch)

So LDA #$01 sets the accumulator (CPU), TAX; STA Bankvalues, X; overwrites Bankvalues without modifying A, and because X = A, X is identical to the values inside of Bankvalues, you're not changing any data, you're just triggering a bankswitch.

Thanks for the clarification!

-------------------------
 

May 7, 2009 at 4:18:40 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
Originally posted by: Cthulhu32

Ahh okay, so when you LDA #$03 you're putting 3 into the A register (CPU), and then storing it into the BankValue (ROM) which signals the bank switch? This way the CPU & ROM match?


That's the key, the values have to match or you will get the wrong result in the bankswitch.  ROM is read only so you can't change the data there.

May 7, 2009 at 8:24:48 PM
frantik (12)

(fr antik) < Eggplant Wizard >
Posts: 316 - Joined: 04/05/2009
California
Profile
You only have to do that for mappers with "bus conflicts". You're writing to memory which is supposed to be Read Only (ROM). Instead of writing, you actually cause the mapper to do things. but mappers with bus conflicts get messed up if you don't write the same value which is contained in the ROM

-------------------------
Super Mario Unlimited - My epic SMB hack
My custom NES Arcade joystick

Aug 9, 2009 at 6:17:11 PM
Stan (81)
avatar
(Demonologist and Linguist Supreme) < Ridley Wrangler >
Posts: 2753 - Joined: 12/31/2006
Virginia
Profile
Great stuff buns, very helpful for where I am in SMS programming actually. Glad you're keeping it going.

Aug 29, 2009 at 10:50:57 PM
Mario's Right Nut (350)
avatar
(Cunt Punch) < Bowser >
Posts: 6574 - Joined: 11/21/2008
Texas
Profile
Any chance you would consider writing one of these for MMC1?

-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Mar 10, 2011 at 10:33:01 PM
Rachel (10)
avatar
(Player of Games, Killer of Threads) < Eggplant Wizard >
Posts: 334 - Joined: 05/24/2008
Texas
Profile
Hey, totally random question on a blast-from-the-past thread... This method works for me when I'm using 2 or 4 CHR banks. However, games with 3 CHR banks do not pull from the proper tileset when running on the PowerPak/NES. (They work fine in Nintendulator, though.) A game that uses 3 CHR banks will only work for me if I set .ineschr to 4 and duplicate one of the banks. I'm continuing to experiment, but I wonder if anyone has any thoughts as to why this might be happening. Just curious.

-------------------------
Resident collector of and expert on vintage girly games and consoles--especially rare stuff! 

Currently playing: Miitomo (iOS), Yoshi's Woolly World (WiiU)

Mar 10, 2011 at 10:49:25 PM
removed04092017 (0)
This user has been banned -- click for more information.
< Bowser >
Posts: 7316 - Joined: 12/04/2010
Other
Profile
Because there's pretty much no games/hardware that uses a number of banks not to 2^bankselectnumberofpins, which is why you need to have 1,2,4,8,16,32,64,128,256 character banks.

Mar 10, 2011 at 10:50:28 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7440 - Joined: 02/28/2007
California
Profile
Memory chips come in powers of 2, so your total CHR and PRG sizes should also be powers of 2. When you use 3 banks different emulators and systems will fill in the missing area in different ways. Some will leave it empty, some will extend the first bank or last bank.

Mar 11, 2011 at 8:31:43 PM
MODERATOR
KHAN Games (88)
avatar
(Kevin Hanley) < Master Higgins >
Posts: 7684 - Joined: 06/21/2007
Florida
Profile
YEAH! RACHEL!!!!!!!!!!!!!!! Keep on codin' girl.

Mar 11, 2011 at 9:09:05 PM
Rachel (10)
avatar
(Player of Games, Killer of Threads) < Eggplant Wizard >
Posts: 334 - Joined: 05/24/2008
Texas
Profile
Yeah, that's what I guessed, too. I was just surprised both in the results I got and that none of the documentation mentioned that point. Maybe 'cause it was obvious to everyone else, hehe . And thanks, Kevin! You know what I'm up to .

-------------------------
Resident collector of and expert on vintage girly games and consoles--especially rare stuff! 

Currently playing: Miitomo (iOS), Yoshi's Woolly World (WiiU)

Mar 25, 2014 at 8:17:11 AM
Mega Mario Man (51)
avatar
(Tim W) < Kraid Killer >
Posts: 2193 - Joined: 02/13/2014
Nebraska
Profile
FYI:

Dead link. http://nesdevwiki.org/..." target="_blank">http://nesdevwiki.org/...

Can you please update to a new site (maybe something better has come along?) or link to an archive on archive.org?
https://web.archive.org/web/...*/http://nesdevwiki.org/

Thanks.

Apr 2, 2014 at 11:41:03 PM
Mega Mario Man (51)
avatar
(Tim W) < Kraid Killer >
Posts: 2193 - Joined: 02/13/2014
Nebraska
Profile
I understand what is happening here, but can I get some examples on why this would be used? Is it just for getting an extra set of character tiles if you run out of room in the first?

The hardest part of learning the code in NES 6502 is understanding why you must do something. Most of the time I can follow the code and understand what it does, I'm just not far enough along to see how it will be useful.

Thanks in advance. I know I keep on bringing up dead threads. My bad.


Edited: 04/02/2014 at 11:57 PM by Mega Mario Man