You will probably have many code segments. One in each bank, at least.
Also, some people like to separate "read only data" (think internal data tables) from "code". You don't need to do this, but you may choose to if you wish.
I do not have an MMC3 example, but I do have one MMC1 example and one NROM example. The first one is from my first nesdev attempt, back in 2008.
MMC1 provides for 16 16K rom banks (well, it might do more, i don't remember. My game wanted to use 16 of them). The top bank is not switched, and is always at $c000 to $ffff (the MMC1 allows other configs, but this is the one that I choose). The permanent segment is called KERNEL (see very bottom of my linker.cfg).
The other "ROM_0" through "ROM_E" are all bank-switched at $8000 to $bfff. I decided to put the bank number at $bfff in each bank, so that code running in KERNEL can push/pop the active bank during an NMI, should it need to bankswitch to handle the NMI. That is the "ROM_x_MARKER" segment. You can ignore those.
The linker section "SEGMENTS" defines the order that the segments are layed out in the ROM file. That is why "HEADER" is first. "ZEROPAGE", "PLAYER" and "DATA" all define RAM, so those are not stored in the final binary. However, the linker needs to know about them so that it can assign addresses and do the "address fixups" when emitting the actual binary.
Note that my MMC1 example uses CHAR-RAM, so you don't see a CHAR-ROM in the SEGMENTS or MEMORY tables.
https://www.ecoligames.com/trac/nes-gam ... linker.cfg
My second example, NROM:
Below is the linker file for my Yars' Revenge game. This game DOES use char-rom, but no bank-switching. prog-rom is 16k, char-rom is 8k. Note that in this config, I define four memory ranges that all get linked into the same segment: BRAND, RODATA, KERNEL and VECTORS. The linker puts them into the same address space, in the segment called "KERNEL". I just choose to use my own segment names instead of the ld65 defaults. When you see "KERNEL", think "CODE", so my examples might be a bit confusing.
I also split the NES's 2K of ram into different memory regions, and I use the linker config file to assign these memory regions to absolute addresses. QSHIELD is a 128 byte buffer of tiles to be blitted to the name-table, 2 columns 16 bytes tall, every NMI (the entire translated QS is stored in ram). NZONE contains the neutral zone tiles that are to be blitted to the name-table, 2 columns 30 tiles tall, every NMI (but only two columns are stored in RAM). I didn't have to do it this way.
I'm showing this because I think that it help illustrate the flexibility of what the linker can do for you.
Code: Select all
# NesYar/src/linker.cfg
# http://www.cc65.org/doc/ld65-5.html
MEMORY {
ZP: start = $0000, size = $0100, type = rw, define = no;
OAMBUF: start = $0200, size = $0100, type = rw, define = no;
QSHIELD: start = $0300, size = $0080, type = rw, define = no;
NZONE: start = $0380, size = $0020, type = rw, define = no;
RAM: start = $0400, size = $0400, type = rw, define = no;
HEADER: start = $0000, size = $0010, file = %O, fill = yes, define = no;
BRAND: start = $c000, size = $0100, file = %O, fill = yes, define = no, fillval = $00;
KERNEL: start = $c100, size = $3ef6, file = %O, fill = yes, define = no, fillval = $00;
VECTORS: start = $fff6, size = $000a, file = %O, fill = yes, define = no;
CHARROM: start = $0000, size = $2000, file = %O, fill = no, define = no;
}
SEGMENTS {
HEADER: load = HEADER, type = ro, define = no;
BRAND: load = BRAND, type = ro, define = no;
RODATA: load = KERNEL, type = ro, define = no, align = $100;
KERNEL: load = KERNEL, type = ro, define = no;
VECTORS: load = VECTORS, type = ro, define = no;
CHARROM: load = CHARROM, type = ro, define = no;
ZEROPAGE: load = ZP, type = zp, optional = yes, align = $100;
OAMBUF: load = OAMBUF, type = bss, define = no;
DATA: load = RAM, type = bss, define = no, align= $100;
NZONE: load = NZONE, type = bss, define = no;
QSHIELD: load = QSHIELD, type = bss, define = no;
}
FILES {
%O: format = bin;
}
btw, the 6502 only has 3 vectors. You'll notice that my linker config allocated 10 bytes instead of 6. I use the extra four bytes to store the assembler time stamp of when the build was made, thusly:
Code: Select all
.segment "VECTORS"
build_ts:
.dword .time
.word sys_nmi
.word sys_reset
.word sys_irq
And I include my char-rom like this:
Code: Select all
.segment "CHARROM"
.incbin "src/charrom.chr", 0, 8192
And the header:
Code: Select all
.segment "HEADER"
.byte $4e,$45,$53,$1a ; header magic
.byte $01 ; # 16K prog roms
.byte $01 ; # 8K char roms
.byte %00000001 ; Vertical mirroring, NROM, no SRAM
.byte %00000000 ; NES mapper upper nibble
.byte 0,0,0,0,0,0,0,0 ; filler