Jump to content
IGNORED

Atari Bank switching - 'best' scheme for homebrew?


Recommended Posts

I am looking at implementing bank switching for my Refhraktor project and wondering - for modern homebrew is there any preference for one scheme or the other?

In case future forum members hit this post looking for bank switching advice, here's where I've been ramping up: http://www.taswegian.com/WoodgrainWizard/tiki-index.php?page=Bank-Switching
 

Link to comment
Share on other sites

Thanks for the input! 

 

I’m definitely looking for maximum cross compatibility as number one - specifically Javatari has been helpful to get play testers. So I understand that rules out some schemes. 


My POC fits into 4K but I’ve started doing some experiments with F8 to prove out more or less the same kernel but with some menus, audio, a little more level data…

 

…which has also got me playing around with the idea of having a dynamic, destructable playfield and RAM would give me some breathing room on that score, and maybe also give me a way to pass data between banks. But.. I don’t see as much discussion going on about RAM and the list of classic games using RAM seems shorter…

 

Edited by Dave C
Link to comment
Share on other sites

There are quite a few homebrews (less originals) using SC RAM in particular, and it's well supported on emulation and multicarts, as well as Melody. Actually soldering one up yourself might be a bit more challenging.

 

For example, if you were to go FxSC:

  • You lose 256 bytes at the top of each bank ($f000-$f0ff)
  • You get 128 bytes of write followed by 128 bytes of read ports
  • You cannot use Read-Modify-Write (RMW) operations on those addresses, e.g. inc, dec

So eg. to add one to a RAM address:

          inc Memory

but in SC RAM

          lda MemoryRead
          clc
          adc # 1
          sta MemoryWrite

Nicely, Stella can be configured to halt in the debugger if you encounter a read from a write port or vice-versa, but it does require special care when coding.

 

To “convince” emulators that you're using SC RAM, you'll probably want to fill the bottom page of ROM (in every bank) with some garbage data, perhaps zeroes. You can also just manually select the right banking method, but why not take advantage of the heuristics …

 

Myself, I will define the write addresses in "the usual way" and then have a constant named READ so I can reference the read ports as e.g. READ+PlayerEquippedItem in the source, rather than having to keep in sync between PlayerEquippedItemWrite = $f004 / PlayerEquippedItemRead = $f084 or so forth.

 

You also have to worry about "ghost" or "phantom" reads from write ports, see e.g. this post for an example, but I believe Stella now traps those as well for you.

If you want even more RAM, there are other options (CBS, &c.) that do exist.

 

17 hours ago, Dave C said:

and maybe also give me a way to pass data between banks.

Zero page is also visible from all banks, so you don't need to use SC RAM for that, but perhaps I misunderstand that comment.

Link to comment
Share on other sites

23 hours ago, Bruce-Robert Pocock said:

There are quite a few homebrews (less originals) using SC RAM in particular, and it's well supported on emulation and multicarts, as well as Melody. Actually soldering one up yourself might be a bit more challenging.

Ok cool, I wasn't clear on the fabrication options, if there were any serious challenges - but Melody seems like the ideal platform.


thanks for the tips - that READ/WRITE constants makes sense
 

23 hours ago, Bruce-Robert Pocock said:

Zero page is also visible from all banks, so you don't need to use SC RAM for that, but perhaps I misunderstand that comment.

Yeah so let me unravel my train of thought - I am actually low on zero page RAM due to using a big chunk of it to hold graphics data, and it would help to offload some of it to SC RAM. And then potentially thinking about loading graphics from one bank into RAM for use by kernel in another (vs keeping a copy of the kernel in the same bank)

 

Link to comment
Share on other sites

In case it helps anybody out I made myself a F8 banks switching + Superchip demo kernel modified from @Thomas Jentzsch's Atari Bank Switching for Dummies. it just flips back and forth between banks as you press the fire button using colors that were 'drawn' into SC ram, with slightly different display kernel in each bank. It also has SUPERCHIP_READ and SUPERCHIP_WRITE constants a la @Bruce-Robert Pocock's method.

 

kernel in bank 0:
image.thumb.png.f4424a2bc1889e2fe002c9b363886bdf.png

