Jump to content
IGNORED

Code and Data redundancy when bank-switching?


Sohl

Recommended Posts

Today I transitioned to using a 16K ROM for my work-in-progress game Immunity.  As of now, I plan to have bank 0 handle the title/game over screens and it also has some common code for preparing playfield graphics for score digit drawing.  Bank 1 is for the one play mode with "outside" the cell membrane action, while the new bank 2 will be for "inside" the cell membrane action, and bank 3 is currently unused.  Maybe I will move my sound-effects stuff there, or other special routines that can be fairly self-contained.  

 

Anyway, I've noticed that I want to have a lot code and data in common between bank 1 and bank 2.  Several of the sprite graphics and some other lookup tables plus short routines like the player/object positioning routine and many more are likely needed in both 1 and 2, or at least accessible from both banks. Maybe this is tolerable, but seems wasteful, and breaks the DRY ("don't repeat yourself") principle.  I have a lot of ugly things like bank-annotated data and subroutine labels, such as VIRUS_SPRITE_Gfx vs  VIRUS_SPRITE_B2_Gfx, and PositionObject vs PositionObject_B2, etc.  I seem to really need two banks for the two main modes of play, since the display kernel for the first mode is already quite large and I imagine the other mode is at least as bad, if not worse, plus the amount of non-common game logic will not fit if both play modes are squeezed into a single back, as far as I can tell.

 

Some strategies that I have considered, but have not worked too hard to implement yet are:

1) Use ORG to align as much of the common data as possible to a common offset within each segment, so the same $Fxxx label is correct in both banks.  The data still needs to be duplicated in the source code for each bank, but the labels for one bank would be interchangeable with the other, so code does not need to refer to bank-specific names.

 

2) Define any routine that will be repeated in two or more banks as a MACRO, so at least the source code is not just copy-and-paste repeated, even if the binary code is actually repeated

 

3) Use the "trampoline" construct, or whatever it should be called, to be able to bank-switch and call a single instance of the code and then bank-switch back to the caller's correct bank when done (not sure how to automatically return to the correct bank without checking other state data in RAM). 

 

An example of 2) I am using... in Bank 1, I have:

PrepScoreForDisplay:    SUBROUTINE
    lda $1ff6            ; (Happens in Bank 1) switch to Bank 0
    jsr DoPrepScoreForDisplay    ; do the work in Bank 0
    lda Mode
    and #InsideCellFlag
    beq .extracellular
    lda $1ff8            ; Switch back to Bank 2
    bvc .done
.extracellular:
    lda $1ff7            ; Switch back to Bank 1
.done:
    rts                    ; (Happens in Bank 1 or 2) return
    

In Bank 2, I have (positioned at the same exact offset from the start of the bank):

PrepScoreForDisplay_B2:        SUBROUTINE
    lda $1ff6            ; (Happens in Bank 2) switch to Bank 0
    jsr DoPrepScoreForDisplay    ; do the work in Bank 0
    lda Mode
    and #InsideCellFlag
    beq .extracellular
    lda $1ff8            ; Switch back to Bank 2
    bvc .done
.extracellular:
    lda $1ff7            ; Switch back to Bank 1
.done:
    rts                    ; (Happens in Bank 1 or 2) return

 

