Banks and zero page using wla-dx

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Banks and zero page using wla-dx
by on (#238511)
So a bit ago I asked how to show some graphics on the screen and managed to get it to work! :)

Now I wanted to add some sound! I arranged a song using SNES GSS and created the .spc file for it.

Now I tried reading online about how to load SPC programs and run them, and the documentation that I managed to find was... lacking (so to speak)

However I did manage to find a SNES program that plays SPC files. It was written with the wla-dx assembler in mind. Porting it to fit into my ca65 project seemed impossible; there was a lot of code!

Instead, I had the bright idea that rather than try to figure out how to port and incorporate that code into my program, it'd be a hell of a lot easier to port and incorporate my meager program into that.

To begin, I tweaked the spc player a bit and got it to play my .spc without showing anything on the screen. So far so good.

Now the next logical step to me would be to just include my binaries (palette data, map data, and tile data) in the ROM.

There was already some code in the spcplayer's main .asm file that included the needed data from the .spc file:

Code:
.bank 1 slot 0

 ; SPC-700 register values
.org $0000
            .incbin     SPC_FILE skip $00025 read $0008

; DSP register values
.org $4000
.incbin     SPC_FILE skip $10100 read $0080

 ; The actual 64k SPC RAM dump

.bank 2

.section "musicDataLow"
.incbin     SPC_FILE skip $0100 read $8000
.ends

.bank 3

.section "musicDataHigh"
.incbin     SPC_FILE skip $8100 read $8000
.ends


That looked good to me. However I had an additional 32K of data to add in along with a single zero byte to use for DMA purposes. Clearly banks 1-3 are full, and bank 0 is for code. So I thought, "Ok, I'll use banks 4 and 5". Thus I added these lines of code:

Code:
.bank 4

palette0:   .incbin     "img/frame0.pal" FSIZE palettelen
palette1:   .incbin     "img/frame1.pal"

frame0map:  .incbin     "img/frame0.map" FSIZE frame0maplen
frame0chr:  .incbin     "img/frame0.chr" FSIZE frame0chrlen

.bank 5

frame1map:  .incbin     "img/frame1.map" FSIZE frame1maplen
frame1chr:  .incbin     "img/frame1.chr" FSIZE frame1chrlen

zerobyte:   .db         $00

.define counter_limit   50


Well the assembler did NOT like that at all...

Code:
project $ wla-65816 -o main.o main.asm
main.asm:185: DIRECTIVE_ERROR: ROM banks == 4, selected bank 4.
main.asm:185: ERROR: Couldn't parse ".bank".
project $


Are banks 4 and 5 not a thing? I read the wikibooks article on memory mapping and it says that the SNES has 256 banks and that ROMs can have up to 4 megabytes of data. To be honest though, the article is pretty sparse and I have no idea what the hell anything is at all! There's this table that I have no idea what it's saying:

Image

That table honestly goes in one ear and out the other...

Furthermore, in my code I need to use some variables, i.e. some scratch space. So in my original project which used ca65, I had the following:

Code:
.segment "ZEROPAGE"

bgstatus:   .res    1
counter:    .res    1

init_bg = %00000001
bgmask  = %00000011


The thing is, wla-dx has no "zero page" directive. But I also realized that while I understand the definition of a zero page (the page that is at address $0000), I don't understand why variables need to go there only...

So here are my questions:

    1. What in the hell is a bank and why is it important?
    2. Why does the assembler not like more than 3 banks?
    3. Why do variables need to go in the zero page? What's important about the zero page?
    4. How do I define the zero page using wla-dx?

I'm totally cool with just being handed some reading materials if they clearly explain what is going on. The problem is I'm stuck between small, vague snippets of text here and there, and the full-on SNES development manual. Neither is great, and so here I am (or I should say, here I have been lol)



UPDATE:

In case it's relevant / anyone wants it, I've attached a zip file containing my entire project (or more accurately, my cannibalized version of spcplayer)
Re: Banks and zero page using wla-dx
by on (#238513)
rchoudhary wrote:
Are banks 4 and 5 not a thing?

Not in your project they're not. You told the assembler to make four banks, and they start at bank 0. So banks 4 and 5 are the fifth and sixth banks of those four. If you want more banks, you have to tell the assembler you want a bigger ROM.

More generally, since you seem confused about this:

The SNES has a 24-bit main address bus. This means it can directly address up to 16 MB, but that includes ROM, RAM, MMIO registers, unused address spaces that return garbage, etc., and the biggest practical ROM is just shy of 12 MB. However, the SNES only has an 8-bit data bus, which means that when an instruction includes an address, it has to be loaded in 8-bit chunks. And being a 65xx processor, it was designed to operate at pretty close to the maximum speed allowed by the memory, so the data bus is the bottleneck.

Therefore, in order to increase processing speed, not all instructions use 24-bit addressing. Every 8 bits you can shave off the instruction results in a one-cycle speedup of that instruction (broadly speaking; there are exceptions) and a one-byte saving in code size (universally, no exceptions). So 16-bit and even 8-bit addressing are used extensively in SNES code.

The 6502 only had 16-bit and 8-bit addressing. An 8-bit address was simply used as is with no offset, which resulted in faster access to the first 256 bytes in the 64 KB memory space. From this we get the term "zero page". Variables do not have to go in zero page on a 6502, but your code will be faster if you use zero page wisely. It will also be smaller, which is much more important on a 6502 than on a 65816.

The 65816 has a more flexible design. First, the zero page has been made movable. There is a register inside the CPU that stores a 16-bit offset to add to any 8-bit address encountered in the code, resulting in the potential for fast, compact access to any 256-byte window within the first 64 KB of address space. This is called "direct page", and ca65 doesn't know about it. You can change it yourself in code using the tcd or pld instructions, but make sure you don't get confused about where it is at any given moment, particularly in subroutines and interrupts.

Second, there are two 8-bit registers inside the CPU called the program bank and data bank registers. The data bank register is used with 16-bit addressing: when a 16-bit address is encountered, a 24-bit address is constructed using the data bank register as the top byte. This results in the address space being effectively divided into 64 KB chunks called banks. The program bank register is the top byte of the program counter and is also used with 16-bit addressing; 16-bit jumps must happen within the current program bank, and execution and branches wrap at bank boundaries. (Note that the data bank register does not operate on 8-bit direct page instructions, only on 16-bit or "absolute" instructions; direct page is limited to bank $00.)

An explicit 24-bit address is considered "long" and is only used if you really need to access something outside the current bank, either to read or write outside the current data bank or to use code blocks and subroutines outside the current program bank.

...

So that's why banks and zero page (or on the SNES, direct page) are a thing. It's to avoid the need to use full-length addresses, which would bloat and slow down code.

...

NB: ROM "banks" are not the same thing as SNES "banks". ROM can be in 32 KB or 64 KB "banks" (LoROM and HiROM, in the traditional nomenclature), which are then mapped into the SNES memory map, and typically mirrored so they appear in more than one place. You will only ever get one ROM bank to one SNES bank, but some SNES banks have other stuff in them besides ROM, which gets in the way a little and makes the remapping necessary. The first and third quarters of the SNES address space, banks $00-$3F and $80-$BF, have a system area in the bottom 32 KB of each bank, so only the top 32 KB of the associated ROM bank (which in the case of a 32 KB ROM bank is the whole thing) is visible in those areas. The second and fourth quarters of the address space have no system area and can be mapped as 100% ROM, except for banks $7E and $7F which are hardwired as the console's WRAM.

This is why the location of the vector table in ROM is different in LoROM and HiROM - a 32 KB LoROM bank appears in its entirety in the top half of a 64 KB SNES bank, so any address in the ROM is higher by $8000 when it appears on the SNES main bus. The vector table is always in the same place from the perspective of the S-CPU.

For HiROM the ROM banks are 64 KB, so the 16-bit addresses are the same between the ROM and the SNES, but the bottom half of the ROM banks are invisible in banks $00-$3F and $80-$BF on the SNES; full access is only possible in banks $40-$7D and $C0-$FF. Due to mirroring, this does not mean that the bottom half of bank $00 in ROM is inaccessible; you simply have to go to (usually) bank $40 or bank $C0 to get it.

There have been some good diagrams produced recently that somewhat illustrate this. They're two-dimensional, with the 16-bit address on the vertical axis and the bank byte on the horizontal axis: viewtopic.php?p=235113#p235113 Do not mistake the "LoROM" and "HiROM" maps for absolute definitive authorities; there are a lot of different ways of mapping cartridges to the bus (many of which result in ROM appearing in banks $40-$7D) and these are just generic instances that illustrate the difference between how 32 KB and 64 KB ROM banking are typically handled. The only hard-and-fast rule is that any address that connects to something inside the SNES (RAM, controller ports, PPU registers, etc.) cannot also connect to something in the cartridge.
Re: Banks and zero page using wla-dx
by on (#238517)
I think you're going about this backwards. The SPC format is meant for playback. GSS has proper exporting for games, in its own binary format.

The supplied code for GSS is in the wla-dx (and tcc-816) format, so you have to convert it to ca65 or your code to wla-dx still, but the GSS code is much smaller than the SPC player code. I do have GSS ca65 code, but that's (C) MCS, used in our past and future SNES titles.
Re: Banks and zero page using wla-dx
by on (#238521)
93143 wrote:
However, it only has an 8-bit data bus, which means that when an instruction includes an address, it has to be in 8-bit chunks

...

The 65816 has a more flexible design. First, the zero page has been made movable. There is a register inside the CPU that stores a 16-bit offset to add to any 8-bit address encountered in the code, resulting in the potential for fast, compact access to any 256-byte window within the first 64 KB of address space. This is called "direct page", and ca65 doesn't know about it.


So, if I'm getting this correct, using an 8-bit address directly is super fast since the bus is 8 bits wide. Now since you're confined to [$00, $FF], something like the following is impossible?

Code:
.zeropage
    .byte001:   .byte   $00
    .byte002:   .byte   $00
    ...
    .byte256:   .byte   $00
    .byte257:   .byte   $00        ; Overflow of the zero page!
    .byte258:   .byte   $00
    .byte259:   .byte   $00


Furthermore, with the 65816, the bank register is used in tandem with any 8-bit addresses so that instead of being confined to [$00, $FF], you can access [X+$00, X+$FF] for any 16-bit X. However, you're saying that this movable zero page is not possible with ca65. Does the .zeropage directive then just set this offset to 0, i.e. confining you to [$00, $FF]?

...

93143 wrote:
You will only ever get one ROM bank to one SNES bank, but some SNES banks have other stuff in them besides ROM, which gets in the way a little and makes the remapping necessary.


This part confuses me a bit. Are you saying that a single bank in the SNES could have a bank from the ROM and something else? A ROM bank can either be 32KB or 64KB, but a SNES bank is always 64KB right?

And then there is this image:

Image

And the wikibook article says this:

Code:
$00: $000000 - $007FFF
$01: $008000 - $00FFFF
$02: $010000 - $017FFF
...
$3D: $1E8000 - $1EFFFF
$3E: $1F0000 - $1F7FFF
$3F: $1F8000 - $1FFFFF


Does this mean that:

Code:
ROM addresses      | is in |   SNES Bank  |   SNES Addresses   
-------------------+-------+--------------+---------------------   
$000000 - $007FFF  |       |   $00        |   $8000-$FFFF
$008000 - $00FFFF  |       |   $01        |   $8000-$FFFF
$010000 - $017FFF  |       |   $02        |   $8000-$FFFF
...                |       |   ...        |   ...
$3D8000 - $3DFFFF  |       |   $7B        |   $8000-$FFFF
$3E0000 - $3E7FFF  |       |   $7C        |   $8000-$FFFF
$3E8000 - $3EFFFF  |       |   $7D        |   $8000-$FFFF


It looks like by shifting the SNES bank left by 15 gets you the high 12 bits of the ROM address. Does that mean anything...?

If so, this implies that in LoROM mode, you can access up to $3F0000 locations right? The wikibook article said cartridges ROM can have up to 4KB = $400000 memory locations. So where are the missing $10000 locations?

Also where do ROM banks fit in all of this?

...

93143 wrote:
a 32 KB LoROM bank appears in its entirety in the top half of a 64 KB SNES bank, so any address in the ROM is higher by $8000 when it appears on the SNES main bus.


So based on that table I made earlier, I think this is the process of turning a SNES address into a ROM address:
    1) Take the SNES address and left shift by 15.
    2) Take the SNES address and subtract $8000. Then add it to part the result from part (1).
    3) Go to the address given in part (2) in ROM.

