Jump to content
IGNORED

Bank switching, best practices?


Recommended Posts

Help!

 

My first programming project for the 2600 has now reached the 4K limit and I desperately need to learn bank switching basics...

 

Are there any useful resources (tutorials, sample code etc) on how to do bank switching? Good compatibility with hardware, e.g. Pixel Past's PCBs, would be a big plus...

 

Regards,

Moderntimes99

Link to comment
Share on other sites

Basically, in DASM, your code will look like this:

 

;---BANK ONE---

  org $D000
  rorg $F000

;--code code code

  org $DFFA
  rorg $FFFA

;--bank one vectors here.

;---BANK TWO

  org $E000
  rorg $F000

;---code, code, code, code


  org $EFFA
  rorg $FFFA

;--bank two vectors here: make sure they point to bank two locations!

Make sure you don't have any code past $xFF7 or thereabouts (depending on how big your binary is).

 

To switch banks, just hit the hotspots; like this:

   org $DD00
  rorg $FD00   ;--this location doesn't matter; just as an example.

  sta $1FF9

And this will immediately transfer control to bank 2 at location $ED00.

 

Note that the actual addresses ($Dxxx, $Exxx) don't matter, pick $1xxx, $2xxx, etc.; everything just needs to be rorged to $Fxxx.

Edited by vdub_bobby
Link to comment
Share on other sites

Help!

 

My first programming project for the 2600 has now reached the 4K limit and I desperately need to learn bank switching basics...

 

Are there any useful resources (tutorials, sample code etc) on how to do bank switching? Good compatibility with hardware, e.g. Pixel Past's PCBs, would be a big plus...

 

Regards,

Moderntimes99

1022041[/snapback]

 

There are several different schemes that were developed for bankswitching, so the "best practices' will depend on which scheme you choose.

 

An excellent document to start with is the one that Kevin Horton wrote, which I gather is pretty much the "bankswitching bible." The actual descriptions of the different bankswitching schemes is a bit sketchy, and you must read them very carefully, then work out all of the details by putting 2 and 2 together so to speak.

 

http://www.tripoint.org/kevtris/files/sizes.txt

 

Actually, I think there might be another version of that document, also by Kevin Horton, but I don't have a link for it right now.

 

The bankswitching schemes described in that document are the ones that were developed and used for the Atari 2600 "back in the day," and some of them may not be very feasible to use today as far as actually manufacturing cartridges for new games. Also, some new bankswitching schemes have been developed more recently, such as the one developed for the "Homestar Runner" game, or the one that supercat is working on.

 

Anyway, according to one of Kevin Horton's documents, the "traditional" schemes can be divided into three basic types, as categorized by the sizes of the banks:

 

