Jump to content
Thomas Jentzsch

Atari Bank Switching for Dummies

Recommended Posts

Atari's bank switching (F8, F6, F4) is pretty straight forward. Nevertheless it can become quite complicated if you want to switch between banks a lot. Therefore I wrote myself some macros, which make things much easier.

 

Here is the basic version, just to get the idea:

; TJ's Atari standard bankswitching macros, here tailored for F4 (adapt for F8 or F6)
BANK_SIZE = $1000

; Use at the start of each bank, required by JMP_LBL macro
  MAC START_BANK ; bank number
BANK_NUM    SET {1}
BANK_ORG    SET $8000 + BANK_NUM*BANK_SIZE
BANK_RORG   SET $1000 + BANK_NUM*BANK_SIZE*2
    SEG     code
    ORG     BANK_ORG, $55
    RORG    BANK_RORG
    ECHO    "Start of bank", [BANK_NUM]d, ", ORG", BANK_ORG, ", RORG", BANK_RORG

; verify start addresses:
START{1} = (START & $fff) | BANK_RORG
    ; optional Space for RAM+
;    DS.B    $100, "#"
  ENDM
 
; a convenience macro, put at the end of every bank
  MAC END_BANK 
   RORG $ff4
    ds      8, 0 ; 
    .word   (START & $fff) | BANK_RORG  ; RESET
    .word   0                           ; BRK (unused)
  ENDM

; Insert code for executing the bankswitch,
; should be put at the very end of each bank, right before the hotspots
  MAC SWITCH_BANK
    ORG     BANK_ORG + $ff4 - BANKSWITCH_SIZE
    RORG    BANK_RORG + $ff4 - BANKSWITCH_SIZE

SwitchBank  = (. & $fff) | $1000
    pha
    txa
    pha
    lda     $fff4,y
    rts

BANKSWITCH_SIZE = ((. & $fff) | $1000) - SwitchBank
  ENDM

; Define a label which can be used by JMP_LBL macro
;Example:
;    DEF_LBL Foo
  MAC DEF_LBL ; address
{1}
{1}_BANK    = BANK_NUM
  ENDM

; Jump to a label in other or same bank. The assembler will take care if the
; code has to bankswitch or not.
; Example:
;    JMP_LBL Foo
  MAC JMP_LBL ; address
   IF {1}_BANK != BANK_NUM      ; convenience IF 
    ldy     #{1}_BANK
    lda     #>({1}-1)
    ldx     #<({1}-1)
    jmp     SwitchBank          ; always at the same address in each bank
   ELSE
    jmp     {1}
   ENDIF
  ENDM 

So if you put these macros into your code and use DEF_LBL to define the labels you want to call, you can jump with JMP_LBL to these labels without having to care for bankswitching.

Disadvantages:

  • a bit slower than dedicated bank switching code
  • uses A, X and Y registers, so you cannot transfer parameters (but with more code this can be reduced to one register).

BTW: This could be adapted to JSR calls to, but I never really needed those.

Edited by Thomas Jentzsch
  • Like 7

Share this post


Link to post
Share on other sites

Well I started out understanding none of this and now I think I understand about half of it. Anybody care to check or correct my comments?

; TJ's Atari standard bankswitching macros, here tailored for F4 (adapt for F8 or F6)
BANK_SIZE = $1000	;j Define a constant,(4K bank size here)

; Use at the start of each bank, required by JMP_LBL macro
  MAC START_BANK ; bank number. j example: start each bank with with argument
					;j START_BANK 1, START_BANK 2, etc
BANK_NUM    SET {1}	;j Transfer the START_BANK argument to BANK_NUM
BANK_ORG    SET $8000 + BANK_NUM*BANK_SIZE ;j $8000 skips over the first potential 8
					;j (4K) banks then sets the start address (ORiGin) of the
					;j bank that was set in the argument.
BANK_RORG   SET $1000 + BANK_NUM*BANK_SIZE*2 ;j ??? If bank 2, for example, BANK_RORG
					;j would be $5000.  Why?
    SEG     code		;j Start the code segment
    ORG     BANK_ORG, $55	;j Put $55 at the start of a bank?
    RORG    BANK_RORG		;j Put the relocatable code in the calculated location?
    ECHO    "Start of bank", [BANK_NUM]d, ", ORG", BANK_ORG, ", RORG", BANK_RORG ;j print stuff

