Sohl Posted April 25, 2021 Share Posted April 25, 2021 (edited) 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 April 25, 2021 by Sohl typo 1 Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted April 25, 2021 Share Posted April 25, 2021 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 2 Quote Link to comment Share on other sites More sharing options...
JetSetIlly Posted April 25, 2021 Share Posted April 25, 2021 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. 3 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted April 25, 2021 Share Posted April 25, 2021 During development of Boulder Dash, we found that 2K slices are not flexible enough and we had to add a lot of redundant code and data. Therefore I came up with 3E+, which has 1K slices and supports a lot of extra RAM. 3 Quote Link to comment Share on other sites More sharing options...
Sohl Posted April 25, 2021 Author Share Posted April 25, 2021 (edited) @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 April 25, 2021 by Sohl typo Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted April 25, 2021 Share Posted April 25, 2021 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). 3 Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted April 26, 2021 Share Posted April 26, 2021 Thomas' bankswitching macro info can be found here: 3 Quote Link to comment Share on other sites More sharing options...
Ben_Larson Posted April 26, 2021 Share Posted April 26, 2021 (edited) 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 April 26, 2021 by Ben_Larson 2 Quote Link to comment Share on other sites More sharing options...
Sohl Posted April 26, 2021 Author Share Posted April 26, 2021 Thanks @SpiceWare for the reference. I will study it! @Ben_Larson, yes this does seem more logical and straightforward the more I think about it. It will just take some significant shifting of code among the banks! Really seems I should go this way. 1 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted April 26, 2021 Share Posted April 26, 2021 20 hours ago, SpiceWare said: Thomas' bankswitching macro info can be found here: Thanks for remembering. I had already forgotten that I had posted this. 2 Quote Link to comment Share on other sites More sharing options...
+SvOlli Posted April 27, 2021 Share Posted April 27, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
Sohl Posted April 28, 2021 Author Share Posted April 28, 2021 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! Quote Link to comment Share on other sites More sharing options...
Sohl Posted May 8, 2021 Author Share Posted May 8, 2021 (edited) 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 Edited May 8, 2021 by Sohl Extra notes, fixed misleading comment Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted May 8, 2021 Share Posted May 8, 2021 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. Quote Link to comment Share on other sites More sharing options...
Sohl Posted May 8, 2021 Author Share Posted May 8, 2021 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? Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted May 8, 2021 Share Posted May 8, 2021 The high bits of the address for each bank are arbitrary, so Stella cannot know them. It will change them as soon as there is a JMP or JSR using the current banks bits. 1 Quote Link to comment Share on other sites More sharing options...
+Omegamatrix Posted May 8, 2021 Share Posted May 8, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
Sohl Posted May 8, 2021 Author Share Posted May 8, 2021 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 Quote Link to comment Share on other sites More sharing options...
+SvOlli Posted May 11, 2021 Share Posted May 11, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
+batari Posted May 11, 2021 Share Posted May 11, 2021 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. 3 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.