(1) Bankswitching schemes where each bank is 4K in size. When you switch banks, the entire 4K ROM cartridge area is switched. In these schemes, when you switch banks, you typically end up in the same spot but in a different bank, hence it can be a little tricky to switch banks if you aren't sure what you're doing. (As I recall, at least one 4K scheme-- used by Activision-- switches banks whenever you JSR or RTS, so you don't end up in the same spot when you switch banks; instead, you end up wherever you JSR-ed to or RTS-ed to.) There must generally be a certain amount of code that's duplicated in each bank, such as the vectors at the top of the ROM ($FFFC/$FFFD and $FFFE/$FFFF), or any code or program data that must be available "at all times," regardless of which bank you're in.

 

(2) Bankswitching schemes where each bank is 2K in size. The bank at the upper 2K area ($F800 to $FFFF) is fixed-- it never gets switched-- but the other banks can be switched into the lower 2K area ($F000 to $F7FF). This sort of scheme can be easier for some people to use, because you can be sitting in the fixed bank in the upper 2K area, switch the bank in the lower 2K area, then JMP or JSR to code in the lower 2K area. When you're done, you can JMP or RTS back to the upper 2K area to switch banks again. Of course, you can also switch banks while you're in the lower 2K area, the same way that most 4K schemes work.

 

(3) Bankswitching schemes where each bank is 1K in size. The bank at the upper 1K area ($FC00 to $FFFF) is fixed, and (as I understand it) any of the other banks can be switched into any of the other three 1K areas ($F000 to $F3FF, $F400 to $F7FF, and $F800 to $FBFF). This sort of scheme is the most flexible, since you can switch a bank while you're sitting in that area (as in the 4K schemes), or be in the fixed bank while switching banks in another area (as in the 2K schemes), or be in one of the switchable 1K bank areas while switching banks in another 1K area.

 

Using bankswitching is actually pretty easy, once you wrap your brain around the basic concepts.

 

The first hurdle is understanding how to code each bank in your program, using DASM or whatever other assembler you prefer. As I see it, there are three ways you can do this (at least, I've done it three different ways):

 

(1) You can code each bank (1K, 2K, or 4K) as though it's a separate program so to speak, using the appropriate ORG address (or whatever your chosen assembler uses), compile the banks separately, and then join the individual compiled banks into a single ROM image using some kind of file-joining utility. This is the method I used first, before I knew better. If there's a "wrong" way to write bankswitched programs, this is probably it! :)

 

(2) You can code each bank as though it's a separate program (exactly the same as above), but use INCBIN commands in the final bank to add the other compiled banks into the last bank while you're compiling it. This is the second method that I used, but it's only marginally less stupid than the first method. :)

 

(3) You can code all of the banks at the same time, in the same source code. This is the best way to do it. You must use combinations of ORG and RORG commands (or whatever your chosen assembler uses) to make sure that each bank gets put in the correct location in the final compiled ROM, while simultaneously relocating any address references to the correct region(s) between $F000 and $FFFF.

 

Assuming you're using DASM, and assuming you're using the third approach, the exact organization of the code will depend on which bankswitching scheme you're using-- not just the sizes of the individual banks, but also the number of banks. In a nutshell, if you number each bank from 1 to whatever, then each bank must be placed in order in memory, consecutively from 1 to whatever, with no gaps in the addresses. Each bank's "physical" starting address will be determined by its size and its number. For example:

 

If you're writing a 2K non-bankswitched game, it's located from $F800 to $FFFF.

 

If you're writing a 4K non-bankswitched game, it's located from $F000 to $FFFF.

 

If you're writing an 8K bankswitched game, where each bank is 4K, then there are two banks in all-- 1 and 2-- so bank_1 is located from $E000 to $EFFF, and bank_2 is located from $F000 to $FFFF. However, since bank_1 will be seen in the normal 4K area when it's selected, you must relocate bank_1 to $F000 to $FFFF, as follows:

    ORG  $E000   ; Beginning of bank_1, size 4K,
    RORG $F000   ; but we must relocate it to the actual 4K ROM area.
;
; code for bank_1 goes here
;
    ORG  $F000   ; Beginning of bank_2, size 4K,
    RORG $F000   ; which also gets seen in the actual 4K ROM area.
;
; code for bank_2 goes here

 

If you're writing an 8K bankswitched game, where each bank is 2K, then there are four banks in all-- 1, 2, 3, and 4-- structured/located as follows:

    ORG  $E000   ; Beginning of bank_1, size 2K,
    RORG $F000   ; but we must relocate it to the lower 2K ROM area.
;
; code for bank_1 goes here
;
    ORG  $E800   ; Beginning of bank_2, size 2K,
    RORG $F000   ; which also gets relocated to the lower 2K ROM area.
;
; code for bank_2 goes here
;
    ORG  $F000   ; Beginning of bank_3, size 2K,
    RORG $F000   ; which also gets seen in the lower 2K ROM area.
;
; code for bank_3 goes here
;
    ORG  $F800   ; Beginning of bank_4, size 2K,
    RORG $F800   ; which is fixed in the upper 2K ROM area.
;
; code for bank_4 goes here

 

If you're writing an 8K bankswitched game, where each bank is 1K, then there are eight banks in all-- 1, 2, 3, 4, 5, 6, 7, and 8-- structured/located as follows:

    ORG  $E000   ; Beginning of bank_1, size 1K,
    RORG $????   ; relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_1 goes here
;
    ORG  $E400   ; Beginning of bank_2, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_2 goes here
;
    ORG  $E800   ; Beginning of bank_3, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_3 goes here
;
    ORG  $EC00   ; Beginning of bank_4, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_4 goes here
;
    ORG  $F000   ; Beginning of bank_5, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_5 goes here
;
    ORG  $F400   ; Beginning of bank_6, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_6 goes here
;
    ORG  $F800   ; Beginning of bank_7, size 1K,
    RORG $????   ; also relocatable to any of the lower 1K ROM areas (?).
;
; code for bank_7 goes here
;
    ORG  $FC00   ; Beginning of bank_8, size 1K,
    RORG $FC00   ; which is fixed in the uppermost 1K ROM area.
;
; code for bank_8 goes here

I'm not entirely sure about the 1K method, because the description is sketchy or non-existent on the very points that I most have questions about! Presumably, any of the three lower 1K areas ($F000 to $F3FF, $F400 to $F7FF, and $F800 to $FBFF) can hold any of the first seven banks. If that's true, then the RORG addresses for those banks can be set to $F000, $F400, or $F800, depending on which 1K area you intend for that particular bank to be switched into. If a given bank holds game data rather than executable code, then it doesn't matter what the RORG address for that bank is, since none of the addresses in that bank will need to be "fixed" per se. That is, you could switch a data-only bank into any of the three lower 1K areas at different times in your game, as long as the code that's reading the data in that bank is referencing whichever 1K area you've switched that bank into at that particular time. And if the code in a given bank is entirely relocatable-- i.e., it uses branch statements, and the only time it ever uses JMP or JSR is when it's going to an address in some other bank rather than to some address within itself, and it never tries to read any data that's stored within itself-- then the RORG could also point to any of the lower 1K areas. But if the code within a given bank does a JMP or JSR to some address within that same bank, or reads data that's stored within that same bank, then the RORG must be set to a specific 1K area, and that will be the only 1K area that you should ever switch that bank into. I'm not sure that the preceding description is correct, but it's the only way that makes sense if my understanding of this scheme is correct.

 

I won't give "skeletons" for the other schemes, since the preceding examples will give you the general idea. For example, if you're writing a 32K bankswitched game that uses 4K banks, then there will be eight banks in all, and their ORG addresses will be $8000, $9000, $A000, $B000, $C000, $D000, $E000, and $F000, with all of the RORG addresses being $F000.

 

Since the Atari 2600 is in a random state on powerup, it's possible for any bank(s) to be in memory at powerup, and you'll need to take that into consideration. For example, if you're using a scheme with 4K banks, then all of the banks must have identical vectors in their last four bytes, pointing to the startup code, which must be duplicated at the same location in all banks. That way, the Atari will always be able to successfully jump to the startup code regardless of which 4K bank happens to be in memory at powerup. Also, it's best for the startup code to immediately select the bank that you actually want to start in, because this will let you minimize the amount of code that must be duplicated in all banks. For example, with a 4K bank scheme, the startup processing might be as follows:

 

(1) Turn on the Atari. Any one of the 4K banks might be in memory.

 

(2) The processor looks at the address that's pointed to by the vector at $FFFC and $FFFD, and then jumps to that target address. Therefore, all of the 4K banks must have the same lo-byte/hi-byte address coded in $FFFC and $FFFD, and all of the 4K banks must have the same executable code at the target address which that vector points to.

 

(3) When the Atari executes the code at that startup target address, the first thing it should do is select whichever 4K bank you want to have in memory initially. As stated above, the code which does this should be duplicated in all of the 4K banks, so that it won't matter which bank is actually in memory when the Atari executes the instruction to switch to the desired startup bank.

 

(4) As soon as the Atari has executed the instruction to select the desired bank, that desired bank will be in memory, therefore the remaining startup code won't need to be duplicated in all of the other 4K banks.

 

After that, you can pretty much code whatever you want in each of the 4K banks, and switch back and forth between them as desired, as long as you're careful to remember that each time you switch to a different bank, you'll immediately end up in the new bank, but will be in whichever memory location comes after the last instruction you just executed. Note that if you're planning on staying in any of the 4K banks for an appreciable length of time-- i.e., long enough to need to perform the display kernel-- then you must include the kernel in that 4K bank, or at least include *a* kernel in that bank. That is, there's really no reason why it must be the same as the kernel in any of the other 4K banks. For instance, you could have different kernels in different 4K banks if your screen display isn't going to use a single kernel for everything-- e.g., a title screen kernel in one bank, and the main game kernel in another bank, etc.

 

On the other hand, if you're using a scheme that has 2K or 1K banks, then you'll always start in the last bank, at the vector in $FFFC and $FFFD, hence you'll want to point that vector to some address in the last bank, since you'll have no way of predicting which bank is in the other 2K area (if you're using 2K banks), or which three banks are in the other three 1K areas (if you're using 1K banks). This means you don't need to duplicate the startup vector or startup code in the other banks, since none of the other banks can occupy the uppermost 2K or 1K area anyway. Consequently, you can just execute whatever startup code you want in the upper 2K or 1K area, and then start selecting the other bank(s) as desired.

 

As far as the actual technique of switching banks, that will depend on the scheme you're using, since each scheme has its own bankswitching "hotspot" addresses or other methodology for selecting between the different banks. You can learn more about this part of it by reading Kevin Horton's bankswitching documentation.

 

I hope the information I've given above, coupled with the information in Kevin Horton's documentation, is enough to get you started. I suggest that you choose one specific bankswitching scheme and experiment with it exclusively. Then, when you've gotten used to that scheme (or perhaps have decided that it's not for you after all), you can try a different scheme. Learn one at a time, rather than trying to learn all of them at once.

 

Michael Rideout

Link to comment
Share on other sites

If a given bank holds game data rather than executable code, then it doesn't matter what the RORG address for that bank is, since none of the addresses in that bank will need to be "fixed" per se. That is, you could switch a data-only bank into any of the three lower 1K areas at different times in your game, as long as the code that's reading the data in that bank is referencing whichever 1K area you've switched that bank into at that particular time.

1022361[/snapback]

 

You can access the data from any bank, but if you're accessing it in a bank other than the one where it's RORG'ed you'll have to manually adjust the address. For example, if MYTBABLE is RORGed at $1000 and you bank it into $1400, you'll have to access it via:

  lda MYTABLE+$400,X

or something similar.

 

BTW, when using 4K banks, it's often useful to RORG each bank to a different address (though they must all start on odd multiples of 4K). The one caveat with doing so is that if you duplicate code you need to avoid duplicating labels. If all of the code that's duplicated among all banks is RORGed to the same address it won't matter if the labels get duplicated in all the copies since they'll all point to the same thing anyway.

Link to comment
Share on other sites

Help!

 

My first programming project for the 2600 has now reached the 4K limit and I desperately need to learn bank switching basics...

 

Are there any useful resources (tutorials, sample code etc) on how to do bank switching? Good compatibility with hardware, e.g. Pixel Past's PCBs, would be a big plus...

 