; verify start addresses:
START{1} = (START & $fff) | BANK_RORG ;j Define START of the bank as this location
					;j with the left nibble stripped off and replaced with the
					;j left nibble of BANK_RORG?
    ; optional Space for RAM+
;    DS.B    $100, "#"
  ENDM
 
; a convenience macro, put at the end of every bank
  MAC END_BANK 
   RORG $ff4
    ds      8, 0 ;j Put zeros in the last 8 bytes for the hot spots before the
			; reset and break vectors
    .word   (START & $fff) | BANK_RORG  ; RESET
    .word   0                           ; BRK (unused)
  ENDM

; Insert code for executing the bankswitch,
; should be put at the very end of each bank, right before the hotspots
  MAC SWITCH_BANK
    ORG     BANK_ORG + $ff4 - BANKSWITCH_SIZE ;j Calculate where the hot spots
											;j go in each bank
    RORG    BANK_RORG + $ff4 - BANKSWITCH_SIZE ;j Calculate where the hot spots
											;j go in the moveable code?

SwitchBank  = (. & $fff) | $1000 ;j take this location and replace the left 
								;j nibble with a 1?
    pha		;j MAC JMP_LBL jumps to here. The accumulator was loaded with one
    txa		;j nibble and x was loaded with the other.  Move it to the accumulator
    pha		;j so it can be pushed on the stack.  Setting up a return address.
    lda     $fff4,y	;j Load the accumulator with one of the hot spots, causing
			;j a bankswitch.
    rts		;j The bankswitch occurred, so this executes in the new bank
			;j The rts is to an address that was calculated in JMP_LBL

BANKSWITCH_SIZE = ((. & $fff) | $1000) - SwitchBank ;j Calculates the size of the
			;j SwitchBank code. Then take this address and limit it to a range of
			;j	%00001111 11111111 to
			;j	%00011111 11111111 .... Why the limit?
  ENDM

; Define a label which can be used by JMP_LBL macro
;Example:
;    DEF_LBL Foo	;j Insert this in the place where you want to be able to jump to.
  MAC DEF_LBL ; address	;j Figure out where we are.
{1}			;j Argument is Foo. Labels the code?
{1}_BANK    = BANK_NUM	;j Foo_BANK is the bank number we are currently in.
  ENDM

; Jump to a label in other or same bank. The assembler will take care if the
; code has to bankswitch or not.
; Example:
;    JMP_LBL Foo
  MAC JMP_LBL ; address
   IF {1}_BANK != BANK_NUM      ; convenience IF  ;j If Foo_BANK is not current bank
    ldy     #{1}_BANK		;j Load y with the hot spot offset
    lda     #>({1}-1)		;j Load MSB of Foo's address
    ldx     #<({1}-1)		;j Load LSB of Foo's address
    jmp     SwitchBank          ; always at the same address in each bank
   ELSE		;j If we're already in the same bank
    jmp     {1}	;j then just jump Foo
   ENDIF
  ENDM 



  • Like 1

Share this post


Link to post
Share on other sites

 

Well I started out understanding none of this and now I think I understand about half of it. Anybody care to check or correct my comments?

; TJ's Atari standard bankswitching macros, here tailored for F4 (adapt for F8 or F6)
BANK_SIZE = $1000	;j Define a constant,(4K bank size here)

; Use at the start of each bank, required by JMP_LBL macro
  MAC START_BANK ; bank number. j example: start each bank with with argument
					;j START_BANK 1, START_BANK 2, etc TJ: for F4 you should start with 0, else the following variables will overrun
BANK_NUM    SET {1}	;j Transfer the START_BANK argument to BANK_NUM
BANK_ORG    SET $8000 + BANK_NUM*BANK_SIZE ;j $8000 skips over the first potential 8
					;j (4K) banks then sets the start address (ORiGin) of the
					;j bank that was set in the argument.
BANK_RORG   SET $1000 + BANK_NUM*BANK_SIZE*2 ;j ??? If bank 2, for example, BANK_RORG
					;j would be $5000.  Why? TJ: Because we need an odd first nibble here
    SEG     code		;j Start the code segment
    ORG     BANK_ORG, $55	;j Put $55 at the start of a bank? TJ: This is the address of the bank in the ROM. These can be anything, but they must be consecutive overall. $55 is just redefining the default fill value for unused areas
    RORG    BANK_RORG		;j Put the relocatable code in the calculated location? TJ: The origin used by the code in this bank. Different origins per bank make debugging easier.
    ECHO    "Start of bank", [BANK_NUM]d, ", ORG", BANK_ORG, ", RORG", BANK_RORG ;j print stuff

; verify start addresses:
START{1} = (START & $fff) | BANK_RORG ;j Define START of the bank as this location
					;j with the left nibble stripped off and replaced with the
					;j left nibble of BANK_RORG? TJ: Startup address of the bank, this code just verifies that all banks use the place in their logical address space
    ; optional Space for RAM+
;    DS.B    $100, "#"
  ENDM
 
; a convenience macro, put at the end of every bank
  MAC END_BANK 
   RORG $ff4
    ds      8, 0 ;j Put zeros in the last 8 bytes for the hot spots before the
			; reset and break vectors
    .word   (START & $fff) | BANK_RORG  ; RESET
    .word   0                           ; BRK (unused)
  ENDM

; Insert code for executing the bankswitch,
; should be put at the very end of each bank, right before the hotspots
  MAC SWITCH_BANK
    ORG     BANK_ORG + $ff4 - BANKSWITCH_SIZE ;j Calculate where the hot spots
											;j go in each bank
    RORG    BANK_RORG + $ff4 - BANKSWITCH_SIZE ;j Calculate where the hot spots
											;j go in the moveable code? TJ: You have to update ORG and ROGR in pairs when using bankswitching, because they are different.

SwitchBank  = (. & $fff) | $1000 ;j take this location and replace the left 
								;j nibble with a 1? TJ: Just like START, this puts all bank switch code at the same spot in address space for each bank
    pha		;j MAC JMP_LBL jumps to here. The accumulator was loaded with one
    txa		;j nibble and x was loaded with the other.  Move it to the accumulator
    pha		;j so it can be pushed on the stack.  Setting up a return address.
    lda     $fff4,y	;j Load the accumulator with one of the hot spots, causing
			;j a bankswitch.
    rts		;j The bankswitch occurred, so this executes in the new bank
			;j The rts is to an address that was calculated in JMP_LBL

BANKSWITCH_SIZE = ((. & $fff) | $1000) - SwitchBank ;j Calculates the size of the
			;j SwitchBank code. Then take this address and limit it to a range of
			;j	%00001111 11111111 to
			;j	%00011111 11111111 .... Why the limit? TJ: The limit comes from the 4K address space we are using.
  ENDM

; Define a label which can be used by JMP_LBL macro
;Example:
;    DEF_LBL Foo	;j Insert this in the place where you want to be able to jump to.
  MAC DEF_LBL ; address	;j Figure out where we are.
{1}			;j Argument is Foo. Labels the code? TJ: Yes, required to have an address
{1}_BANK    = BANK_NUM	;j Foo_BANK is the bank number we are currently in.
  ENDM

; Jump to a label in other or same bank. The assembler will take care if the
; code has to bankswitch or not.
; Example:
;    JMP_LBL Foo
  MAC JMP_LBL ; address
   IF {1}_BANK != BANK_NUM      ; convenience IF  ;j If Foo_BANK is not current bank
    ldy     #{1}_BANK		;j Load y with the hot spot offset
    lda     #>({1}-1)		;j Load MSB of Foo's address
    ldx     #<({1}-1)		;j Load LSB of Foo's address
    jmp     SwitchBank          ; always at the same address in each bank
   ELSE		;j If we're already in the same bank
    jmp     {1}	;j then just jump Foo
   ENDIF
  ENDM 

My comments above (TJ...)

  • Like 2

Share this post


Link to post
Share on other sites

Curious. I am still learning but Is there a method of getting the same amount pf space without bankswitching?

Share this post


