A question about Tables, indexing, and banks

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
A question about Tables, indexing, and banks
by on (#237226)
Hello!

I've been writing and studying basic SNES/65816 examples that I can look at in a debugger since visually analyzing things enables me to learn better. One of the more basic concepts which I thought I fully understood but apparently don't is tables.

My main question is: Is it up to the programmer to specify which bank the table is stored in when trying to access it?

Yesterday I was writing a very simple program that accessed, read and stored a value from a table. For the fun of it I specified a different bank since I figured in larger programs you might be required to do that anyways. Little did I know this would cause quite a bit of confusion for myself. My table looked like this (Note: I am using WLA-DX and the .BANK directive to specify bank 1 instead of bank 0):

Code:
.BANK 1 SLOT 0
MyTable:
   .db $10,$11,$12,$13,$14,$15,$16,17,$18,$19,$1A,$1B,$1C,$1D,$1E,$1F


Then i had a small snippet of code that loaded Y with a value, and I accessed MyTable with the index and stored it in $10 (Another note: I have ldx $77 in there so that I could set a breakpoint on 'read' on 7E0077 to see the execution of everything afterwards):

Code:
ldy #$05
ldx $77
lda MyTable,y
sta $10


In this instance I expected to see the value #$15 stored at RAM location 7E0010. Instead I was seeing the value #$A2 being stored. This confused me and I scratched my head for a couple hours. When I debugged the program I would see this:

Code:
$00/8148 A6 77       LDX $77    [$00:0077]   A:5BA2 X:0000 Y:0005 P:envMxdizC
$00/814A B9 1C 81    LDA $811C,y[$00:8121]   A:5BA2 X:0000 Y:0005 P:envMxdiZC
$00/814D 85 10       STA $10    [$00:0010]   A:5BA2 X:0000 Y:0005 P:eNvMxdizC


When I looked in ROM at 80811C+5 the value there was indeed #$A2. I kept looking back and forth, re-running, tracing until finally after about half an hour it hit me. My table is in bank 1, but the program is looking at bank 0. :|

I looked at bank 1 instead and sure enough my correct table values were there. I modified my program to have the table in bank 0 and it stored the proper values. All that being said I learned a good lesson out of it. I suppose I incorrectly assumed that by specifying the table is in bank 1 that the main code where I do lda MyTable,y it would know to look in bank 1 rather than default to bank 0.

Looping back to my original question of Is it up to the programmer to specify which bank the table is stored in when trying to access it? I am guessing the answer is yes? I am re-reviewing addressing modes but if anyone has any insight or examples I would appreciate it.
Re: A question Tables, indexing, and banks
by on (#237232)
You have long modes where you the programmer specify the bank:
Code:
lda  $123456 ; Read from $12:3456

Most other addressing modes will use the bank specified by the DBR (data bank register). Access the DBR with the PHB/PLB instructions.
Code:
lda  #$AB
pha
plb
lda  $1234 ; Read from $AB:1234

Stack relative and direct page are always bank zero, but subsequent indirects use DBR. (direct page can also be offset via the DP register through PHD/PLD)
Code:
lda  $12   ; Read from $00:0012
lda  ($12) ; Read 16-bit address from $00:0012, then read from DBR + 16-bit address
lda  [$12] ; Read 24-bit address from $00:0012, then read from 24-bit address

Indirect jumps are a law unto themselves:
Code:
jmp ($1234)   ;
jmp [$1234]   ; Read from bank zero + $1234
jsr ($1234,x) ;
jmp ($1234,x) ; Read from program bank + $1234 + x

Some other instructions have their own rules too (e.g. block moves), but that should cover the most common ones. Page crossing is also a thing, but that won't come up too often.

If in doubt, refer to the manual. Chapter 17 is rather thorough.
Re: A question Tables, indexing, and banks
by on (#237235)
The term "bank" here is a bit vague for me. I can't tell if you're talking about the WLA DX assemblers' concept of "banks" (through .bank directive or related), or if you're talking about 65816 banks.

If you're talking about WLA-DX bank concepts etc: I can't help here as I don't quite understand them (it's an assembler I don't use). I suspect, however, the WLA DX "bank" stuff just means "banks of data" in a nebulous/abstract form. I get the impression the assembler refers to banks as bank 0, bank 1, bank 2, etc. but those don't necessarily correlate with actual 65816 banks. This quickly gets into a discussion about SNES memory modes (mode 20 vs. mode 21) and actual the actual ROM memory map. There needs to be a way to "connect" those two together, however, for it to be effective; mode 20 is trickier in this regard because only the upper 32KBytes of a 65816 bank contains your ROM (i.e. the first 64KB of your ROM is mapped to $808000-FFFF and $818000-FFFF, as well as $008000-FFFF and $018000-FFFF), but mode 20 is easier in other regards.

If you're talking about 65816 banks: Kingizor's post is spot on. Long addressing (24-bit) on the 65816 is available and you can use it as you see fit. If the additional cycle times are a problem, you can always use plb to manipulate the active bank data is being read from (ex. sep #$20 / lda #$82 / pha / plb to set B=$82, followed by lda $9481 which would read a byte of data from $829481). You can back up the current B register (sometimes called DB -- and don't confuse this with the A/B/C nomenclature of the accumulator, e.g. xba and tdc) on the stack through use of phb. This methodology (changing B/DB) is very, very common on the 65816. Or you can just do lda.l $829481 and pay the cycle penalty for every access. It's up to you -- there's no wrong approach.
Re: A question Tables, indexing, and banks
by on (#237244)
Thank you both for the replies. Modifying the DBR is exactly what I was looking for. So now I have:

Code:
lda #$81
pha
plb

ldy #$05
ldx $77
lda CollisionMap,y
sta $10


Which produces the expected result when my table is stored in bank 1.

Quote:
I can't tell if you're talking about the WLA DX assemblers' concept of "banks" (through .bank directive or related), or if you're talking about 65816 banks.


Honestly I thought they were directly correlated. I am going to do some more debugging and playing around with that directive. When I assemble I see the following which would mean each of my banks are 32kb in size:

Code:
Free space at $0162-$7fb1.
Free space at $7fb6-$7fbf.
Free space at $7fe0-$7fe3.
Free space at $7ff0-$7ff3.
Free space at $8000-$811b.
Free space at $812d-$3ffff.
Bank 00 has 32354 bytes (98.74%) free.
Bank 01 has 32751 bytes (99.95%) free.
Bank 02 has 32768 bytes (100.0%) free.
Bank 03 has 32768 bytes (100.0%) free.
Bank 04 has 32768 bytes (100.0%) free.
Bank 05 has 32768 bytes (100.0%) free.
Bank 06 has 32768 bytes (100.0%) free.
Bank 07 has 32768 bytes (100.0%) free.
261713 unused bytes of total 262144.


Are mode 20 and 21 another way of saying LoROM mode and HiROM mode respectively? I've been referring to here: https://en.wikibooks.org/wiki/Super_NES ... _map#LoROM Below the memory map table there's a snippet that sounds like it's talking about the same thing you are. If they are the same I have been using LoROM mode. Nearly all (if not all of them) examples I've seen use some variation of a header written by Neviksti. The one I am currently using is a lot more WLA directive "heavy".

Couple followup questions: Long addressing is one extra machine cycle compared to absolute addressing (i.e what I'm doing now) is that correct? If it is then could it potentially be detrimental to the program if I am looping through values using long addressing? I know I don't have many values right now but let's say for example in the context of a game where I have a bunch of data stored in tables.
Re: A question Tables, indexing, and banks
by on (#237247)
JG92 wrote:
Are mode 20 and 21 another way of saying LoROM mode and HiROM mode respectively?
Yes.
JG92 wrote:
Long addressing is one extra machine cycle compared to absolute addressing (i.e what I'm doing now) is that correct?
Yes.
JG92 wrote:
Long addressing is one extra machine cycle compared to absolute addressing (i.e what I'm doing now) is that correct? If it is then could it potentially be detrimental to the program if I am looping through values using long addressing? I know I don't have many values right now but let's say for example in the context of a game where I have a bunch of data stored in tables.
Potentially, yes. This is where changing the DBR comes in handy.
Re: A question Tables, indexing, and banks
by on (#237248)
Thanks! On that note I'll probably stick with what I am doing now unless I find I have a particular need to use another way.

edit: Also I see it now(The 20 & 21 corresponding to LoRom & HiRom). In Neviksti's original header he has it commented:

Code:
.DB     $20                         ; Memory Mode   ( $20 = Slow LoRom, $21 = Slow HiRom )
Re: A question Tables, indexing, and banks
by on (#237250)
Yes, the term LoROM and HiROM generally refer to mode 20 and 21 memory mappings. These are dictated by the actual PCB itself (how Nintendo chose to run address/data lines, etc.). There are several "variations" of these, however, but there are also a very well-known set of standard board types. These correlate directly with the 65816's addressing space, i.e. "how" your ROM ends up being laid out in memory. The wiki page you referenced explains it, but is also extremely verbose -- Tepples made a visual that's easier to understand (first = general map, 2nd = mode 20, 3rd = mode 21). Make sense?

Bank $00 on the 65816 is special because it's where direct page RAM always resides, where most MMIO registers are, and -- most importantly -- where the 65816/6502 vectors have to be (for RESET, NMI, IRQ, BRK, and COP) up in the $00FFxx area. The CPU always loads them out of bank $00. So you'll find that both mode 20 and mode 21 tend to ensure that at least 32KB of ROM ends up being at $008000-FFFF. Also make sense?

It looks like your assembler is generating 32KB banks, which means your ROM result is probably mode 20. That's fine! But adhering to mode 20, this means your ROM actually is mapped like this in to 65816 addressing space:

$008000-FFFF = WLA DA bank 0
$018000-FFFF = WLA DA bank 1
...
$808000-FFFF = WLA DA bank 0 (can use high speed/FastROM mode)
$818000-FFFF = WLA DA bank 0 (can use high speed/FastROM mode)
...

The advantage of this mode is that $xx0000-7FFF is identical throughout most of the banks (there are some exceptions), which means you can access MMIO registers while in any bank without any problem (i.e. lda #$86 / pha / plb / lda $94f2 / sta $2100 will work exactly how you think).

The "high speed/FastROM" mode I describe is relevant when you're using those banks/addressing ranges and have $420D bit 0 set to 1. In that situation, things there execute at 3.58MHz, rather than the usual 2.68MHz elsewhere.

Mode 21 is an entirely different beast, where there's a similar mode 20 layout for banks $00-3F, and again for banks $80-BF. However, banks $C0-FF are completely different: here your ROM is completely 100% linear and in 64KByte banks (which is the normal size of a bank on 65816). I could tell you how WLA DX implemented support for this if I had tried it, but I suspect it might be something like this:

$008000-FFFF = WLA DX bank 0 (first 32KB)
$018000-FFFF = WLA DX bank 0 (next 32KB)
...
$808000-FFFF = WLA DX bank 0 (first 32KB) (can use high speed/FastROM mode)
$818000-FFFF = WLA DX bank 0 (next 32KB) (can use high speed/FastROM mode)
...
$C00000-FFFF = WLA DX bank 0 (full 64KB)
$C10000-FFFF = WLA DX bank 1 (full 64KB)
...

Make sense? (Probably not)

As for your follow-up question about long addressing:

Correct, long (24-bit) addressing takes 1 additional CPU cycle. It's impossible for me to answer whether or not it could be "detrimental" to your program because I don't know your program -- you're the author, and I don't think it would be fair to ask me to review your entire program for optimisations/profiling.

If you have a routine that is being executed in a timing-sensitive area (such as NMI or HBlank), places where every CPU cycle counts, then you need to take this into consideration.

Just remember that the "setup" (sep/lda/pha/plb) and the "restore" (plb, possibly with a sep) take cycles too, so depending on what you're doing + how much data + whatever, it may be faster to use long addressing. If you aren't sure, count the cycles yourself by hand.

In other cases -- take for example what I just showed you about mode 21, think about code running out of banks $C0-FF -- you're going to have to use long addressing to access MMIO registers (e.g. sta $002100). There are magic tricks to avoid that though, depending on what you are doing (particularly how much MMIO register access you might be doing). A technique I've seen used in commercial games (just as an example for screen brightness) is rep #$20 / tdc / pha / lda #$2100 / tcd / sep #$20 / lda #$0f / sta $00 / ... / rep #$20 / pla / tcd. This points D (direct page) to $2100, which is at the start of the MMIO register area; the sta $00 thus writes to $2100, with backing up and restoration of the D register. You obviously can't access your own direct page variables in the midst of that, though.

Like I said: it's impossible to cover every situation.

Hope this helps.
Re: A question Tables, indexing, and banks
by on (#237259)
This is very helpful thank you. Mode 21 looks interesting but more complicated than mode 20. I'll probably look into it way later on but stick with mode 20 while I am still learning the basics. Those maps from tepples are a great visual! Looks like I am good to go for now. Not to mention a lesson I won't soon forget given I was banging my head against the wall for a solid 2 hours yesterday. Stupid computer/emulator, doing what I told it to do. Can't it just read my mind or something?! :lol: