- For making cartridges of your Super NES games, see Reproduction.
The first thing is to understand how the SNES uses the available memory. As far as I know, RAM and registers are at $xx:0000 - $xx:7FFF and ROM is at $xx:8000 - $xx:FFFF. xx is the value at Program Bank or Data Bank register depending on whether its accessed by read/write instruction or instruction fetch. Do reads/writes at $0000 - $7FFF care what value xx is?
Now, PPU memory is a bit more of a mystery to me. How do you assign pattern tables and how many tiles can they hold at once? Can parts of the pattern table be swapped or are you limited to certain minimum sizes you can swap at once? Or can you swap individual tiles kinda like CHR RAM on some NES mappers? What about nametables and attributes?
There are multiple different ways that cartridges work. What you described is "Mode 20h", mostly, or "LoROM".SusiKette wrote:The first thing is to understand how the SNES uses the available memory.
Divide the 24-bit address of the SNES's CPU into three eight-bit quantities. In order, call those the "bank", "page", and ... nothing.
The SNES itself only imposes a few limits:
- 8K of RAM can be accessed in banks $00-$3F, $7E, and $80-$BF, across pages $00-$1F.
- 128K of RAM can be accessed in banks $7E and $7F, across all pages, not redundant.
- registers to configure how everything operates can be accessed in banks $00-$3F and $80-$BF, in pages $21 and $40-$43.
The SNES hardware makes specific memory layouts easier. It provides a specific signal on the connection to the cartridge that lets the cart know when ("bank" is $40-$7D or $C0-$FF) or ("bank" is $00-$3F or $80-$BF and "page" is $80-$FF).
In the SNES, 64KB of RAM is available to hold everything to be drawn. Unlike the NES, this cannot be changed by the cartridge. Also unlike the NES, there's a wide variety of ways to interpret the data. There is no attribute table; nametable entries are always 16 bits wide. Up to four background layers plus a layer of sprites can be drawn, each from their own location in memory. Each background layer has its own nametable, each nametable can be any of the four combinations of 32 or 64 tiles wide and tall; each nametable entry can represent an 8x8 (or 16x8 in specific cases) or a 16x16 pixel region.How do you assign pattern tables and how many tiles can they hold at once? Can parts of the pattern table be swapped or are you limited to certain minimum sizes you can swap at once? Or can you swap individual tiles kinda like CHR RAM on some NES mappers? What about nametables and attributes?
I'd suggest using some SNES emulator with a debugger—NO$SNS, bsnes-plus, or Mesen-S—and looking at what's going on.
So in other words, the programmer defines where each data is located in VRAM? Still I don't know how you define sprite graphics location, or is it fixed?lidnariq wrote:In the SNES, 64KB of RAM is available to hold everything to be drawn. Unlike the NES, this cannot be changed by the cartridge. Also unlike the NES, there's a wide variety of ways to interpret the data. There is no attribute table; nametable entries are always 16 bits wide. Up to four background layers plus a layer of sprites can be drawn, each from their own location in memory. Each background layer has its own nametable, each nametable can be any of the four combinations of 32 or 64 tiles wide and tall; each nametable entry can represent an 8x8 (or 16x8 in specific cases) or a 16x16 pixel region.
Exactly! You set the base address in VRAM for each "SC" (tilemap nametable) for each background layer, the "BG CHR name" (pattern table for background characters) and the "OBJ CHR name" (pattern table for sprites) using hardware registers. For example the SC base addresses are set by writing the addresses to registers $2107-$210A. Depending what modes, formats and sizes you use, you have to plan and set up VRAM accordingly.So in other words, the programmer defines where each data is located in VRAM? Still I don't know how you define sprite graphics location
The Color Generator (palette) and OBJ attributes (sprite attributes) however have their own fixed memory and are not set in VRAM. The palette is set into CGRAM and sprite attributes are in OAM like for NES.
Be aware, however:
1. There are alignment requirements for each "type" of data:
a) Background nametable (officially called "BG-SC" or "SC" data) base address is 2KByte / 1KWord aligned; see MMIO registers $2107-210A
b) Background CHR (officially called "BG character data") base address is 8KByte / 4KWord aligned; see MMIO registers $210B-210C
c) Sprites/OAM are more complicated; there is an 16KByte / 8KWord alignment; see MMIO register $2101. Sprite alignment in PPU RAM is somewhat complicated; refer to other docs
d) Palette is stored independently elsewhere (i.e. not in PPU RAM), and specific MMIO registers are used to interface with it
All backgrounds/sprites/everything share the same PPU RAM, and with higher bit depths on the SNES than the NES, it can be very easy to run out of PPU RAM (considering alignments, etc.).
Mirroring/etc. is controlled via MMIO registers as well (commonly called "SC size"), with native support for 1x1, 1x2, 2x1, and 2x2 (i.e. 4-screen). Larger sizes require more PPU RAM, obviously.
2. How the data is used/accessed/utilised depends on video mode (MMIO register $2105) and background number. For example, mode 1 has 3 backgrounds, 2 of which are 4 bits per pixel, with the 3rd BG being 2bpp.
3. SNES documentation and MMIO registers often refer to things/offsets __in words__, not bytes. As such, you'll become extremely reliant on macros to do the byte-to-word conversions, and will often spend time debugging problems relating to this when using PPU RAM viewers and so on (some show things in "raw values the PPU has", others show things in bytes, some intermix both). Here are 4 ca65 macros I use, and some equates, with some examples, for mode 1 (I chose not to include details about BG3):
Code: Select all
.define ppuaddr(addr) (addr / 2) .define bgmap(addr, size) ((((addr / 2) & $FC00) >> 8) | size) .define bg12chr(bg1addr, bg2addr) (((bg2addr / 2) >> 8) | ((bg1addr / 2) >> 12)) .define bg34chr(bg3addr, bg4addr) (((bg4addr / 2) >> 8) | ((bg3addr / 2) >> 12)) SC_SIZE_32X32 = %00 SC_SIZE_64X32 = %01 SC_SIZE_32X64 = %10 SC_SIZE_64X64 = %11 bg1mapaddr = $0000 ; $0000-0FFF: BG1 map, 2 horizontal screens bg2mapaddr = $1000 ; $1000-17FF: BG2 map, single screen bg1chraddr = $2000 ; $2000-xxxx: BG1 CHR data bg2chraddr = $6000 ; $6000-xxxx: BG2 CHR data sep #$30 ; 8-bit A/X/Y rep #$10 ; 16-bit X/Y lda #bgmap(bg1mapaddr, SC_SIZE_64X32) sta $2107 lda #bgmap(bg2mapaddr, SC_SIZE_32X32) sta $2108 lda #bg12chr(bg1chraddr, bg2chraddr) sta $210B ldx #ppuaddr(bg1mapaddr) stx $2116 ... ldx #ppuaddr(bg1chraddr) stx $2116 ...
if you need it.
Lots of great information on there. A lot of it is based on anomie's docs, but there are some original contributions.
Also: https://problemkaputt.de/fullsnes.htm is nocash's doc, which is more recent than anomie's stuff and contains (IIRC) some info that isn't in the above. I find it harder to navigate, but maybe that's just me.
If you already know about these, great.
The great part about direct page is that it's fairly quick and painless to move it, so you don't have to settle on one setting and leave it there for the whole game. In fact you can use it almost as a third index register, which then provides the ability to do nested free indexing because most direct-page instructions have indexed variants. You just have to get in the habit of pushing and pulling D in interrupts.
But yeah, the stack should probably go in RAM...