Is that what you were saying essentially? Or am I misunderstanding how all the addresses interact?

...

This kinda brings me to another question actually. Where does the code and ROM data live? I know it starts out on the cartridge, but does it stay there?

Specifically, when the processor enters the fetch state, where does it fetch from? Does the PC contain an address that gets translated into accessing the ROM as I detailed above?

When you refer to an address in the code itself, e.g. lda $2000, that's a SNES address right? So if I want to access $3D8000 in the ROM, I need to set the SNES bank register to contain $7B with a tcd or pld instruction, and then I need to access $8000 with something like lda $8000. Is that the correct process? Or is there a way to do all this much simpler? Again, how do ROM banks fit into this picture as well?

...

But I guess I still have some project-specific questions:
    1. How do I store stuff in the zero page with wla-dx?
    2. (New one!) Is there a difference between .byte and .res in ca65? If so, which one does .db from wla-dx correspond to? And what's the equivalent of the other one?
    3. How do I configure the SNES to have more banks and use a bigger ROM? I have a chip that supports up to 8Mbit, so I might as well set the limit to that. Here is what the Header.asm file looks like for my project:

    Code:

    .MEMORYMAP                              ; Begin describing the system architecture.
        SLOTSIZE $8000                      ; The slot is $8000 bytes in size. More details on slots later.
        DEFAULTSLOT 0                       ; There's only 1 slot in SNES, there are more in other consoles.
        SLOT 0 $8000                        ; Defines Slot 0's starting address.
    .ENDME                                  ; End MemoryMap definition

    .ROMBANKSIZE $8000                      ; Every ROM bank is 32 KBytes in size
    .ROMBANKS 4                             ; 1 Mbit(s) - Tell WLA we want to use 4 ROM Banks

    .SNESHEADER
        ID "SNES"                           ; 1-4 letter string, just leave it as "SNES"

        NAME "SPC PLAYER           "        ; Program Title - can't be over 21 bytes,
        ;    "123456789012345678901"        ; use spaces for unused bytes of the name.

        SLOWROM
        LOROM

        CARTRIDGETYPE $00                   ; $00 = ROM only, see WLA documentation for others
        ROMSIZE $08                         ; $08 = 2 Mbits,  see WLA doc for more..
        SRAMSIZE $00                        ; No SRAM         see WLA doc for more..
        COUNTRY $01                         ; $01 = U.S.  $00 = Japan, that's all I know
        LICENSEECODE $00                    ; Just use $00
        VERSION $00                         ; $00 = 1.00, $01 = 1.01, etc.
    .ENDSNES

    .SNESNATIVEVECTOR                       ; Define Native Mode interrupt vector table
        COP EmptyHandler
        BRK EmptyHandler
        ABORT EmptyHandler
        NMI VBlank
        IRQ EmptyHandler
    .ENDNATIVEVECTOR

    .SNESEMUVECTOR                          ; Define Emulation Mode interrupt vector table
        COP EmptyHandler
        ABORT EmptyHandler
        NMI EmptyHandler
        RESET Start                         ; where execution starts
        IRQBRK EmptyHandler
    .ENDEMUVECTOR

    .BANK 0 SLOT 0                          ; Defines the ROM bank and the slot it is inserted in memory.
    .ORG 0                                  ; .ORG 0 is really $8000, because the slot starts at $8000
    .SECTION "EmptyVectors" SEMIFREE

    EmptyHandler:
           rti

    .ENDS

    .EMPTYFILL $00                          ; fill unused areas with $00, opcode for BRK.
                                            ; BRK will crash the snes if executed.


    I kept .ROMBANKSIZE as is, set ROMSIZE $0A to correspond to 8 Mbit, did .ROMBANKS 8 so that I have 8 banks that're 1 Mbit each. Unfortunately, when I try to run the assembler, I get

    Code:
    MEM_INSERT: Origin ($14080) overflows from bank (4).
       ^ main.asm:187: Writing .INCBIN data


    Which makes no sense because the binaries in bank 4 total up to a single kbit... I figure something ain't right with my header.