Regards,

Moderntimes99

1022041[/snapback]

 

 

Sad, really. Anyone's first '2600 project should, by law, be at *most* 4K.

Without first experiencing and overcoming the absolute necessity to make tradeoffs in design, programming and memory usage... you're missing out on one of the main reasons for programming this system, but more importantly selling yourself short. You won't have the skills and experience to be able to write tight and efficient code when you want to, and your games will most likely never reach their full potential.

Cheers

A

  • Like 1
Link to comment
Share on other sites

To switch banks, just hit the hotspots; like this:

   org $DD00
  rorg $FD00  ;--this location doesn't matter; just as an example.

  sta $1FF9

Writing to the ROM isn't a good idea, as it might cause bus conflicts. It's better to use "CMP $1FF9", "BIT $1FF9" or "NOP $1FF9" instead.

 

BTW vdub_bobby, did you already try if changing this would fix the problems with Reindeer Rescue on NMOS EPROMS?

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

Sad, really.  Anyone's first '2600 project should, by law, be at *most* 4K.

Without first experiencing and overcoming the absolute necessity to make tradeoffs in design, programming and memory usage... you're missing out on one of the main reasons for programming this system, but more importantly selling yourself short.  You won't have the skills and experience to be able to write tight and efficient code when you want to, and your games will most likely never reach their full potential.

 