kernel in bank 1:

image.thumb.png.f1fc137233f71fbda9a39d273f540f83.png

poc-banks-sc.asm bank_switch_f8.h

Edited by Dave C
  • Like 1
Link to comment
Share on other sites

Thanks for starting a bank switching thread.  I'm a coding n00b so I don't have anything really to contribute in terms of answers but would like to ask some peripherally related questions. 

 

How do folks generally divide what goes in which bank?  I know that the 2600 can only read 4k at any one time so some stuff has to be repeated but how do folks generally determine what must be repeated versus how to divide the rest?  Is bank switching typically used between levels or screens or is the process fast enough to happen between frames?  If the former, I'd assume that basic character info (sprites, sounds, animation, etc) has to be carried over to each bank and the rest of the level-specific info is only on that bank (layout, enemy info, kernel).  If it's fast enough to happen between frames then that is likely out the window and many more options are available.  Sorry if it's a stupid question but I figured I'd ask as I'm trying to see how much of a benefit you can give a 4k size constrained game with a bump to 8k (but no other additional cartridge based enhancements like RAM or processing).  The homebrew pacman game that comes in 4k and 8k seems like it can make a heck of a difference.

Link to comment
Share on other sites

1 hour ago, LatchKeyKid said:

How do folks generally divide what goes in which bank?

If using F8 (8k), then it usually ends up being game logic in one bank, display kernel and graphics in the other.  Using that type of division (display / logic split) helps minimize the amount of bankswitching code.

 

1 hour ago, LatchKeyKid said:

I know that the 2600 can only read 4k at any one time so some stuff has to be repeated but how do folks generally determine what must be repeated versus how to divide the rest?

Try to avoid repeating stuff... Only repeat stuff when absolutely necessary.

 

1 hour ago, LatchKeyKid said:

Is bank switching typically used between levels or screens or is the process fast enough to happen between frames?

Bankswitching is fast... for F8 (and company) it's only a simple memory read of a specific address that cause the memory to swap.  The only complication is making sure the bankswitching code nicely crosses from one bank into another without interrupting itself. 

 

1 hour ago, LatchKeyKid said:

I'm trying to see how much of a benefit you can give a 4k size constrained game with a bump to 8k (but no other additional cartridge based enhancements like RAM or processing).

Ok, so F8 was a good example for me to base my answers off of :)

  • Like 1
Link to comment
Share on other sites

56 minutes ago, splendidnut said:

If using F8 (8k), then it usually ends up being game logic in one bank, display kernel and graphics in the other.  Using that type of division (display / logic split) helps minimize the amount of bankswitching code.

 

Try to avoid repeating stuff... Only repeat stuff when absolutely necessary.

 

Bankswitching is fast... for F8 (and company) it's only a simple memory read of a specific address that cause the memory to swap.  The only complication is making sure the bankswitching code nicely crosses from one bank into another without interrupting itself. 

 

Ok, so F8 was a good example for me to base my answers off of :)

Thanks.  I took a look at your example code and it doesn't seem too big to counteract the benefit of doubling the cartridge size (I realize that's an understatement!).  You still have to put in the code you linked above with the bank switching macros in each bank, correct?  And you can switch banks with a simple "LDA $1FF8" command for the scanlines with the kernel and than just "LDA $1FF9" for the rest with game code/logic?  That seems very flexible in that case if it can go there and back within a single frame. 

 

My only real experience with Atari programming is my Pitfall hack so I'm pretty much basing it on that (exteme?) case with its seemingly rare large kernel for a 4k game.  In that game, it seems like there is some game logic running during the kernel itself but I don't know if that's "normal"; obviously if you know you'll be bank switching then you can develop the code to avoid that in the first place.  I'm curious what would be possible to add in that style of game via hack like an intro screen, shooting mechanics, and music.

Link to comment
Share on other sites

38 minutes ago, LatchKeyKid said:

You still have to put in the code you linked above with the bank switching macros in each bank, correct?

 

My code is self-contained and is designed to be a very simple template to start a project from.  It minimizes the amount of bankswitching code while also breaking the frame into three pieces:

 - VSYNC, and vblank game logic code (before display starts)

 - Display code

 - overscan game logic code (after display ends)

 