The actual work is done in the "DoPrepScoreForDisplay" routine that is only in Bank 0 (based on @SpiceWare's example code!).  For this long routine, it is worth it, but for a ten to twenty statement routines, it does not seem efficient to write and use this bank-switching code for each function that would be shared between multiple banks.  Is there a more efficient, streamlined way to do this, or does it just come down to recognizing the point were a common routine is long enough to make a bank-switching call and return worth it?

 

Other ideas?  What have you code-masters done to keep the data and code redundancy to a tolerable level?  If you can point out good examples of code to study or articles or postings, please share and point me in a good direction.  I've tried to absorb some previous good examples posted here in AA, but I'd like to learn more, so any additional comments or suggestions would be appreciated!

--

Mike

Edited by Sohl
typo
  • Like 1
Link to comment
Share on other sites

It's always a tradeoff with 2600 development - you'll end up duplicating some things, and jumping to other banks for others.

 

There are bankswitching schemes that let you bank in 1K or 2K slices. That would probably let you reduce duplication at the expense of coding complication. I'm aware of these 2, but have not used them so am not familiar with the specifics in how you use them.

  • E0 - Parker Brothers, uses 1K slices
  • E7 - M-Network, uses 2K slices

 

  • Like 2
Link to comment
Share on other sites

57 minutes ago, SpiceWare said:

It's always a tradeoff with 2600 development - you'll end up duplicating some things, and jumping to other banks for others.

 

There are bankswitching schemes that let you bank in 1K or 2K slices. That would probably let you reduce duplication at the expense of coding complication. I'm aware of these 2, but have not used them so am not familiar with the specifics in how you use them.

  • E0 - Parker Brothers, uses 1K slices
  • E7 - M-Network, uses 2K slices

 

There's also 3F (Tigervision) and 3E, both using 2k slices. Supercharger too.

  • Like 3
Link to comment
Share on other sites

@SpiceWare, @JetSetIlly, @Thomas Jentzsch Thanks for your comments.  I'm not sure I'd want to go that way, but good to be aware of.  Are those bank switching schemes well-supported in emulation and hardware such as Harmony carts or AA hardware carts?

 

So far, I'm trying to use my suggestion 2) to define the code once  (keep it DRY!), but set up subroutine instance in each bank where I want to use it:

    MAC PosObjectCode
        ...  ; (code here)
    ENDM

;;;; BANK 1

PosObject_B1: 
         PosObjectCode
         ret
;;;  ... (in some later code)

        jsr PosObject_B1 

;;;; BANK 2

PosObject_B2: 
         PosObjectCode
         ret
;;; ... (in some later code)

        jsr PosObject_B2 

 

Another thing I'm thinking about is re-organizing my code along a more functional axis rather than "gameplay" mode.   For example, if I can fit all my drawing kernels, at least the ones for active gameplay into a single bank, then I don't need to worry about duplicating data for sprites, PFA graphics, color tables, etc. that are common across multiple gameplay modes, so maybe this looks like in my case:

Bank 0 - Sound and some big support routines (score graphics prep)?

Bank 1 - Graphics kernels for all screens?

Bank 2 - VBlank & Overscan routines for all gameplay screens, lots of game logic for all gameplay modes

Bank 3 - Initialization, maybe some specialized graphics kernel code for non-gameplay screens (title/game over/game version selection) that does not need sprites from Bank 1

 

I think if I do it this way, the bank-switching can be slightly less smart... the Bank 2 would always switch to Bank 1 for drawing the screen (albeit to different specific routines), then always switch back to Bank 2 for overscan processing.  This seems better than in my current setup, where I might have two different banks to switch back to.   But it would take some work to get there!  Do any of you tend to set things up in functional sections like this, or have different banks handle different phases of play (more time-sequential)?

 

 

Edited by Sohl
typo
Link to comment
Share on other sites

By using macros, you can make bankswitching fully transparent. You only have to put the logic into the macros. Of course the resulting code is slightly less efficient than manually optimized one.

 

To do so, you have to define labels (using macros) for each target routine which contain the current bank. And then you define e.g. a macro for JMP which compares the current bank with the target bank and if necessary uses common bankswitching code (same code and address in all banks).

 

 

 

 

  • Like 3
Link to comment
Share on other sites

4 hours ago, Sohl said:

I think if I do it this way, the bank-switching can be slightly less smart... the Bank 2 would always switch to Bank 1 for drawing the screen (albeit to different specific routines), then always switch back to Bank 2 for overscan processing.  This seems better than in my current setup, where I might have two different banks to switch back to.   But it would take some work to get there!  Do any of you tend to set things up in functional sections like this, or have different banks handle different phases of play (more time-sequential)?

 

I have tried to do functional separation in the past specifically to eliminate code duplication.

 

Here's a comment I put at the top of my source code for Panky the Pandy (which was also 16KB).


; Bank 1: Display kernel, main program loop, vertical blank game logic, AI-related overscan game logic, and graphics data
; Bank 2: Room initialization routine, room data, and all other overscan game logic
; Bank 3: Title screen and ending screen kernels and graphics data
; Bank 4: Music playing routine and music data

 

So there's 2 different banks where kernels live (1 and 3), but none of them share (and consequently duplicate) graphics data as far as I recall.

Edited by Ben_Larson
  • Like 2
Link to comment
Share on other sites

Here's some code I use for bankswitching. It's a modified version of the code used in Bang!

 

There it's done with code generation, so this code differs. The cool part is that the banks are encoded in the upper 3 bits of the address when jumping to a routine. Note the comments when trying to use it, some lines need to be removed according to the bankswitching used.

 

There is a frontend used by macros that enable the jumping across banks, as well as for RTS. These need A and X for calculating the bank and the jump.

 

The backend expects the code for calculating the jump at the same address in each bank. I suggest ti put it directly before the bankswitching hotspots. Also note that the RTS will trigger a read on the following byte, so putting this before the hotspots is a very bad idea.

 

; macros for using
; all bankjumps overwrite A and X registers

.macro bankjsr _addr
   ldx #<(_addr-1)
   lda #>(_addr-1)
   jsr bankjmpcode
.endmacro

.macro bankjmp _addr
   ldx #<(_addr-1)
   lda #>(_addr-1)
   jmp bankjmpcode
.endmacro

.macro bankrts
   jmp bankrtscode
.endmacro

; this code needs to be replicated to all banks
; to the exact same address

bankjmpcode:
   pha
   txa
   pha

bankrtscode:
   tsx
   lda $02,x
   asl
   rol             ; remove when F8
   rol             ; remove when F8 or F6
   rol 
   and #%00000111  ; use this for F4
   and #%00000011  ; use this for F6
   and #%00000001  ; use this for F8
   tax
   lda $fff4,x     ; use this for F4
   lda $fff6,x     ; use this for F6
   lda $fff8,x     ; use this for F8
   rts

bankreset:
   lda $fff4 + (reset >> 13) ; use this for F4
   lda $fff6 + (reset >> 14) ; use this for F6
   lda $fff8 + (reset >> 15) ; use this for F8
   jmp reset

; this is padding for the bankswitch bytes

   .byte $f4,$f5,$f6,$f7,$f8,$f9,$fa,$fb ; use this for F4
   .byte $f6,$f7,$f8,$f9,$ff,$ff         ; use this for F6
   .byte $f8,$f9,$ff,$ff                 ; use this for F8

; reset vector
   .word bankreset
; irq vector
   .word bankreset

For me this was some kind of the golden solution to the "bankswitch-problem". Still you need to keep code and data in the same bank, but for my demo this never was a problem...

 

Also note, as these code fragments are taken out of the demo and modified for this discussion, so there might be bugs in there. If so, let me know, and I'll try to fix this. The demo was also coded using ca65 so the syntax for other assemblers might differ.

  • Like 1
Link to comment
Share on other sites

11 hours ago, SvOlli said:

Here's some code I use for bankswitching. It's a modified version of the code used in Bang!

 

There it's done with code generation, so this code differs. The cool part is that the banks are encoded in the upper 3 bits of the address when jumping to a routine. Note the comments when trying to use it, some lines need to be removed according to the bankswitching used.

 

There is a frontend used by macros that enable the jumping across banks, as well as for RTS. These need A and X for calculating the bank and the jump.

 

The backend expects the code for calculating the jump at the same address in each bank. I suggest ti put it directly before the bankswitching hotspots. Also note that the RTS will trigger a read on the following byte, so putting this before the hotspots is a very bad idea.

 ...

For me this was some kind of the golden solution to the "bankswitch-problem". Still you need to keep code and data in the same bank, but for my demo this never was a problem...

 

Also note, as these code fragments are taken out of the demo and modified for this discussion, so there might be bugs in there. If so, let me know, and I'll try to fix this. The demo was also coded using ca65 so the syntax for other assemblers might differ.

Thanks @SvOlli.  I'll study this and try to grok it!

Link to comment
Share on other sites

  • 2 weeks later...

I've implemented @Thomas Jentzsch's bank-switching macros, but I've run into a minor issue and a major issue.

 

Here is my adjusted version of the macros to work explicitly with a 16 K 'F6' style ROM:

 

;===============================================================================
; Bank-Switching and cross-bank common code (need to keep at same alignments)
; 
; These are Thomas Jentzsch's Atari standard bankswitching macros, 
;   tailored for F6  by M. Losh
; 
; Comment tags:
;   'j'  = Just Jeff on AA;
;   'TJ' = Thomas Jentzsch
;   'ml' = M. Losh
;===============================================================================
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 		;TJ {1} = bank number. 
						;j example: start each bank with with argument
						;j START_BANK 0, START_BANK 1, 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 $C000 + BANK_NUM*BANK_SIZE ;j skips over the first potential banks; ml revised to $C000 for F6 16K type ROM
						;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. 
						;TJ: $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
    ;TJ  optional Space for RAM+
;    DS.B    $100, "#"	;ml not using RAM+
  ENDM
 
; Insert code for executing the bankswitch,
; should be put at the very end of each bank, right before the hotspots
BANKSWITCH_SIZE = 7

  MAC SWITCH_BANK
    ORG     BANK_ORG + $ff6 - BANKSWITCH_SIZE 	;j Calculate where the hot spots ;ml adjusted offset for F6 ROM
												;j go in each bank
    RORG    BANK_RORG + $ff6 - BANKSWITCH_SIZE	;j Calculate where the hot spots ;ml adjusted offset for F6 ROM 
												;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 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     $fff6,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

; a convenience macro, put at the end of every bank
  MAC END_BANK 
   RORG $ff6			;ml adjsuted position for F6 ROM
    ds      6, 0 ;j Put zeros in the last (ml) 6 bytes for the hot spots before the
			; reset and break vectors
    .word   (START & $fff) | BANK_RORG  ; RESET
    .word   0                           ; BRK (unused)
  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	;tj 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  ;TJ 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 

 

The minor issue I have is that DASM does not like that BANKSWITCH_SIZE calculation, so I commented it out and replaced it with an hardcoded size of 7.

 

I have placed the bank start and bank end macros into my code to insert the needed bank-switching code instructions and to define the needed symbols and assemble-time equates (for example):

 

;===============================================================================
; BANK 0 -- Non-Playing Mode(s)
;===============================================================================
    START_BANK 0
START:
ColdStart_B0:
	lda $1ff6			;	switch to Bank 0 
	jmp InitSystem

; (SKIPPING AHEAD ... )

;===============================================================================
; MainFrameStart - Chooses correct main routine to begin new frame, based on mode
;===============================================================================
	DEF_LBL MainFrameStart
		SUBROUTINE
		lda Mode
		and #NonplayFlag
		beq .playing
		jmp MainNonPlay
.playing:
		lda Mode
		and #InsideCellFlag
		beq .exo
		jmp MainIntra
.exo:	jmp MainExo

;===============================================================================
; Main Routine for Non-Play Title/GameOver screen
;===============================================================================
MainNonPlay:
        jsr VerticalSync    	
        jsr VBlankNonPlay
        jmp KernelNonPlay
		
		; NOTE: KernelNonPlay will bank-switch & jump to  OverScanNonPlay
		;       OverScanNonPlay will bankswitch and jump back to MainFrameStart
		;		which will choose which main routine to run, if game has not
	    ;       started yet, it will jump back to MainNonPlay to complete this 
	    ;       main loop (distributed as it may be!)

; (SKIPPING AHEAD ... TO END OF DRAWING KERNEL )
NonPlayScreenDone:
		JMP_LBL OverScanNonPlay

;===============================================================================
; Define end of Bank
;===============================================================================
	SWITCH_BANK
	END_BANK 

;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
;===============================================================================
; BANK 1 -- Drawing Kernels, graphics data
;===============================================================================
	START_BANK 1	
ColdStart_B1:
	lda $1ff6			;	jump to Bank 0
	nop
	nop
	nop

; (SKIPPING AHEAD ... TO BANK 2 )

;===============================================================================
; OverscanNonPlay  - Non-playing screens overscan function; check for new game start
; --------------
;===============================================================================
	DEF_LBL OverScanNonPlay
	SUBROUTINE
        lda #2      ; LoaD Accumulator with 2 so D1=1
        sta VBLANK  ; STore Accumulator to VBLANK, D1=1 turns image output off
; (SKIPPING AHEAD ...)
FinishOverscanNonPlay:
.OSwait:
        lda INTIM      			; Check the timer
        bne .OSwait  			; Branch if its Not Equal to 0

        JMP_LBL MainFrameStart  ; JuMP to Main

;===============================================================================
; Define End of Bank
;===============================================================================
	SWITCH_BANK
	END_BANK 

;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
;===============================================================================
; BANK 3 -- Music, support stuff?
;===============================================================================
	START_BANK 3

(ect.)

 

The major issue I have is that when running, the bankswitch only lasts one instruction in Stella.  The first screenshot is Stella in Bank 0 just before I load $FF6+2 (y=2 to actually load $FF8) to switch to Bank 2. The second screenshot shows that after loading $FF8, Stella recognizes it should be in Bank 2, but the disassembly window is not showing $5xxx address range for Bank 2.  The RTS call should set the PC $5034 (address of my OverScanNonPlay routine), however after I single step, as you can see in the third screenshot, Stella says I am back to Bank 0, and the offset $034 (which would be OK in Bank 2) is not a valid routine in Bank 0.

 

Here are excerpts of the listing file that shows the call to the OverScanNonPlay in Bank 0, end of Bank 0,  and the entry point of OverScanNonPlay in Bank2:

*********** IN BANK 0 ********************
...
   2155  c869
   2156  c869				   NonPlayScreenDone
      0  c869					      JMP_LBL	OverScanNonPlay
      1  c869					      IF	OverScanNonPlay_BANK != BANK_NUM
      2  c869		       a0 02		      ldy	#OverScanNonPlay_BANK
      3  c86b		       a9 50		      lda	#>(OverScanNonPlay-1)
      4  c86d		       a2 33		      ldx	#<(OverScanNonPlay-1)
      5  c86f		       4c ef 1f 	      jmp	SwitchBank
      6  c872				  -	      ELSE
      7  c872				  -	      jmp	OverScanNonPlay
      8  c872					      ENDIF
   2158  c872

...
                                                                          
   2505  ce12							;===============================================================================
   2506  ce12							; Define end of Bank
   2507  ce12							;===============================================================================
   2508  ce12
   2509  ce12							; ORG $CFFA	    ; set address to 6507 Interrupt Vectors
   2510  ce12							; RORG $FFFA	     ; set address to 6507 Interrupt Vectors
   2511  ce12							; .WORD ColdStart_B0 ; NMI
   2512  ce12							; .WORD ColdStart_B0 ; RESET
   2513  ce12							; .WORD ColdStart_B0 ; IRQ
   2514  ce12
      0  ce12					      SWITCH_BANK
      1  cfef					      ORG	BANK_ORG + $ff6 - BANKSWITCH_SIZE
      2  cfef
      3  cfef					      RORG	BANK_RORG + $ff6 - BANKSWITCH_SIZE
      4  cfef
      5  cfef
      6  cfef		       1f ef	   SwitchBank =	(. & $fff) | $1000
      7  cfef
      8  cfef
      9  cfef		       48		      pha
     10  cff0		       8a		      txa
     11  cff1		       48		      pha
     12  cff2		       b9 f6 ff 	      lda	$fff6,y
     13  cff5
     14  cff5		       60		      rts
     15  cff6
     16  cff6
     17  cff6
     18  cff6
     19  cff6
     20  cff6
      0  cff6					      END_BANK
      1  cff6					      RORG	$ff6
      2  cff6		       00 00 00 00*	      ds	6, 0
      3  cffc
      4  cffc		       00 10		      .word.w	(START & $fff) | BANK_RORG
      5  cffe		       00 00		      .word.w	0
   2517  d000
   2518  d000
   2519  d000							;===============================================================================

*********** IN BANK 2 ********************
...
   4874  e034							;===============================================================================
   4875  e034							; OverscanNonPlay  - Non-playing screens overscan function; check for new game start
   4876  e034							; --------------
   4877  e034							;===============================================================================
   4878  e034
      0  e034					      DEF_LBL	OverScanNonPlay
      1  e034				   OverScanNonPlay
      2  e034		       00 02	   OverScanNonPlay_BANK =	BANK_NUM
   4880  e034					      SUBROUTINE
   4881  e034		       a9 02		      lda	#2	; LoaD Accumulator with 2 so D1=1
   4882  e036		       85 01		      sta	VBLANK	; STore Accumulator to VBLANK, D1=1 turns image output off
...

 

Weirdly, in the third screen shot, the Stella PC field shows '5034 OverScanNonPlay' correctly, but the disassembly window is labeled 'Bank 0' and shows code from the $1xxx range, which is Bank 0 code.  And if I keep stepping, I am definitely executing in Bank 0, which is not intended and soon leads to an illegal instruction crash.

 

Does anyone see anything wrong about how I'm using Thomas Jentzsch's macros?  I am running on the latest Stella 6.5.3 for Windows, and it seems to recognize by .bin as an F6 16K type correctly.   Any help or hints here would be appreciated!

--

Mike_Sohl

BeforeHotspotSwitch.png

AfterHotspotBeforeRTS.png

AfterRTS.png

Edited by Sohl
Extra notes, fixed misleading comment
Link to comment
Share on other sites

Additional observation for the bank switching issue above:  The routine I'm trying to switch to, which is intended to be in Bank 2, shows up in my Stella disassembly window in Bank 1 if I select Bank1 with the CartridgeF6 pull-down option list.  See screenshot.  But the disassembly address is $5034, which should be Bank 2 right?  Bank 1 contents should be shown with addresses in the $3xxx range, shouldn't they?

 

 

Bank1Disassem.png

Link to comment
Share on other sites

The problem is that the code is ending at $FFF5, right before the hotspot $FFF6, and because it is code the next address is read which triggers the bankswithch. If data is at $FFF5 it's fine, but RTS is not. The solution is just leave $FFF5 as data or put a buffer byte in there. I often just pad the byte before the hotspots with a buffer byte as I have ran into this situation many times.

  • Like 1
Link to comment
Share on other sites

5 minutes ago, Thomas Jentzsch said:

I suppose the RTS at $ff5 creates a dummy read at $ff6 and switches to bank 0. If you move the code one byte (BANKSWITCH_SIZE = 8 ) it should be fine.

  @Thomas Jentzsch: Yes, that seems to help!  I had to pad out the SWITCH_BANK macro with a NOP:

 

BANKSWITCH_SIZE = 8

  MAC SWITCH_BANK
    ORG     BANK_ORG + $ff6 - BANKSWITCH_SIZE 	;j Calculate where the hot spots ;ml adjusted offset for F6 ROM
												;j go in each bank
    RORG    BANK_RORG + $ff6 - BANKSWITCH_SIZE	;j Calculate where the hot spots ;ml adjusted offset for F6 ROM 
												;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 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     $fff6,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
	nop					;ml added for padding

;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

 

Any idea why the BANKSWITCH_SIZE calculation is not working in DASM?  I get a "Expression in mnemonic not resolved" error.

 

Thanks a lot for a usable workaround, Thomas, as well as the original bank-switching support macros!  With this more function-based banking code structure, I avoid a lot of graphics data redundancy and streamline things quite a lot.  Now I have much more code space for game logic!  And the macros give me an easy way to jump back and forth more if I need to move some code around later.  I really appreciate the examples and support.

 

Now I have a bit more debugging to do.... ?  Not everything is back to 100% working yet in the new bank layout.

--

Mike_Sohl

Link to comment
Share on other sites

On 5/8/2021 at 7:11 PM, Sohl said:
On 5/8/2021 at 6:42 PM, Thomas Jentzsch said:

I suppose the RTS at $ff5 creates a dummy read at $ff6 and switches to bank 0. If you move the code one byte (BANKSWITCH_SIZE = 8 ) it should be fine.

  @Thomas Jentzsch: Yes, that seems to help!  I had to pad out the SWITCH_BANK macro with a NOP:

I ran into that same problem hacking on code that uses the Arcadia Supercharger bankswitching. This is also why I put the reset routine directly before the bankswitch hotspots in my example code, as the JMP does not do a "dummy read". Also not only RTS, but every single byte instruction triggers a "dummy read" of the following address. So the byte after an "INX" for example is read twice. But that's always unnoticed.

  • Like 1
Link to comment
Share on other sites

The dummy read trick isn't bad at all if you use this functionality to your benefit.

 

One trick I have used to always start in the same bank with minimal overhead is to place something like this at the end of every bank:

 

 ; F4 example shown: use FFF5 for F6 or FFF7 for F8

 ORG $FFF3

 BRK

 ORG $FFFC

.word $FFF3

.word Start

 

The dummy fetch at $FFF4 occurs before the actual BRK is executed. So, no matter what bank things start in, you will always end up in bank 0 at "Start" without the need for explicit bankswitch instructions to do that.

 

You don't even need to use .word Start in every bank, only bank 0, so you can use BRK for other things in banks other than 0.

  • Like 3
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...