For other readers, this is what I ended up doing:
ld65 memory config (called main.cfg) --
Code:
MEMORY {
ZP: start = $00, size = $100, type = rw;
RAM: start = $0200, size = $600, type = rw;
HDR: start = $0000, size = $10, type = ro, file = %O, fill = yes;
PRG0: start = $8000, size = $4000, type = ro, file = %O, fill = yes, fillval = $FF;
PRG1: start = $8000, size = $4000, type = ro, file = %O, fill = yes, fillval = $FF;
PRG2: start = $8000, size = $4000, type = ro, file = %O, fill = yes, fillval = $FF;
PRG3_C000: start = $C000, size = $2000, type = ro, file = %O, fill = yes, fillval = $FF;
PRG3_E000: start = $E000, size = $2000, type = ro, file = %O, fill = yes, fillval = $FF;
CHR0: start = $0000, size = $1000, type = ro, file = %O, fill = yes, fillval = $00;
CHR1: start = $0000, size = $1000, type = ro, file = %O, fill = yes, fillval = $00;
}
SEGMENTS {
ZP: load = ZP, type = zp;
BSS: load = RAM, type = bss, align = $100, define = yes;
HEADER: load = HDR, type = ro, align = $10;
PRG0_8000: load = PRG0, type = ro, start = $8000;
PRG0_A000: load = PRG0, type = ro, start = $A000;
PRG1_8000: load = PRG1, type = ro, start = $8000;
PRG1_A000: load = PRG1, type = ro, start = $A000;
PRG2_8000: load = PRG2, type = ro, start = $8000;
PRG2_A000: load = PRG2, type = ro, start = $A000;
PRG3_C000: load = PRG3_C000, type = ro, start = $C000;
PRG3_E000: load = PRG3_E000, type = ro, start = $E000;
VECTORS: load = PRG3_E000, type = ro, start = $FFFA;
CHR0: load = CHR0, type = ro, define = no;
CHR1: load = CHR1, type = ro, define = no;
}
FILES {
%O: format = bin;
}
main.s, which is what's assembled when calling ca65:
Code:
.setcpu "6502"
;
; NES (1.0) header
; http://wiki.nesdev.com/w/index.php/INES
;
.segment "HEADER"
.byte "NES", $1a
.byte $04 ; 16KB PRG-ROM banks = 4
.byte $01 ; 8KB CHR-ROM banks = 1
.byte $40, $00 ; Mapper 4 (MMC3)
.byte $00
.byte $00
.byte $00
.byte $00
.byte $00
.byte $00
.byte $00
.byte $00
;
; Zero page variables
;
.segment "ZP"
scroll1: .res 1
scroll2: .res 1
;
; PRG banks (8KBytes each, per MMC3)
;
.scope PRG0
.segment "PRG0_8000"
.segment "PRG0_A000"
.endscope
.scope PRG1
.segment "PRG1_8000"
.segment "PRG1_A000"
.endscope
.scope PRG2
.segment "PRG2_8000"
.segment "PRG2_A000"
.endscope
.scope PRG3_C000
.segment "PRG3_C000"
.include "prg3_c000.s"
.endscope
.scope PRG3_E000
.segment "PRG3_E000"
.include "prg3_e000.s"
;
; Labels nmi/reset/irq are part of prg3_e000.s
;
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
.endscope
;
; CHR banks (1 or 2KBytes each, per MMC3)
;
.scope CHR0
.segment "CHR0"
.incbin "font.chr"
.endscope
.scope CHR1
.segment "CHR1"
.endscope
You can see a couple things here:
1. Code for PRG0, PRG1, and PRG2 has not been written -- instead, the linker takes care of this by filling the relevant 16KByte regions in the resulting .nes file with value $FF. When I need these PRG pages (which per MMC3 will be 8KBytes), I'll edit the ld65 memory map and change the size to $2000 and split them up much like I did with PRG3_C000 and PRG3_E000.
2. The NMI, RESET, and IRQ/BRK vectors are declared in main.s, but the labels that are referenced are in prg3_e000.s.
3. CHR0 references a 4096-byte file (font.chr) and CHR1 isn't used yet (like #1 above, te linker will fill this 4096-byte region in the resulting .nes file with $00 -- I haven't gotten around to drawing graphics for that CHR page yet).
prg3_c000.s contains literally jack squat right now --
Code:
.org $c000
nop
nop
nop
nop
nop
prg3_e000.s -- you can see the vector labels defined here:
Code:
.org $e000
reset:
sei
cld
ldx #$ff
txs
;
; Infinite loop for the time being
;
: jmp :-
nmi:
rti
irq:
rti
And finally how to build it (I just call this make.bat) --
Code:
@echo off
SET CC65=D:\Console\cc65
del *.o test.nes
@echo on
%CC65%\bin\ca65 main.s
%CC65%\bin\ld65 -C main.cfg -m ld65_map.txt -o test.nes main.o
@pause
What ld65_map.txt contains after a successful build:
Code:
Modules list:
-------------
main.o:
HEADER Offs = 000000 Size = 000010
ZP Offs = 000000 Size = 000002
PRG3_C000 Offs = 000000 Size = 000005
PRG3_E000 Offs = 000000 Size = 00009F
VECTORS Offs = 000000 Size = 000006
CHR0 Offs = 000000 Size = 001000
Segment list:
-------------
Name Start End Size
--------------------------------------------
CHR0 000000 000FFF 001000
HEADER 000000 00000F 000010
ZP 000000 000001 000002
PRG3_C000 00C000 00C004 000005
PRG3_E000 00E000 00E09E 00009F
VECTORS 00FFFA 00FFFF 000006
Exports list:
-------------
Imports list:
-------------
Note that the reason PRG3_E000 is $9F in size is because I do have some PPU/etc. code in there which I've chose to emit from this post -- it's irrelevant to what I'm showing here. :-) I just wanted to provide folks with a working example for ca65/ld65.