The boot code and display code sit in bank 0, and both of the game logic blocks sit in bank 1.

 

 

If you want more flexibility with bankswitching (or want to use something other than F8), @Thomas Jentzsch's code would be the better option to use.

  • Like 1
Link to comment
Share on other sites

Thanks again.  That would work for me for the purposes of a future hack that I may add to so I definitely appreciate it.  I'm only starting out so I'll be lucky if I can fill out the extra space with code that actually works let alone any of the more complicated (relatively speaking) bank switching schemes.  :)   I have to slowly work my way up to Thomas' "bank switching for dummies". 

Link to comment
Share on other sites

On 8/16/2022 at 6:16 PM, LatchKeyKid said:

How do folks generally divide what goes in which bank?  I know that the 2600 can only read 4k at any one time so some stuff has to be repeated but how do folks generally determine what must be repeated versus how to divide the rest? 

Note, after typing all the below, I realized it is much more relevant to bigger (16kiB+) ROMs and maybe not so much for 8k projects, but just to show a little different way of looking at things. Consider this (by Atari 2600 standards) the “programming In The Large” version …

 

Myself, I try to map a very small section of code at the tail end of ROM (keeping in mind that you might start up in any bank, so the IRQ/NMI/RES vectors are part of this file) that I think of like "wired memory" in Unix terms (un-swappable).  In each bank, I include the same file (EndBank.s) that starts at the same fixed location in each bank. However, it can be compiled slightly differently in each bank. So eg.

;;; Perform a far call to a memory bank with a specific local
;;; service routine to perform; eg, this is how we handle sounds,
;;; text displays, and the common map header and footer code;
;;; anything that does not need data from the local ROM bank.
FarCall:
          lda #BANK
          pha
          sta BankSwitch0, x
          jsr DoLocal
          ;; fall through after rts

;;; Return from a FarCall.
FarReturn:
          pla
          tax
          sta BankSwitch0, x
          rts

That label DoLocal has a different definition in each ROM bank.  So e.g. in bank 0 it might be $f2b4 and in bank 4 it might be $f090. In each bank's source I just define a DoLocal label wherever I like, and include EndBank.s at the tail.

 

In Grizzards I usually use the y register to select a service routine that the destination bank provides. EG:

          ldy # ServiceDoSomething
          ldx # RelevantBankNumber
          jsr FarCall

;; and then somewhere in that other bank

DoLocal:
          cpy # ServiceDoSomething
          beq DoSomething

          cpy # ServiceDoSomethingElse
          beq DoSomethingElse

          brk                           ; crash if Y is not on our list.

 

In addition there are a couple of hard routine pointers there, that are used for jumping to a specific bank, like the cold start (RES) vector and the break (IRQ) vector point to, or like:

GoCombat:
          ldx #$ff              ; smash the stack
          txs
          sta BankSwitch0 + CombatBank
          jmp DoLocal

 

I've arranged certain types of data into certain banks to avoid duplication as much as possible; e.g. the font for text is only present in a couple of banks, so anything that displays text has to go through one of them; so eg. my Combat screen is made up of calls to any number of different ROM banks, for showing the name of monsters, displaying the group of monsters opposing you, showing the name of the Move you're selecting to use next, &c. Or: for the various banks that handle drawing the Map screens and such, the map data is in each bank, but a lot of the VBlank activity is common to all of them (and does not actually refer to the map data itself), eg. drawing the score at the top of the screen, scanning the joystick & switches, &c. So there is a call from the “map kernel” to

          ldy #ServiceTopOfScreen
          ldx #MapServicesBank
          jsr FarCall

 

The full source for my “wired memory” is at https://github.com/brpocock/grizzards/blob/main/Source/Common/EndBank.s and a relatively typical Bank file is eg. https://github.com/brpocock/grizzards/blob/main/Source/Banks/Bank02/Bank02.s which, as you can see, is largely just the DoLocal dispatch code followed by .includes for the various subroutines and data files it uses.

Link to comment
Share on other sites

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...