Link to post
Share on other sites

Sorry, I don't get your question.

Share this post


Link to post
Share on other sites

Sorry. I was wondering if information can only be accessed in 4k increments or is there a way to have more without bankswitching?

 

Not sure if I am fully understanding it or not or if my question makes much sense at all...

Share this post


Link to post
Share on other sites

The Atari can only access up to 4K ROM without bankswitching. If you need more ROM space you need bankswitching.

Share this post


Link to post
Share on other sites

Okay. Thanks. That's what I thought from all the reading but wasn't sure if I missed anything.

Share this post


Link to post
Share on other sites

Hi Thomas,

 

I see in your macros that bank 0 will use:

ORG $8000

RORG $1000

 

...and bank 1 will use:

ORG $9000

RORG $3000

 

Can you explain why this makes debugging easier than if you would just use RORG $F000 for each bank?

 

Thanks!

Share this post


Link to post
Share on other sites
Posted (edited)

Because when one bank references labels from another bank, you can tell by the MSB which bank the labels actually exist in.

 

Tho it's true that it's not much an advantage over using $1000 for all banks IMO, where the upper 3 bits for all MSB's can be repurposed for other data...so if it IS used, you could truncate the label addresses to be 13 bit whenever you use need them.

Edited by Nukey Shay

Share this post


Link to post
Share on other sites

Actually it is a huge advantage when using Stella's debugger. Else you code will be cluttered by labels from all banks. And the correct label may not even show, if the same 13 bit address is used in another bank.

  • Like 1

Share this post


Link to post
Share on other sites

Actually it is a huge advantage when using Stella's debugger. Else you code will be cluttered by labels from all banks. And the correct label may not even show, if the same 13 bit address is used in another bank.

 

For some reason the Stella 6.0 debugger doesn't show me labels for Bank 1, but only for Bank 0.

In Stella I do see that:

  • Bank 0 is at $1000 - $1fff
  • Bank 1 is at $3000 - $3fff

And the symbols dump file also contains the correct labels for Bank 1 in that $3000 - $3fff range.

 

But when I step through the debugger from Bank 0 into Bank 1, I see the addresses left to the assembly code stay in the '1xxx' range instead of switching to the '3xxx' range. Also, in Bank 1 it doesn't show any of the labels from that bank.

So while Stella seems to know that Bank 1 is in the $3000 - $3fff range, it doesn't use that correctly in the debugger and for matching label names.

 

Am I doing something wrong maybe?

Share this post


Link to post
Share on other sites
Posted (edited)

The problem is, that the PC is still using the old bank's upper bits.

 

For debugging, use a jump after entering a new bank. Afterwards, you should be fine.

Edited by Thomas Jentzsch

Share this post


Link to post
Share on other sites

The problem is, that the PC is still using the old bank's upper bits.

 

For debugging, use a jump after entering a new bank. Afterwards, you should be fine.

 

Weird, I do see the PC is showing the correct upper bits after bank switching, but the debugger doesn't seem to reflect the new bank addresses.

 

Adding a dummy jmp *+3 as first instruction after entering the new bank doesn't help.

Share this post


Link to post
Share on other sites

Actually it is a huge advantage when using Stella's debugger. Else you code will be cluttered by labels from all banks.

That is why I edited the reply to scratch out "use" and put "need" there instead. It's an extra 3 bits of Ram from any variable holding MSB data.

Share this post


Link to post
Share on other sites

 

Weird, I do see the PC is showing the correct upper bits after bank switching, but the debugger doesn't seem to reflect the new bank addresses.

 

Adding a dummy jmp *+3 as first instruction after entering the new bank doesn't help.

Hm, I suppose I have to see the code (or an example code) do understand what is going on.

Share this post


Link to post
Share on other sites
Posted (edited)

Hm, I suppose I have to see the code (or an example code) do understand what is going on.

 

Hi Thomas,

 

I extracted the bank-switching code into a very basic example showing rainbow colors and doing a single F8 bank switch (from bank 0 to bank 1, and back again).

 