Why not insist on starting with a maximum of 2K, like the really early games? :) Anyone who can squeeze a decent game into 2K has my deepest admiration. Of course, a lot of those early games weren't much in the way of appearance (screen graphics), and the gameplay was often extremely simple, but appearance isn't everything, and simple gameplay can still produce very fun games.

 

But having said that, just because someone's first game is 8K, 16K, etc., doesn't mean that the code can't/won't be tight and efficient, and that the person's games are likely to never reach their full potential. For example, the kernel itself and all the main routines might be extremely small and tightly-coded, with many, many Ks of *data* (playfield maps, player shapes, color tables, etc.). Just because he's needing to go past the 4K barrier in his first game doesn't mean his game must therefore be filled with bloated code that a "better, more superior" coder could chop down into a quarter of the size! Maybe it's bloatware, and maybe it's not! :)

 

Still, I kind of like the idea of starting with 2K, and then working up to 4K, before getting into bankswitching... or maybe being required to take a 4K game and find a way to squeeze it down into 2K without losing too much. Who ya gonna call? The Atari 2600 Newbie Programming Gaming Police! :) "But officer, I was only doing 8K!" "Yes, but the limit is 2K in this neighborhood, there are children present."

 

Michael Rideout

  • Like 1
Link to comment
Share on other sites

Sad, really.  Anyone's first '2600 project should, by law, be at *most* 4K.

Without first experiencing and overcoming the absolute necessity to make tradeoffs in design, programming and memory usage... you're missing out on one of the main reasons for programming this system, but more importantly selling yourself short.  You won't have the skills and experience to be able to write tight and efficient code when you want to, and your games will most likely never reach their full potential.

Cheers

A

1022393[/snapback]

:roll:

 

Oh, and no, I haven't done much with Reindeer Rescue since they shipped.

Edited by vdub_bobby
Link to comment
Share on other sites

Sad, really.  Anyone's first '2600 project should, by law, be at *most* 4K.

Guilty! :)

 

Without first experiencing and overcoming the absolute necessity to make tradeoffs in design, programming and memory usage... you're missing out on one of the main reasons for programming this system, but more importantly selling yourself short.  You won't have the skills and experience to be able to write tight and efficient code when you want to, and your games will most likely never reach their full potential.

100% agreed here. While maybe not your with your first game (though I would suggest doing so for beginners), you should at least try once to create a 4K (or maybe even 1k) game. I've learned a lot when participating in the 1K Minigame Compo.

Link to comment
Share on other sites

Oh, it's not even a game I'm working on it's just a small intro...

 

firsttry.bin

 

Regards,

Moderntimes99

1022623[/snapback]

 

And a very nice small intro it is, too! :lust: Assuming that this is related to the project that you wanted bankswitching help for, it will be interesting to see what you do with it once you actually add some bankswitching and have more memory for screen data and such! :D

 

Michael Rideout

Link to comment
Share on other sites

  • 4 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...