-----

Note: I'm completely ignoring HiROM until I can actually grasp LoROM :lol:
Re: Banks and zero page using wla-dx
by on (#238524)
First off, I think calima's post is important and should not get lost in this mess. SNESGSS is intended to produce game audio. An SPC is not game audio; it is a dump of game audio for playback on PCs.

Now then:

rchoudhary wrote:
So, if I'm getting this correct, using an 8-bit address directly is super fast since the bus is 8 bits wide.

There's a caveat. If the direct page offset is not a multiple of 256 (that is, the bottom byte is nonzero), the CPU has to spend a cycle adding it to the 8-bit address and you don't get any speed benefit over 16-bit addressing. The code is still smaller, and there are use cases where the direct page register can be used almost as a third index register, so there may still be benefits to using non-page-aligned DP. Also, in some cases indexing can add a cycle to 8-bit but not 16-bit addressing, with much the same result. Exact instruction timing is probably out of scope for the moment, so let's leave that topic...

Quote:
Now since you're confined to [$00, $FF], something like the following is impossible?

Code:
.zeropage
    .byte001:   .byte   $00
    .byte002:   .byte   $00
    ...
    .byte256:   .byte   $00
    .byte257:   .byte   $00        ; Overflow of the zero page!
    .byte258:   .byte   $00
    .byte259:   .byte   $00

Yes. Zero page is 256 bytes long. If you want to access $0100 on a 6502, you need to use 16-bit addressing. On a SNES, you could either use 16-bit addressing or change DP so that $0100 is within scope. (Or use 24-bit addressing, because direct page is always in bank zero and if your data bank is nonzero you could end up with 8-bit and 16-bit addressing pointed at different banks. Fortunately the SNES has the exact same identical RAM mirror and system registers in fully half of all the banks in the memory space, which mitigates this issue somewhat.)

Quote:
Furthermore, with the 65816, the bank register is used in tandem with any 8-bit addresses so that instead of being confined to [$00, $FF], you can access [X+$00, X+$FF] for any 16-bit X.

It's not a "bank" register. The term "bank" in the context of S-CPU addressing is reserved for the top byte of a 24-bit address. The program and data bank registers are 8-bit registers that are used to 'complete' 16-bit addresses. The direct page register is a 16-bit register that does what you describe. And it's not X, it's D (or DP). X is one of the index registers.

So, I guess that's a yes.

Quote:
However, you're saying that this movable zero page is not possible with ca65.

No, you can still do it, because ca65 allows you to use the instructions that move direct page around at runtime. But the assembler was designed for the 6502 and its automatic organization functionality doesn't recognize the possibility of doing this. I haven't used ca65, so I'm not totally sure how far you'd have to go to work around it, but I'm pretty sure you can.

...

Quote:
93143 wrote:
You will only ever get one ROM bank to one SNES bank, but some SNES banks have other stuff in them besides ROM, which gets in the way a little and makes the remapping necessary.

This part confuses me a bit. Are you saying that a single bank in the SNES could have a bank from the ROM and something else? A ROM bank can either be 32KB or 64KB, but a SNES bank is always 64KB right?

Right. Take a look at the image you posted. In the first and third quarters of the map, there's a bunch of stuff between $0000 and $7FFF in each bank, and it's not ROM. Then you have ROM from $8000 to $FFFF.

This means you can write code that accesses ROM, RAM, the PPU bus and the CPU's internal MMIO registers, all without using 24-bit addressing for anything.

Quote:
Does this mean that:

Code:
ROM addresses      | is in |   SNES Bank  |   SNES Addresses   
-------------------+-------+--------------+---------------------   
$000000 - $007FFF  |       |   $00        |   $8000-$FFFF
$008000 - $00FFFF  |       |   $01        |   $8000-$FFFF
$010000 - $017FFF  |       |   $02        |   $8000-$FFFF
...                |       |   ...        |   ...
$3D8000 - $3DFFFF  |       |   $7B        |   $8000-$FFFF
$3E0000 - $3E7FFF  |       |   $7C        |   $8000-$FFFF
$3E8000 - $3EFFFF  |       |   $7D        |   $8000-$FFFF

Yes.

Quote:
It looks like by shifting the SNES bank left by 15 gets you the high 12 bits of the ROM address. Does that mean anything...?

Not really, other than the fact that each ROM bank is a 15-bit address space, and they're contiguous in the actual ROM chip. My understanding is that the cartridge just connects pins 16+ of the A bus address to pins 15+ of the ROM address, and pin 15 of the A bus address either does nothing or helps trigger SRAM select in a small logic chip, but not every cartridge was wired the same and there may be differences in how they handled things.

Also, the bank byte is 8 bits, so I'm not sure how you got 12 just by shifting it...

Quote:
If so, this implies that in LoROM mode, you can access up to $3F0000 locations right?

How do you figure? The bank byte being bits 15-22 of the address would give you 8 MB of addressable space. Banks $7E and $7F are taken up by WRAM, so you can't actually access a full 8 MB in pure LoROM mode, but if you do the usual trick of mirroring banks $00-$7F of the ROM in banks $80-$FF on the S-CPU bus (ie: the cartridge ignores pin 23 of the A bus address for ROM accesses), you can do 4 MB pretty easily because the ROM that's blocked by WRAM in $7E and $7F appears unobstructed in $FE and $FF.

Quote:
Also where do ROM banks fit in all of this?

Well, there's not necessarily such a thing, except in so far as certain areas of ROM are mapped to specific SNES banks. So the first 32 KB of a ROM could be considered the first "bank" if you're using a LoROM-type map, and the assembler would need to know this in order to put things where they should go and translate labels and such. But technically the ROM is contiguous. Or for big games it could be multiple chips - a 4 MB game might have been four 1 MB ROMs. The ROM bank structure of 32 or 64 KB chunks is purely conceptual, necessary only to allow the assembler to put together a working SNES program.

...

Quote:
93143 wrote:
a 32 KB LoROM bank appears in its entirety in the top half of a 64 KB SNES bank, so any address in the ROM is higher by $8000 when it appears on the SNES main bus.

So based on that table I made earlier, I think this is the process of turning a ROM address into a SNES address:
    1. Take the ROM address and right shift by 15. This is the SNES bank number.
    2. Take the low 15 bits of the ROM address. Then add $8000 to it. This is the SNES address.
    3. SNES bank + SNES address = absolute SNES address?

    1. Yes, but no. You can generally add $80 to the SNES bank number thus obtained (and/or possibly $40 or $C0 or whatever, depending on the cartridge) and still have the correct address, because of mirroring. It may even be better to do so, because if you access ROM in bank $80 or above, you can use FastROM mode...
    2. Yes.
    3. Technically the term "absolute" means 16-bit addressing within a bank. (This is probably a holdover from the 6502, which had a 16-bit address space and no banks.) Full 24-bit addressing is called "long" addressing. Other than that, yes, assuming that by "+" you don't literally mean addition. (SNES bank << 16) + absolute SNES address = long SNES address.

Quote:
Where does the code and ROM data live? I know it starts out on the cartridge, but does it stay there?

Yes, generally. Obviously you have to feed the PPU graphics data, and if you have compressed data you may want to decompress it into WRAM as needed (particularly since it is very advantageous to have graphics data in an uncompressed block that can be rapidly transferred via DMA during VBlank). And obviously any data generated by the program has to go in RAM. But code usually stays in ROM and is executed in place. Exceptions would include certain types of special chip games, where the CPU outright can't access the ROM if the coprocessor is using it, and self-modifying code, which doesn't work real well in read-only memory. In cases like those you'd have to explicitly transfer the code to RAM and jump to it, but since you aren't in a case like those you don't have to worry about it.

The audio module is different. It has no cartridge access, only a 64-byte boot ROM that defines a simple transfer protocol for the APU I/O ports. You have to follow this protocol to load a program and data (or, at minimum, your own data transfer code...) in order to play music and sound effects.

Quote:
Specifically, when the processor enters the fetch state, where does it fetch from? Does the PC contain an address that gets translated into accessing the ROM as I detailed above?

The S-CPU does not really know the difference between ROM and any other part of its memory space. The address translation circuitry for the ROM is all in the cartridge. You can actually write to ROM just as easily as you can write to RAM, but of course the write won't do anything.

Quote:
When you refer to an address in the code itself, e.g. lda $2000, that's a SNES address right? So if I want to access $3D8000 in the ROM, I need to set the SNES bank register to contain $7B with a tcd or pld instruction, and then I need to access $8000 with something like lda $8000.

No no. tcd and pld are for the direct page register, not either of the bank registers. You have to set the data bank with plb.

...

Quote:
    1. How do I store stuff in the zero page with wla-dx?
    2. (New one!) Is there a difference between .byte and .res in ca65? If so, which one does .db from wla-dx correspond to? And what's the equivalent of the other one?
    3. How do I configure the SNES to have more banks and use a bigger ROM? I have a chip that supports up to 8Mbit, so I might as well set the limit to that.

    1. WLA-DX doesn't care about any of that. You can define variables as 8-bit numbers, and if you use them as addresses it should default to using direct page. It might not; in that case it's best to use .b after the opcode, e.g. lda.b DPVAR, just to make sure it doesn't pad your address to 16 bits...
    2. .db means define byte. It just plops a byte down into the ROM at the specified location (ie: if you use .db in the middle of a code section, you have to jump past it. Don't do that; it's silly.) You can .dw, meaning define 16-bit word, but since you can .db multiple comma-separated bytes you don't absolutely have to .dw; just remember that the SNES is little-endian. I haven't used ca65 so I don't know what its keywords mean.
    3. Don't set the limit any higher than you need to. It will assemble a ROM that big and you will have to wait any time it needs to be written or loaded. That said, it should be as simple as changing .ROMBANKS to something higher than 4.

Quote:
I kept .ROMBANKSIZE as is, set ROMSIZE $0A to correspond to 8 Mbit, did .ROMBANKS 8 so that I have 8 banks that're 1 Mbit each.

That's not how that works. Specifying .ROMBANKS 8 combined with .ROMBANKSIZE $8000 does not give you 8 banks of size $20000.

If you're using LoROM, ROM banks are always 32 KB ($8000 bytes). This is because the assembler has to make sure the ROM constitutes a working SNES program when mapped into the SNES address space, and if your cartridge is ignoring bit 15 of the address, using 32 KB banks is the only way to do that. Do not change .ROMBANKSIZE.

ROMSIZE doesn't do anything. It's part of the header, which is for bookkeeping purposes only (and to tell some emulators and flashcarts what they're supposed to pretend to be, although I'm sure Nintendo didn't plan for that when they designed it). The SNES doesn't read the header, and I'm pretty sure the assembler doesn't care what it says either, other than to include it in the ROM.

As for why what you did didn't work...
Code:
.org $4000
.incbin     SPC_FILE skip $10100 read $0080

That almost adds up to the offset in the error message...

Are you sure everything is shipshape in your data includes? Correct filesizes and all?

WLA DX can be weird sometimes. This may not be your fault.

I have no idea, and I've kinda run out of time. Must go to bed...

Quote:
Note: I'm completely ignoring HiROM until I can actually grasp LoROM :lol:

Probably wise. I found memory mapping to be the hardest thing to understand about the SNES.
Re: Banks and zero page using wla-dx
by on (#238542)
93143 wrote:
Quote:
Does this mean that:

Code:
ROM addresses      | is in |   SNES Bank  |   SNES Addresses   
-------------------+-------+--------------+---------------------   
$000000 - $007FFF  |       |   $00        |   $8000-$FFFF
$008000 - $00FFFF  |       |   $01        |   $8000-$FFFF
$010000 - $017FFF  |       |   $02        |   $8000-$FFFF
...                |       |   ...        |   ...
$3D8000 - $3DFFFF  |       |   $7B        |   $8000-$FFFF
$3E0000 - $3E7FFF  |       |   $7C        |   $8000-$FFFF
$3E8000 - $3EFFFF  |       |   $7D        |   $8000-$FFFF

Yes.

How can you say yes to that?! It's a big, strong, definitive no! The terms used in the above ASCII chart are "SNES Bank" and "SNES Address", i.e. 65816 bank and 65816 address.

If those column labels had read "WLA DX Bank" and "WLA DX Address" then "Yes" would be correct.

65816 bank $00 = $000000-00FFFF
65816 bank $01 = $010000-01FFFF
65816 bank $02 = $020000-02FFFF
etc...

The fact banks on the 65816 are 64KBytes each doesn't change. Ever. It doesn't matter if you're in mode 20 (LoROM) or mode 21 (HiROM).

The problem here, as I've discussed time and time again with assemblers (esp. WLA DX), is that the assembler is trying to be "smart" by comprehending mode 20 (LoROM)'s "only half the bank is available for ROM" model, while simultaneously using terms that directly conflict with well-established 65816 terms (read: bank). This confuses the living hell out of programmers, especially new ones. This thread -- and several others -- are hard proof.

Mode 21 (HiROM) may be different because it's more "64KB-friendly", but comes with its own trade-offs as well.

This is why I often say, relying on "more complex assembler features" is often a travesty because unless you're intimately familiar with exactly what the assembler is doing, you really can't be sure what is going to go where or what's going to happen. Once again I become a broken record: this is how/why generating listings files is important. More often than not, you can find the error by examining the code there. "Hey! I told you to load the upper 8 bits of the 24-bit address of this label (e.g. the bank of the label) into the 8-bit accumulator, and that should be a value of 6, but it assembled to 0! What gives?" This approach works well with both featured assemblers and bare-bones ones.

Everyone here knows my general opinion, but I guess it needs repeating: the ancient "just use .org and segment your stuff yourself" model is a lot more logical, because it effectively allows for a more coupled relationship between the native 65816 and the SNES memory model of choice. ca65/ld65 sits somewhere in between that, but getting your ld65 linker config wrong (and thus things like .bankbyte() or ^ return the wrong value) is still very much a reality. (In fact, last night I wasted over an hour dealing with an actual code bug of a single-overwritten-byte-of-data that in absolutely no way/shape/form could ca65/ld65 ever have caught or warned me about, no matter how many "features" it offered.)

Now, as for this subject:

93143 wrote:
Quote:
However, you're saying that this movable zero page is not possible with ca65.

No, you can still do it, because ca65 allows you to use the instructions that move direct page around at runtime. But the assembler was designed for the 6502 and its automatic organization functionality doesn't recognize the possibility of doing this. I haven't used ca65, so I'm not totally sure how far you'd have to go to work around it, but I'm pretty sure you can.

The "workarounds" are as follows:

1. Do not relocate DP at all -- keep it at $0000, i.e. lda #$0000 / tcd once during RESET -- and make use of type = zp in your ld65 configuration like normal.

2. Relocate DP as you wish, but it's entirely up to you to refer to variables located within the now-relocated DP through some other mechanism -- I strongly recommend use of equates because it becomes very obvious what's different. In other words: "normal" variables located within "standard" direct page (let's assume $0000) might be declared with varA: .res 2 / varB: .res 2 inside of .segment "ZEROPAGE" or the like, while for variables located within a new DP location (let's assume $1000) might be declared with altDP_varC = $00 / altDP_varD = $02 to differentiate them from the others (and probably preceded by a large comment).

Because the assembler/linker has no idea when you've done tcd at run-time, it's up to you to micro-manage this. (This is not that unreasonable, BTW -- even powerful assemblers during the heyday of the 65816 like ORCA/M advocated this as well. You are free to do whatever you want at run-time, but it's up to you to make sure it's correct. Do not expect hand-holding!). And be sure to put D back when you're done.

3. I think there is a third-party patch introducing some new pseudo-op directives to allow for tracking of it by the assembler, but I can't find it right now. It was something (IIRC) rainwarrior found on the ca65 mailing list, I think. There's a post here about it but I can't find it. And no I'm not talking about the GitHub issues he made recently for it.

I tend to recommend #1. You can safely use #1 and #2 together, but in #2 like I said, it's up to you to put things back when you're done / pay very close attention to what variables you're accessing and what addressing modes you're using in all of your instructions. You have to do this anyway regardless, but in general, my view is that ca65/ld65 is still really not "fully" 65816-friendly. It does a pretty good job so far, but there's a lot of "screwing around" that I think results in wasted time.

I think for a 6502/65c02 assembler/linker suite ca65/ld65 is pretty cool (read: fancy), but the more I've used it for 65816 (in the past month or so), including running a patched version of ca65/ld65 for dealing with segment declaration order (rainwarrior wrote this patch and so far it's holding up), the more annoyed I've gotten. I'm very biased right now though -- but quite honestly if I could add up all the man hours I've wasted dealing with ca65/ld65-isms in just 1-2 years, it'd be in the mid-2-digit range. I have better things to do than "babysit" my assembler.
Re: Banks and zero page using wla-dx
by on (#238562)
koitsu wrote:
93143 wrote:
Quote:
Does this mean that:

Code:
ROM addresses      | is in |   SNES Bank  |   SNES Addresses   
-------------------+-------+--------------+---------------------   
$000000 - $007FFF  |       |   $00        |   $8000-$FFFF
$008000 - $00FFFF  |       |   $01        |   $8000-$FFFF
$010000 - $017FFF  |       |   $02        |   $8000-$FFFF
...                |       |   ...        |   ...
$3D8000 - $3DFFFF  |       |   $7B        |   $8000-$FFFF
$3E0000 - $3E7FFF  |       |   $7C        |   $8000-$FFFF
$3E8000 - $3EFFFF  |       |   $7D        |   $8000-$FFFF

Yes.

How can you say yes to that?! It's a big, strong, definitive no! The terms used in the above ASCII chart are "SNES Bank" and "SNES Address", i.e. 65816 bank and 65816 address.

But the entire set of column labels indicates that this chart is specifically about where the ROM goes. The key words are "is in". It doesn't imply that SNES addresses $0000-$7FFF don't exist in the given bank, just that in a canonical LoROM configuration no ROM is mapped to them. As far as I can tell, the ROM mapping expressed in the chart is correct.

...

As for ca65... Do you know if the assembler you've mentioned as under development will/could support Super FX code?
Re: Banks and zero page using wla-dx
by on (#238567)
93143 wrote:
But the entire set of column labels indicates that this chart is specifically about where the ROM goes. The key words are "is in". It doesn't imply that SNES addresses $0000-$7FFF don't exist in the given bank, just that in a canonical LoROM configuration no ROM is mapped to them. As far as I can tell, the ROM mapping expressed in the chart is correct.

That chart is terrible (the number of curse words I want to use here is astounding, BTW). Whoever wrote that has no idea how to write technical documentation. You don't take an English sentence, extract select grammatical pieces from it, and turn them into column headers working from left to right. Good lord. Why do people keep making these absolutely horrible things that confuse people? This has been going on for ALMOST THIRTY YEARS! Every person who complains about this stuff is justified. SiMKiN's old memory layout docs were clearer than this, sheesh.

For the person confused: mode 20 and the whole "what's with the upper 32KBytes?!" thing is really simple to understand (and even easier to program for):

  • The first 32KBytes of your ROM file end up in 65816 bank $00, range $8000-FFFF
  • The next 32KBytes of your ROM file end up in 65816 bank $01, range $8000-FFFF
  • Rinse lather repeat until bank $7D
  • Thus, maximum ROM space in banks $00-7D == 126 * 32768 = 4,128,768 bytes (just short of 32mbit -- this is due to system RAM being in banks $7E-7F)

The exact rinse-lather-repeat model applies to banks $80-FF (e.g. bank $80 = bank $00, bank $81 = bank $01, etc.), which can be used for high speed mode (3.68MHz), a.k.a. "fastrom"
Thus, maximum ROM space in banks $80-FF == 128 * 32768 = 4,194,304 bytes (a full 32mbit)

Make sense?

What your assembler does with "a bank" is entirely up to the assembler and requires extensive review of its documentation. Overloading the term "bank" (within the assembler itself) lends itself to mass confusion, resulting in threads/topics like this. The confusion is totally justified, don't get me wrong, it's just sad that it keeps happening. :(

The term "bank" in the 65816 CPU always means 64KBytes (i.e. 0000-FFFF). It just so happens that mode 20/LoROM SNES cartridges are wired so that each "32KB portion" ends up in the upper half of each 65816 bank. The reason Nintendo did this was certainly to help with ease-of-access to MMIO registers ($21xx, $42xx, $43xx) and having direct page + RAM access everywhere, regardless of what bank your code is actively running in. Considering that their previous console was the NES, which is limited to 64KBytes of total addressing space, where the upper 32KB ($8000-FFFF) was mapped cartridge ROM, I think it makes sense.

Mode 21/HiROM does away with some of this and goes for "true" linear 64KByte banks, with the exception of bank $00. The complication here is what I just described: accessing MMIO registers and RAM requires either use of long addressing (24-bit addressing, ex. lda #$8f / sta $002100 to turn of the screen) or tricks like relocating direct page to the MMIO register base (ex. rep #$20 / lda #$2100 / tcd / sep #$20 / lda #$8f / sta $00 to turn off the screen). Both have their pros and cons. What mode 21 gets you is more ROM space, and is commonly used for games that are 32mbit+ in size.

93143 wrote:
As for ca65... Do you know if the assembler you've mentioned as under development will/could support Super FX code?

Extremely off-topic at this point, but the answer is: in its current form, no, but it certainly could be extended to support it since it's a multi-arch assembler. This is probably not going to happen before release, but as said, could be extended later on without too much effort.
Re: Banks and zero page using wla-dx
by on (#238569)
Would it be more accurate to say that a mode $20 ROM has up to 128 "half banks" starting at $80, and a mode $21 ROM has up to 64 "banks" starting at $C0?
Re: Banks and zero page using wla-dx
by on (#238573)
tepples wrote:
Would it be more accurate to say that a mode $20 ROM has up to 128 "half banks" starting at $80, and a mode $21 ROM has up to 64 "banks" starting at $C0?

Sure, that sounds good, but I must stress the importance of mentioning banks $00-7D. The 65816 doesn't power on in bank $80, it powers on in bank $00, it reads vectors from bank $00, vectors addresses are 16-bit (not 24-bit) thus RESET, NMI, IRQ etc. execute out of bank $00 by default, the stack and direct page are "truly" in bank $00, etc...

The first time someone reads the description of mode 20, and they get it, it they go "Ahhh! I get it!!!" followed by a few minutes of pause, and then "But wait a minute... 32mbit would take up 128 banks... 00-7D means only 126 banks... if they're half size, e.g. 32KBytes, what happens to the last 64KBytes of a 32mbit game using mode 20?!?" (I speak from having personally experienced this one, and from seeing many people go through the same thing.)

Mode 20 makes things "so simple!!" that people start forgetting stuff like how sta $1111 isn't necessarily going to write to RAM in any other mode, or on another system using 65816; I'll refer you to the zillion-page Espozo thread where he asked about converting something from mode 20 to mode 21 as an example. Seeing lda #$xx / pha / plb is common on 65816.
Re: Banks and zero page using wla-dx
by on (#238575)
koitsu wrote:
Whoever wrote that has no idea how to write technical documentation.

Oof that'd be me chief... :? You aren't wrong though lol

After reading everything, I think I get how the ROM bank maps into a SNES bank: Every successive ROM bank gets mapped into the upper half of a successive SNES bank (excluding banks $7E and $7F which are WRAM).

So the ROM is mapped to two ranges: banks $00-$7D and banks $80-$FF.

Why would you do mirroring like that? Especially if in the first case your ROM is cut off?

Also, does the programmer have to manually initialize both the mapping and the mirroring? Or are they built in?

Finally, how exactly do you access a space in ROM then? Like say I have a variable in ROM address $1234 that I want to load into the A register. Do I simply do lda $1234?

Now say I want to store the value in the A register in WRAM at address $BEEF. How do I access that? Do I need to calculate the CPU address (the address that gets mapped into a WRAM address) manually?
Re: Banks and zero page using wla-dx
by on (#238577)
I consider banks $80-$FF to be the actual home of ROM in mode $20. Bank $80 is mirrored down to $00 for interrupt handling purposes. And the electrically simplest way to mirror $80 to $00 brings banks $81-$FD along with it to $01-$7D, as the CPU conveniently doesn't generate the ROM enable signal for WRAM banks $7E and $7F.
Re: Banks and zero page using wla-dx
by on (#238580)
rchoudhary wrote:
After reading everything, I think I get how the ROM bank maps into a SNES bank: Every successive ROM bank gets mapped into the upper half of a successive SNES bank (excluding banks $7E and $7F which are WRAM).

So the ROM is mapped to two ranges: banks $00-$7D and banks $80-$FF.

You got it. :-)

rchoudhary wrote:
Why would you do mirroring like that? Especially if in the first case your ROM is cut off?

I've talked a bit about this in the past in another thread (re: the "history" of the SNES), but I'll try my best to paraphrase what I know. I've seemingly become a kind of historian around here due to my age and involvement with all of this stuff (earlier than most):

Nintendo didn't originally have PCBs (cartridges) that supported addressing up to 32mbits of ROM in mode 20: the original ones only supported up to 16mbit. We know this not only because of cartridge analysis, but also because official documentation for a long time only depicted banks $00-3F and $80-BF being mapped to ROM. (Mode 21/HiROM has always supported up to 32mbit)

As games and desires grew, they made newer versions of PCBs with more addressing lines thus supported mapping more ROM space. We know this for the exact same reasons as above: cartridge analysis, and newer official documentation depicting banks $00-7D and $80-FF being mapped to ROM. I suspect this happened around the same time they rolled out use of their MAD-1 address decoder chip (which is used for both mode 20 and 21). I suspect folks like byuu can talk more about that stuff though. But I can assure you that if you were to get an old first-gen mode 20 SNES game cartridge (ex. Super Mario World) and compare it to a last-gen cartridge, you'd see exactly what I'm describing.

The other part of your question is essentially "so why would they just 'chop off' those 2 banks due to $7E/7F and force you to use $FE/FF?"

When it comes to addressing lines (read: each individual bit that makes up an address, no matter if it's 4, 8, 17, 24, 32, etc. bits), if you don't "map the address line", then for whatever reason (hardware guys like lidnariq, Memblers, and probably byuu can talk about this -- I might have it backwards?), the address range essentially ends up being "masked" and an effect of that is mirroring.

So what's REALLY is happening, I strongly suspect, is this: banks $00-7F do in fact get mapped to ROM (!!!) via the cartridge addressing lines, for a full 32mbit... however, the SNES console itself has addressing lines that map RAM (WRAM) in banks $7E and $7F, and those "come after" the cartridge/ROM mapping, thus those take higher precedence. Think of it kind of in layers, if that makes sense: mappings overlayed atop mappings.

rchoudhary wrote:
Also, does the programmer have to manually initialize both the mapping and the mirroring? Or are they built in?

Excellent question. There is no programming involved; they're dictated by the cartridge/PCB itself. Emulators accomplish this through a series of heuristics, but the most common way is to refer to the "map mode" cartridge header byte ($FFD5 in bank $00). You can read about it elsewhere, like here or here. Just know that there's two versions (older and newer) of the header. The older/original went from $FFC0-FFDF, the newer goes from $FFB0-FFDF. Nintendo also expanded the header by 16 bytes sometime later on. Nintendo was very strict about the cartridge header format -- it's extremely well-documented, officially and unofficially. I prefer the older version because I don't need all the fancy nonsense. So if you were wondering how SNES emulators like SNES9x would show you the "name of the ROM", that's where the data comes from.

Don't confuse this header with, say, copier file format headers (like .smc files that have a 512-byte header)! What I'm talking about is in the actual ROM itself, and is not a "file format" header.

Edit: removed miserable attempt at cartridge header explanation.

rchoudhary wrote:
Finally, how exactly do you access a space in ROM then? Like say I have a variable in ROM address $1234 that I want to load into the A register. Do I simply do lda $1234?

Think about it for a second. Again, mode 20:

ROM (file offset) $00000-07FFF get mapped to bank $00 address $8000-FFFF (also bank $80 address $8000-FFFF)
ROM (file offset) $08000-0FFFF get mapped to bank $01 address $8000-FFFF (also bank $81 address $8000-FFFF)
ROM (file offset) $10000-17FFF get mapped to bank $02 address $8000-FFFF (also bank $82 address $8000-FFFF)
ROM (file offset) $18000-1FFFF get mapped to bank $03 address $8000-FFFF (also bank $83 address $8000-FFFF)
...etc...

You tell me: where would file offset $1234 refer to in 65816 addressing space? If you guessed bank $00 address $9234, then give yourself a cookie, i.e. lda $009234 when using long addressing (to keep things simple for now).

But if you were using mode 21, it becomes very different: [u]edit: previously said bank $80 etc., should've been $C0 etc.[/tt]:

ROM (file offset) $00000-0FFFF get mapped to bank $C0 address $0000-FFFF (also upper 32KB at bank $00 address $8000-FFFF)
ROM (file offset) $10000-1FFFF get mapped to bank $C1 address $0000-FFFF (also upper 32KB at bank $01 address $8000-FFFF)
ROM (file offset) $20000-2FFFF get mapped to bank $C2 address $0000-FFFF (also upper 32KB at bank $02 address $8000-FFFF)
ROM (file offset) $30000-3FFFF get mapped to bank $C3 address $0000-FFFF (also upper 32KB at bank $03 address $8000-FFFF)
...etc...

Here, offset $1234 would refer to bank $C0 address $1234, or $C01234, i.e. lda $C01234 when using long addressing. You would have no way of accessing the "lower 32KB half of a ROM bank" via banks $00-7D -- only banks $C0-FF could provide that.

Hopefully this also explains why Tepples said he prefers to think of mode 20 as "originating in bank $80/81/82/..., and mirrored to bank $00/01/02/...". I tend to look at it the other way around, but we both understand how it works.

Your program has to be written with knowledge of what mode (20 or 21) is being used. Your assembler lets you control this via a multitude of directives; .org is common (for code, and sometimes data), but assemblers like ca65/ld65 control it via ld65 configuration. In effect, your assembler memory configuration (all calculated addresses, etc.) have to "match" the memory mode on the SNES you're using, otherwise you'll refer to wrong/bogus data. This is a super common complication with ANY computer, FYI, so the SNES is not unique in this regard at all.

rchoudhary wrote:
Now say I want to store the value in the A register in WRAM at address $BEEF. How do I access that? Do I need to calculate the CPU address (the address that gets mapped into a WRAM address) manually?

WRAM on the SNES can be accessed in 2 ways: a) directly via native 65816 addressing or b) through MMIO registers $2180/1/2/3. Let's assume the former, but I'll cover the latter at the end:

WRAM addressing itself is 17-bit, ranging from $00000-1FFFF. WRAM is mapped into 65816 banks $7E and $7F linearly, which makes it easy to understand:

WRAM $00000-0FFFF is mapped to bank $7E address $0000-FFFF
WRAM $10000-1FFFF is mapped to bank $7F address $0000-FFFF

Effectively, the WRAM-to-65816-native-address calculation formula is literally offset+$7E0000.

Thus, sta $7EBEEF would get you what you want. But if you really did want to use the MMIO registers, then rep #$30 / ldx #$BEEF / stx $2181 / stz $2183 / sta $2180 would accomplish the same thing.

Just don't forget that the lower 8KBytes ($0000-1FFF) of WRAM (e.g. $7E0000-1FFF) is mapped to bank $00 address $0000-1FFF as well. This is where (usually) the direct page, stack, etc. reside (in RAM). Hopefully this also helps explain the "little blue box in bank $7E" in Tepples' diagram/map.
Re: Banks and zero page using wla-dx
by on (#238584)
koitsu wrote:
But if you were using mode 21, it becomes very different:

ROM (file offset) $00000-0FFFF get mapped to bank $80 address $0000-FFFF (also upper 32KB at bank $00 address $8000-FFFF)

Hold on, don't you mean bank $40 or $C0, not $80?

Banks $80-$BF mirror banks $00-$3F, with the system area and everything. You can only access the bottom half of a HiROM bank in $40-$7D and $C0-$FF.
Re: Banks and zero page using wla-dx
by on (#238585)
koitsu wrote:
So what's REALLY is happening, I strongly suspect, is this: banks $00-7F do in fact get mapped to ROM (!!!) via the cartridge addressing lines, for a full 32mbit... however, the SNES console itself has addressing lines that map RAM (WRAM) in banks $7E and $7F, and those "come after" the cartridge/ROM mapping, thus those take higher precedence. Think of it kind of in layers, if that makes sense: mappings overlayed atop mappings.


How does this "precedence" take place in hardware? And why does it only happen for banks $7E-$7F and not $FE-$FF?

koitsu wrote:
There is no programming involved; they're dictated by the cartridge/PCB itself.


So the PCB itself maps it's ROM into the SNES address space? How? By "dictated" do you mean that within the ROM is some code that tells the SNES how memory should be mapped, e.g. by setting itself to LoROM or HiROM mode?

What I thought happens is whenever the CPU sees lda $009234 it thinks "ah shit gotta go to the cartridge" and sends the address $1234 to the ROM chip on the PCB, which in turn sends a byte or two back. Is that not how that works?

I actually bought a PCB from a guy on Etsy:

Image

It looks like the cartridge pins are just connected to the IC. Is the PCB basically just a breakout board for the ROM chip?

koitsu wrote:
You tell me: where would file offset $1234 refer to in 65816 addressing space? If you guessed bank $00 address $9234, then give yourself a cookie, i.e. lda $009234 when using long addressing (to keep things simple for now).


So based on that, let's say my main.asm file had something like this:

Code:
.bank 0
...
...                       ; $FF bytes of other stuff
...

myvar:    .db    $21      ; At address $100

...

    lda myvar             ; Still in bank 0



Would the preprocessor turn lda myvar into lda $008100? Can the assembler handle a full 24 bit address though?

User 93143 pointed out that there is a Data Bank Register. Would the assembler use that instead? Like would it load the Data Bank Register with $00 and then just do lda $8100?

And then finally, in the wikibook article on memory mapping, there is this chart:

Image

The part that confuses me is the hardware registers bit. If I want to force blank, I simply write $80 to $2100 in the code. But in which bank? What's the difference between $002100, $002100, ..., $3F2100? Also, why can I just do a lda $2100 instead of having to do lda $xx2100 (where xx is whatever the right bank is? I'm assuming it's because of the Data Bank Register?
Re: Banks and zero page using wla-dx
by on (#238586)
93143 wrote:
koitsu wrote:
But if you were using mode 21, it becomes very different:

ROM (file offset) $00000-0FFFF get mapped to bank $80 address $0000-FFFF (also upper 32KB at bank $00 address $8000-FFFF)

Hold on, don't you mean bank $40 or $C0, not $80?

Banks $80-$BF mirror banks $00-$3F, with the system area and everything. You can only access the bottom half of a HiROM bank in $40-$7D and $C0-$FF.

You're right, I meant banks $C0-FF. I'll edit my previous post to correct the error. Thanks!

This is why these "gargantuan information threads asking 900 questions per post" are very hard to manage/organise; chance of mistakes greatly increases as scope increases.

As for $40-7D: this is only the case on some/specific PCB revisions and is not a universal norm for mode 21. These details (re: $40-7D) are omitted from old official documentation, and new official documentation only denotes this for specific boards -- some mode 21 boards have nothing in the lower half of those banks, but the upper half has parts of the upper half of ROM (e.g. acts like mode 20); see SVHC-8PV5B for example (very diverse/complicated board since it supports mode 20 or mode 21, as well as multiple mask ROM sizes). And OTHER boards, like SVHC-4QW5B (mode 21 only) only map banks $40-5F of all places. In other words: it's a clusterf***.

The mode 21/HiROM memory map Tepples did establishes Good Habits and is pretty universal. Teaching people to exclusively use $C0-FF is good. "Edge cases" where you have some crazy programmer doing crazy things (do you know of one? ;-) ) should not be catered to in commonplace learning documentation (reference material yes, learning no).
Re: Banks and zero page using wla-dx
by on (#238587)
rchoudhary wrote:
So the PCB itself maps its ROM into the SNES address space? How?
By the physical locations of the wires on the PCB, and specific parts on the PCB.

Quote:
By "dictated" do you mean that within the ROM is some code that tells the SNES how memory should be mapped, e.g. by setting itself to LoROM or HiROM mode?
No software. It's purely electrical. (Exceptions exist. Pretend they don't)

Quote:
What I thought happens is whenever the CPU sees lda $009234 it thinks "ah shit gotta go to the cartridge" and sends the address $1234 to the ROM chip on the PCB, which in turn sends a byte or two back. Is that not how that works?
It's about ... 70% right? The actual order of events is
SNES says "Hey, I'm talking about $009234" - this number is expressed in binary, and placed on the 24 address lines coming out.
SNES says "By the way, that's a place that is part of the 'normal ROM locations'" - we call this signal /ROMSEL but in practice that's a over-simplification.
SNES says "Oh, and I'm reading"

After that, the answer is "it depends". Specifically in the PCB you showed, the "normal ROM location" signal is just used by the ROM directly, and is used to tell the ROM to wake up at the right time. The ROM gets only some of the address lines from the SNES, looks up the right value inside itself, and provides that value to the SNES when the ROM is told that the SNES is reading.

Quote:
It looks like the cartridge pins are just connected to the IC. Is the PCB basically just a breakout board for the ROM chip?
For boards like this, with just one ROM and the CIC - yes, exactly that.
Re: Banks and zero page using wla-dx
by on (#238588)
lidnariq wrote:
lidnariq wrote:
By the physical locations of the wires on the PCB, and specific parts on the PCB.
...
No software. It's purely electrical.


How does setting the mode to $20 or $21 factor in then? Because for both of those modes it seems that the mapping changes. The same SNES address might refer to different locations in the cartridge ROM depending on the mapping scheme right? How does wiring by itself fix this?

UPDATE

I found an excerpt online that says this:

Quote:
The SNES has 24 address lines, that’s 3 bytes. that’s why we have
$00:0000 to $FF:FFFF bytes of addressing possible on SNES.
In traditional memory maps, the ROM in banks 00-7f is mirrored in $80-$ff. This is because the ROM is ignorant of SNES address line 23 (0-23). In a LoRom setup, SNES Address line 15 is not connected to the ROM, so even though the SNES accesses the cart with address $00:8000, the ROM is being accessed at $00:0000. Bit 15 is the one that makes $0000 turn to $8000. So when you disregard it, you go from $0000-7fff, then back to $0000,$7fff, then after that, bit 16 will be set, and the ROM is connected, so it knows to send other location data. Clearly it is shifted. SNES A15 NC ROM A15, therefore, SNES A16 = ROM A15. This means that ROM will be accessed without gaps, even though the SNES is gapping.


So it looks like by just ignoring bit 15 we can achieve the mapping to banks $00-$7F, and by ignoring bit 23 we can achieve the mirroring to banks $80-$FF. So I guess that's how the wiring itself gives you LoROM mapping.

But how does setting the mode to say $21 work? The memory layout is a bit different right? How can you achieve it? You can't "rewire" the cartridge after all (right?)...
Re: Banks and zero page using wla-dx
by on (#238589)
rchoudhary wrote:
How does setting the mode to $20 or $21 factor in then? Because for both of those modes it seems that the mapping changes.
Because Nintendo required that the right number be there when they manufactured the ROMs. And emulators have since used that information to choose how to handle the file on your disk. But there's nothing in the SNES itself that cares what's there.

Quote:
So it looks like by just ignoring bit 15 we can achieve the mapping to banks $00-$7F
... no, that's something else. Specifically for the PCB you showed, ignoring bit 15 means that the contents seen at $400000 are the same as the contents seen at $408000. A15 is that $8000.

However, the SNES itself puts something different at $000000 from $008000. (That's the "ordinary ROM area" signal I was talking about)

Quote:
But how does setting the mode to say $21 work? The memory layout is a bit different right? How can you achieve it? You can't "rewire" the cartridge after all (right?)...
Sure you can. Take a close look at the two places to place the ROM on the board. See how for many of the signals, the two adjacent holes are connected, but for several they're not?
Re: Banks and zero page using wla-dx
by on (#238590)
I've edited my reply to remove the hardware-oriented parts since lidnariq et al can answer those. I did do write-ups but this thread is turning into what I call a "time vampire" and that isn't making me happy.

rchoudhary wrote:
So based on that, let's say my main.asm file had something like this:
{snipping for brevity}
Would the preprocessor turn lda myvar into lda $008100? Can the assembler handle a full 24 bit address though?

There's no preprocessor involved. I think you mean "assembler pass", which would be true -- sort of. It's more complicated with assembler+linker combos but I'll explain it.

I can't answer the first question because I don't know what WLA DX does with .bank 0. I don't use WLA DX for reasons exactly like this, and exactly why I chastise the thing to no end. I would expect to see an .org in there somewhere, or possibly a linker config that specifies bank-to-address values. .bank 0 to me doesn't smell like "65816 bank $00", it smells more like "the first bank". There must be something else that correlates that with an actual 65816 bank/address.

As for your 2nd question: of course the assembler can handle long addressing! Nothing stops you from doing lda.l myvar to use opcode $AF (lda abslong), which is 4 total bytes in size.

But what address it assembles to is going to depend on WLA DX's configuration (either the assembler or the linker, it depends. "It depends" applies to ca65/ld65 too, just for the record. Linkers make something that's pretty logical/simple into something much more complicated.) This is why, again, I strongly suggest generating listings files. From these you can often discern if the address you expect matches what the assembler generated.

For symbols that get deferred to the linker, the situation is more complicated, as the addresses are "relative" or "relocatable" rather than literal. I think it's easier if I just demonstrate using ca65/ld65 (because there's no way in hell I'm touching WLA DX). Below is a line of code I wrote, and this output is from the listings file ca65 (the assembler) generated:

Code:
00038Ar 1  BF rr rr rr      lda f:TextLookupTable,x

This is an accumulator load from a 24-bit address indexed with X. The address is label called TextLookupTable. I forced 24-bit addressing using the f: prefix (this is akin to WLA DX's .l suffix on the opcode, or on the operand). But you can see there's no actual address there -- it shows rr rr rr where there would normally be literal values (example: had I written lda $7e1234,x I would've gotten BF 34 12 7E for the actual bytes in the listing file itself). So I have to refer to the symbol-to-addressing map file that the linker (ld65) generates. Here we find it:

Code:
al 039800 .TextLookupTable

And there we have our answer: address $039800. Thus, we can safely assume the full instruction is BF 00 98 03 -- and sure enough that is what's in the ROM file.

Because you're asking all sorts of questions, the next thing you're going to ask me is how exactly the linker knew to put that variable in bank $03 address $9800? Those details come from a combination of the actual code itself (where TextLookupTable is declared) and the linker configuration. So let's look at that. First the code:

Code:
.segment "BANK03" : far

...

TextLookupTable:
.repeat 12, row
    .repeat 8, i
        .word ((i*2)+(row*$20) & $03FF)
    .endrepeat
.endrepeat
...

The important part is on the first line: this variable/label is inside of a segment called BANK03, and it's declared as far (which means the assembler is supposed to know intelligently when to use 24-bit vs. 16-bit addressing, but I wanted to take no chances). The important thing is that it's not in bank $00 where my code runs from. So what about this magic BANK03 thing? On to the linker configuration:

Code:
MEMORY {
    ...
    ROM00: start = $008000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
    ROM01: start = $018000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
    ROM02: start = $028000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
    ROM03: start = $038000, size = $8000, type = ro, file = %O, fill = yes, fillval = $FF;
    ...
}
SEGMENTS {
    CODE:     load = ROM00, align = $8000, type = ro, optional = no;
    BANK01:   load = ROM01, align = $8000, type = ro, optional = yes;
    BANK02:   load = ROM02, align = $8000, type = ro, optional = yes;
    BANK03:   load = ROM03, align = $8000, type = ro, optional = yes;
    ...
}

This is where I start getting annoyed at ld65 (I'm one of those people who prefer assemblers without linkers, i.e. I'd rather just use .org statements), but hopefully you can "sort of" piece together what's happening there: segment BANK03 refers to a memory layout entry called ROM03. ROM03's memory layout says the start of the segment is at $038000 in 65816 addressing space. All this correlates/works properly with mode 20 given its memory mapping.

Remember: the linker is what generates the ROM file itself, which is how it's able to "fill in all the blanks" that the assembler couldn't.

I am certain WLA DX works similarly in this regard, I just want nothing to do with its terrible syntactical sugar to try and make heads/tails of it. It doesn't really make any sense to me either, while ca65/ld65's setup at least makes MORE sense, once you put all the pieces together.

rchoudhary wrote:
User 93143 pointed out that there is a Data Bank Register. Would the assembler use that instead? Like would it load the Data Bank Register with $00 and then just do lda $8100?

No, the assembler will not do this for you automatically. You can do it with code yourself. Again: I don't speak WLA DX but this can be done with pretty much any assembler using pseudo-ops that allow you to get the bank address of "something" (a symbol, label, whatever) and do whatever you want with it. Using my above ca65/ld65 example, I could effectively do this in my code:

Code:
sep #$20    ; A=8
lda #.bankbyte(TextLookupTable)    ; or even .bankbyte(BANK03)
pha
plb

You might know .bankbyte as ^ in WLA DX or other assemblers -- it gets the upper 8-bits of the 24-bit address (calculated either at assemble-time or link-time; see above).

There is currently no assembler out there, that I know of, which "tracks" B (a.k.a. DB, i.e. data bank register) changes. In fact, you can't do this reliably anyway in the assembler, at least not reliably 100% of the time, for the exact same reasons as why you can't reliably 100% of the time track sep/rep sizing: because those instructions happen at runtime, not assemble-time. An assembler is not an emulator. The best the assemblers can do is try to "follow your code" (linearly) and handle it from there. This is why every assembler has pseudo-ops that let you tell it "8-bit X/Y" or "16-bit A", etc.. Just nobody has done it with B/DB.

There is an assembler in the works that will handle D (direct page) and B/DB changes through pseudo-ops, but it's not out yet and still being worked on (but actively/daily).

The rule of thumb is this: if you change S (stack), D (direct page), or B/DB (data bank register), it is _your responsibility_ to make sure your code does the right thing and is accessing the right variables, behaves correctly, backs up/restores those values (if doing something temporary), etc.. The assembler cannot do everything for you magically.

rchoudhary wrote:
And then finally, in the wikibook article on memory mapping, there is this chart:
{snipping for brevity}
The part that confuses me is the hardware registers bit. If I want to force blank, I simply write $80 to $2100 in the code. But in which bank? What's the difference between $002100, $002100, ..., $3F2100? Also, why can I just do a lda $2100 instead of having to do lda $xx2100 (where xx is whatever the right bank is? I'm assuming it's because of the Data Bank Register?

I think these massive posts and walls of text (replies, questions, everything) are causing you to miss/overlook things already stated.

There is no difference in mode 20 between $2100 at bank $00, bank $01, or bank $3E. The $2000-5FFF region is mirrored as well. PLEASE STOP LOOKING AT THAT TERRIBLE MEMORY MAP ON THE WIKIBOOKS WIKI. Instead, look at Tepples' mode 20 memory map again: download/file.php?id=15008 :-)

Address ranges $2000-5FFF are labelled I/O for a reason (MMIO registers). Now look at the bank numbers at the top of the picture. Now you understand the mirroring; i.e. you can have B set to $2E and do sta $2100 and you'll affect the screen brightness register. This is why I said earlier mode 20 is "easy to program for"!

I stated earlier that the 65816 on power-on/reset starts in bank $00 -- that's where your code starts executing from. B is also set to $00. The 65816 CPU starts in 6502 emulation mode as well. This is why one of the most common set of instructions at a RESET vector are these:

Code:
sei   ; Inhibit interrupts
phk
plb   ; Sets B/DB to the same bank as K (active code bank)
clc
xce   ; Enable native 65816 mode (i.e. get out of emulation mode)

Often followed by this, or a variation of:
Code:
rep #$30
lda #$0000
tcd
ldx #$01FF
txs

And a cli later on to re-enable interrupts (this doesn't affect NMI, only IRQ and some others).

BTW, you cannot lda $2100. Not all MMIO registers are readable -- in fact, it's best to assume they aren't unless explicitly stated in documentation. Try referring to this. Because of this fact (not all MMIO registers are readable), it's up to you to retain MMIO register contents through use of a separate variable somewhere in direct page or RAM, if you need to. It's based entirely on circumstance/situation. $2100 is not a great example because it pretty much just does forced blanking (screen off) and brightness. But if you look at $2101 (also not readable), you can see how there would be situations where you may want to tweak a single bit in that MMIO register, but since you can't read it directly, you need to "keep track" somehow...