Jump to content
IGNORED

Bank Switching


Sergioz82

Recommended Posts

Hello everyone and happy new year!

 

I have this unhealty idea of using bank switching in a game so I can add way more sound and graphics assets, but unfortunately I only have a vague idea of what I'm talking about.

 

Let's say I want create a 64K ROM file for example, with 32K used for the game engine that will be loaded in RAM expansion and the remaining 32K used for assets that will be accessed by the engine during level loading and then transferred to VDP (data for bitmap mode tables, sprites patterns and attributes etc..). 

I made some searches and I found this, which seems to confirm it's feasible:

http://www.stuartconner.me.uk/ti/ti.htm#bank_switching

 

For what I understood, in addresses >6000 to >7FFF one won't find program data but word pointers to the actual 8K memory banks. So basically the 4A can manage up to 512 8K banks, correct?

 

And I ask for the future: (for when the game is possibily ready to be converted into bin format): will I need a customized loader/tool to create a working bin file?

 

Of course any tutorial or specific thread (I found some but they didn't go into detail or they were more hardware-oriented) is more than welcomed :)

 

Thanks in advance.

 

EDIT: I thought I was posting this in Development section. How can I move it there?

 

Edited by Sergioz82
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

11 minutes ago, Sergioz82 said:

Let's say I want create a 64K ROM file for example, with 32K used for the game engine that will be loaded in RAM expansion and the remaining 32K used for assets that will be accessed by the engine during level loading and then transferred to VDP (data for bitmap mode tables, sprites patterns and attributes etc..). 

 

You can also set up the game to run from cartridge space and only copy parts of the program to expansion RAM that are needed without regard to which ROM bank is addressed at any given time. The trampoline (bank-switching) code is an example of code that works best running from RAM. Whatever you decide to do, there are many of us here who can help. For example, my current version of fbForth runs from a 32-KiB cartridge ROM (4 banks).

 

...lee

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

2 minutes ago, Lee Stewart said:

 

You can also set up the game to run from cartridge space and only copy parts of the program to expansion RAM that are needed without regard to which ROM bank is addressed at any given time. The trampoline (bank-switching) code is an example of code that works best running from RAM. Whatever you decide to do, there are many of us here who can help. For example, my current version of fbForth runs from a 32-KiB cartridge ROM (4 banks).

 

...lee

That's cool! What about the execution speed? Is accessing the cartridge slower than accessing RAM?

Link to comment
Share on other sites

Just to be clear, the 4A does not handle bank switching in the cartridge space. Extra hardware in the cartridge handles it. 

 

In the cartridge design you reference, there is a single latch that hold a bank selector. When you right to an address in the cartridge ROM space, that hardware latches a number of bits of the address bus, and sets them as the bank select value. These are in turn used as the upper address bits of a ROM on the cartridge. 

 

Most cartridge emulators for real hardware or software emulation of the whole system support this banking hardware in the cartridge.

  • Like 5
Link to comment
Share on other sites

41 minutes ago, Sergioz82 said:

That's cool! What about the execution speed? Is accessing the cartridge slower than accessing RAM?

 

No, because they are both on the 8-bit bus. Obviously, any code you run from Scratchpad RAM (>8300 – >83FF) runs on the 16-bit bus and, of course, is faster than ROM or Expansion RAM. The only thing that ultimately does make ROM execution a bit slower is the necessity of running the trampoline code to switch ROM banks. If you can spare the space, that code should run from Scratchpad RAM anyway.

 

...lee

  • Like 3
Link to comment
Share on other sites

1 hour ago, Sergioz82 said:

For what I understood, in addresses >6000 to >7FFF one won't find program data but word pointers to the actual 8K memory banks. So basically the 4A can manage up to 512 8K banks, correct?

Yes you will ...find program/data there. Writing to ROM addresses, can be used to switch to the PAGES, that are associated with those addresses, but wont effect the data at the ROM addresses being written to.

  • Like 3
Link to comment
Share on other sites

Basically, the cartridge port is 8k of memory. You can put anything you want in that 8k - code, program, pointers, it's up to you.

 

The standard banking scheme on the TI is to write to a ROM address - the address you choose becomes the extended address bits. Every /other/ address can be used in this way (because of the 16-bit nature of the CPU), so you have 4k worth of banks. 4k of address expansion times 8k of memory space gives 4096*8192 = 32MB of potential memory space.

 

I took it further with Dragon's Lair by adding the data bus, which gives you a potential 8GB, but I recommend keeping it to the standard till you need that, cause it adds complications.

 

The primary gotcha with this scheme is that it banks the /entire/ 8k space. This means if your program is running from cartridge memory, the memory can be switched out from underneath it. There are two main ways to deal with this:

 

- first is to copy the program to RAM and run from there - whether scratchpad or memory expansion. Many cartridges do this - some cartridges copy their entire program to memory expansion and just use the cartridge as data storage after loading.

- second is to have the same code in every page of the cartridge - at least at the memory addresses that switch. This way, even though the page has changed, the same code is present in both the starting and ending pages, so the program continues unabated. I like to use this technique with a subroutine that handles the banking - I set the target page and address to jump to, then call the subroutine. It switches the page and then jumps to the new address - only this little stub of code needs to exist on all pages.

 

You will ultimately be responsible in your own code for managing the paging and knowing where code and data is stored. I handle it manually myself, but I know there is some support for building the code and images in the xdt99 toolsuite. 

 

 

  • Like 6
  • Thanks 2
Link to comment
Share on other sites

RXB 2021 added a 3rd ROM to the XB cart so to speed up the execution all the Registers are in Scratch Pad.

The Assembly programs are still in the ROM running from the 8 bit bus but uses the Scratch Pad 16 bit bus for registers that I load XB variables into for speed.

For example CALL HCHAR in RXB is 9 times faster than CALL HCHAR in normal XB.

 

Switching ROM pages is done from GPL using CLR @>6004 that would turn on that ROM page, and CLR @>6000 to reset back to page 1 like XB normally does.

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

@Tursi

Thank you very much.

 

For the moment I'll keep it simple, I think the game I have in mind can run by loading the code in RAM and then accessing the banks only for assets. I don't know yet how how much space I'll end up using but surely 32Mb are way more than my needs so I think the assets problem is over.

 

Anyway you guys opened a new world of possibilities to me :)