If you run this example and in the Stella debugger set a breakpoint on the 'BankSwitch' label, you can step through the code and see the problem that after the bank-switch the addresses inside the debugger are not updated to reflect the correct addresses (i.e. they should be in the $3000 - $3fff range, but they keep showing addresses in the $1000 - $1fff range).

 

Maybe I'm doing something wrong, but maybe it's a bug in Stella 6.0.

 

Thanks!

;===============================================================================
; F8 Bank Switch sample
;===============================================================================
    processor 6502
    include "vcs.h"
    include "macro.h"

    MAC COMMON_BANKSWITCH_CODE
    ;make sure we always start at label InitSystem in Bank 0 
    lda #>(InitSystem-1)
    ldy #<(InitSystem-1)
    ldx #$ff
    txs		; init stack pointer
    inx		; X = 0, because InitSystem is in Bank 0
BankSwitch = (. & $fff) | $1000 ; 
    pha             ; push hi byte
    tya             ; Y -> A
    pha             ; push lo byte
    lda $1FF8,x     ; do the bank switch
    rts             ; return to target
    ENDM

;===============================================================================
; BANK 0
;===============================================================================
    SEG Code
    ORG $8000
    RORG $1000

    COMMON_BANKSWITCH_CODE

;===============================================================================
; Init
;===============================================================================
InitSystem:
    CLEAN_START ; initialize zero page and stack

GameLoop:   
    VERTICAL_SYNC

    ;Now switch to code in bank 1
    lda #>(LabelInBank1-1)
    ldy #<(LabelInBank1-1)
    ldx #1
    jmp BankSwitch
ReturnFromBankSwitch:

    ; 36 scanlines of vblank...
    REPEAT 36
        sta WSYNC
    REPEND

    lda #0
    sta VBLANK ; stop vertical blank

;-------------------------------------------------------------------------------
; Now display a screen of rainbow colors
;-------------------------------------------------------------------------------
DisplayKernel:

    ; 192 scanlines of picture...
    ldx #0
    REPEAT 192 ; scanlines
        inx
        stx COLUBK
        sta WSYNC
    REPEND

;-------------------------------------------------------------------------------
; VerticalBlank - enter blanking
;-------------------------------------------------------------------------------
OverScan:

    ; end of screen - enter blanking
    lda #%01000010
    sta VBLANK

    ; 30 scanlines of overscan...
    REPEAT 30
        sta WSYNC
    REPEND

    ; back to game loop
    jmp GameLoop

    ;ending vectors
    ORG  $8ff8
    RORG $1ff8
    ds 4, 0
    .word $1000      ; RESET
    .word 0          ; BRK


;===============================================================================
; BANK 1
;===============================================================================
    SEG Code
    ORG $9000
    RORG $3000

    COMMON_BANKSWITCH_CODE

LabelInBank1:
    jmp *+3 ; jump to next instruction (maybe this fixes PC issue?)
    ; switch back to bank 0
    lda #>(ReturnFromBankSwitch-1)
    ldy #<(ReturnFromBankSwitch-1)
    ldx #0
    jmp BankSwitch

    ;ending vectors
    ORG  $9ff8
    RORG $3ff8
    ds 4, 0
    .word $3000      ; RESET
    .word 0          ; BRK
Edited by Dionoid

Share this post


Link to post
Share on other sites

You are right, your code doesn't disassemble correct. I have no idea why similar code works, but your's doesn't. I'll create an issue for it, but it may take quite some time to get to it.

Share this post


Link to post
Share on other sites
Posted (edited)

You are right, your code doesn't disassemble correct. I have no idea why similar code works, but your's doesn't. I'll create an issue for it, but it may take quite some time to get to it.

 

Hi @Thomas Jentzsch, do you have a small bank-switching example where the Stella debugger actually shows the correct addresses after switching to the non-startup bank?

From that I think I can figure out the differences.

 

I tried changing my code by moving the common bank-switch code to the end of the banks (just before the hotspots starting at $fff4) but that didn't fix the issue with the missing debug labels.

Edited by Dionoid

Share this post


Link to post
Share on other sites
Posted (edited)

Note that I found a specific combination of Stella settings that seems to solve the F8 debugging issues for me, see this post.

Edited by Dionoid
  • Like 1

Share this post


Link to post
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.

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