For example there's another game I have in mind, but for the moment it is too complex for my skills. One of the difficulties I thought of, aside storing assets, was how to replace the game engines in RAM by loading them from ROM according to what has to be shown on screen. Now also this problem is already solved (except the actual coding but that's a problem for another day)

 

 

 

  • Like 1
Link to comment
Share on other sites

I'm using XDT99 assembler and its banking features.  Here's the bankswitch code I'm currently using in my game, which does not require expansion RAM; all banking is done by the ROM program in the 6000-7FFF space.  I created a separate label for each bank, since I wanted to minimize register usage during bankswitching, the tradeoff is using a bit of ROM space in all banks.  I'm not saying you have to do it this way, since you are planning to use expansion RAM, these techniques would be unnecessary.

 

* Cartridge memory space
CARTAD EQU >6000             ; Cartridge address space
CARTED EQU CARTAD+>1FFF      ; Cartridge space end
BANK0W EQU CARTAD             ; Bank 0 write address
BANK1W EQU CARTAD+2           ; Bank 1 write address
BANK2W EQU CARTAD+4           ; Bank 2 write address
BANK3W EQU CARTAD+6           ; Bank 3 write address
BANK4W EQU CARTAD+8           ; Bank 4 write address
BANK5W EQU CARTAD+10          ; Bank 5 write address
BANK6W EQU CARTAD+12          ; Bank 6 write address
BANK7W EQU CARTAD+14          ; Bank 7 write address

       SAVE CARTAD,CARTAD+>2000  ; Assembler writes full 8K banks

       AORG CARTAD
***************************************************************
       BANK ALL
***************************************************************
       BYTE >AA     ; Standard header
       BYTE >00     ; Version number 1
       BYTE >01     ; Number of programs (optional)
       BYTE >00     ; Reserved (for FG99 this can be G,R,or X)
       DATA >0000   ; Pointer to power-up list
       DATA PRGLST  ; Pointer to program list
       DATA >0000   ; Pointer to DSR list
       ;DATA >0000   ; Pointer to subprogram list  (this doubles as next program list entry)

PRGLST DATA >0000   ; Next program list entry
       DATA START   ; Program start address
       STRI 'LEGEND OF TILDA'
       EVEN


START  CLR @BANK0W
       JMP x#MAIN

; Note: BANKnX must be followed by DATA destination address in new bank
; Return address R11 is modified to return after DATA
; otherwise BANKn must be called with target address in R13
BANK1X MOV *R11+,R13
BANK1  CLR @BANK1W       ; sprload.asm, enemload.asm
       B *R13

BANK2X MOV *R11+,R13
BANK2  CLR @BANK2W       ; map.asm
       B *R13

BANK3X MOV *R11+,R13
BANK3  CLR @BANK3W       ; tiles.asm
       B *R13

BANK4X MOV *R11+,R13
BANK4  CLR @BANK4W       ; herospr.asm, keys.asm, gameover.asm
       B *R13

BANK5X MOV *R11+,R13
BANK5  CLR @BANK5W       ; movobj.asm, status.asm
       B *R13

BANK6X MOV *R11+,R13
BANK6  CLR @BANK6W
       B *R13

BANK7X MOV *R11+,R13
BANK7  CLR @BANK7W
       B *R13

* Bank 0 last, to take advantage of JMP for returning to bank 0
BANK0X MOV *R11+,R13
BANK0  CLR @BANK0W       ; main.asm, hero.asm, scroll.asm
       B *R13

***************************************************************
       BANK 0
***************************************************************

MAIN
       LIMI 0      ; Interrupts off
       LWPI WRKSP  ; Set workspace pointer

       ; example of using bank switch with target address in following data statement
       BL @BANK3X          ; tiles.asm
       DATA x#LTITLE       ; Load title tiles in bank3

       ; ...


***************************************************************
       BANK 3
***************************************************************

LTITLE
       MOV R11,R13   ; Save return address

       ; code to load title tile graphics

       JMP BANK0    ; Return to saved address in R13 bank 0
       ; could also use "B @BANK0" if JMP can't reach

 

Edited by PeteE
  • Like 5
  • Thanks 2
Link to comment
Share on other sites

That's what the p-code card does, for example. Part of the GROM address space is used as an executable assembly code repository. This data is copied from GROM to RAM twice, first one chunk at startup and then another for runtime support. So for sure you can do that. It can of course not execute TMS 9900 machine code directly from GROM.

On the other hand the 99/4A can execute GPL from GROM, and the p-code card's PME can execute p-code directly from GROM.

Or you can use the GROM space to store data for screen layouts or whatever else you can think of.

 

Another bank switching trick is to use a CRU output to form parts of the address of the extended banked memory. Hardware permitting, of course.

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

@PeteE

 

Thank you very much for the code!

 

I downloaded XDT99 but I still have to read the documentation/thread. To this day I always programmed on E/A via Classic99 but it's time to move on, I suppose :)

 

Can I ask you if the following statements are correct? Just to check if I got something wrong, better figure it out now than later while coding:

 

 

1) I assume via ASM one can't write on banks, only read. So, a write operation at a specific word in the cartridge address interval is used only to tell the computer I want to select the corresponding memory bank. Then the cartridge address interval puts me through said 8K memory bank (>6000 = memory bank byte 0, >6001 = memory bank byte 1 etc)

 

2) the headers are not required if I use the banks only for assets storage. I was thinking to write an address table (map level 1 EQU bankX+yy, sprites level 1 EQU bankZ+kk and so on) and a routine to manage the transfer from banks to VDP.

 

3) In order to create a ROM file with data assigned to the banks the way I want I have to use XDT99, E/A can't do that.

 

4) If I want to test the program I can't just compile and create a E/A 3 or 5 file, I must have a bin file.

 

Link to comment
Share on other sites

11 hours ago, retrodroid said:

Not to derail the thread, but aside from the "unbanked" 8K or cartridge ROM, is it also possible to add/use a GROM to store DATA (but not code)?

A number of TI titles also did this! Only thing to remember is that GROM is 4-5 times slower than ROM (not including set up time if you need to change the address) - more than adequate for loading from, but test your needs if you want to use it real time in your game loop.

 

On a stock, non-paged cartridge, you can have up to 40k of GROM (using 8k GROMs, which these days there's no reason not to.) If you want to page GROM, TI defined a standard system where you simply access the GROM from a different address base. The console checks up to 16 of these bases, but there is room in the memory map for 256 of them, so you can technically do up to 10MB of GROM using this scheme. I released the "ubergrom" AVR firmware to support up to 120k in a single chip (if you are going to hardware). The only gotcha with GROM bases is that if you are going to use them for data only, I recommend that the first 32 bytes of the first two bases be identical. Otherwise there is a bug in the TI boot code that builds the program list which will make it spin forever looking for the GROM program list. If the first 32 bytes of the first two bases are identical, then it assumes bases are not in use and doesn't bother scanning the rest.

 

  • Like 2
Link to comment
Share on other sites

2 hours ago, Tursi said:

A number of TI titles also did this! Only thing to remember is that GROM is 4-5 times slower than ROM (not including set up time if you need to change the address) - more than adequate for loading from, but test your needs if you want to use it real time in your game loop.

 

On a stock, non-paged cartridge, you can have up to 40k of GROM (using 8k GROMs, which these days there's no reason not to.) If you want to page GROM, TI defined a standard system where you simply access the GROM from a different address base. The console checks up to 16 of these bases, but there is room in the memory map for 256 of them, so you can technically do up to 10MB of GROM using this scheme. I released the "ubergrom" AVR firmware to support up to 120k in a single chip (if you are going to hardware). The only gotcha with GROM bases is that if you are going to use them for data only, I recommend that the first 32 bytes of the first two bases be identical. Otherwise there is a bug in the TI boot code that builds the program list which will make it spin forever looking for the GROM program list. If the first 32 bytes of the first two bases are identical, then it assumes bases are not in use and doesn't bother scanning the rest.

 

Excellent info, thanks!  So I am challenging myself to code my project so it can/could be deployed on a cart to an unexpanded TI99/4A, ideally using similar storage capacities and techniques as they used back in the 80s. What was the largest cartridge sold back then in terms of ROM and GROM banks?

 

It seems like maybe I won't even need to implement that SLE algorithm for my level screens if I can use basically limitless GROM to store them anyway. But where's the fun in that? ;)

 

 

Edited by retrodroid
Link to comment
Share on other sites

Not sure on the largest... but ignoring third party XB was 12K of ROM bank-switched and 32k of GROM. Parsec was 8k of ROM and 24k of GROM. (Edit: well, technically 24k on XB and 18k on Parsec, because they used 6k GROMs).

 

Third party - well, sky's the limit. ;) I know there were a lot of large cartridges released.

 

 

Edited by Tursi
  • Thanks 1
Link to comment
Share on other sites

6 hours ago, Sergioz82 said:

1) I assume via ASM one can't write on banks, only read. So, a write operation at a specific word in the cartridge address interval is used only to tell the computer I want to select the corresponding memory bank. Then the cartridge address interval puts me through said 8K memory bank (>6000 = memory bank byte 0, >6001 = memory bank byte 1 etc)

 

The banks are switched by writing to even addresses. For a normally, (non-inverted) latched banking scheme, the following obtain:

       CLR  @>6000     ; selects BANK 0
       CLR  @>6002     ; selects BANK 1
       ...

 

...lee

Edited by Lee Stewart
clarification
  • Like 1
Link to comment
Share on other sites

5 hours ago, Sergioz82 said:

2) the headers are not required if I use the banks only for assets storage. I was thinking to write an address table (map level 1 EQU bankX+yy, sprites level 1 EQU bankZ+kk and so on) and a routine to manage the transfer from banks to VDP.

You cannot assume that all hardware, i.e. cartridge boards, start up in bank 0, so it's generally best to add headers to all banks, if possible. If it's not possible, e.g. if you need to store exactly 8K in some banks, you should at least add headers to the first and the last banks, and your quit handler should switch to the first bank before quitting.

  • Like 1
Link to comment
Share on other sites

5 hours ago, Sergioz82 said:

3) In order to create a ROM file with data assigned to the banks the way I want I have to use XDT99, E/A can't do that.

 

4) If I want to test the program I can't just compile and create a E/A 3 or 5 file, I must have a bin file.

It's certainly a lot easier with XDT99. Using E/A you could generate E/A#5 files for each bank, remove the headers, and combine them. Yes you need a bin file.

 

  • Like 1
Link to comment
Share on other sites

3 hours ago, Asmusr said:

You cannot assume that all hardware, i.e. cartridge boards, start up in bank 0, so it's generally best to add headers to all banks, if possible. If it's not possible, e.g. if you need to store exactly 8K in some banks, you should at least add headers to the first and the last banks, and your quit handler should switch to the first bank before quitting.

Even if the cartridge /does/ power on in bank 0, you should still have headers in every bank, or a way to reset when the console resets. Otherwise, if the user resets the console while the cartridges is in a different page, it won't show up and a power cycle will be needed. It doesn't cost much to put it in every bank with a page switch as the first instruction.

 

We've also done cartridges with GPL startups that fix the bank.

 

If you do have a quit handler that resets the cartridge as Rasmus suggests, that definitely works, just make sure you handle QUIT yourself and not via the console interrupt. With that in place the only gotcha is people with hardware reset buttons. ;)

 

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