This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

.cfg for CA65 with mmc3 256kb CHR+ 256kb PRG?

.cfg for CA65 with mmc3 256kb CHR+ 256kb PRG?
by on (#39442)
I'm running into limitations with the ca65 default link file for .nes.

Just wondering if any of you happen to have any example .cfg (or a set of .cfg) files (and maybe a batch file) for ca65 which I can use to link together a .nes with 256kb CHR and 256kb PRG.

I'm looking to have 8k banks for the .prg where I could specify different memory start addresses for different segments.

For example I'm looking to learn how to specify that my sound routines that are assembled as if starting at $C000, and also game logic routines that are assembled as if starting at $C000 which would be swapped out at run time. Would these need to be in seperate .asm files, and assembled seperately? Or do I just define different segments in the .cfg which map to the same address?

Thanks!
Crash

by on (#39514)
Which board are you talking about? The only common boards that I know of that can switch $C000 are GNROM, BNROM, and A*ROM (32 KiB banks), and T*ROM (8 KiB banks; $E000 and either $8000 or $C000 must be fixed).

by on (#39526)
Here is how my config file looks for my Chipography program. It uses a modified UNROM so the lower 16kB is fixed, and the upper 16kB ($C000+) is banked. Note that when bankswitching this area, you'll probably need vectors in every bank that point to valid addresses. (due to random startup bank or random reset button hitting by the user).

I cut out a lot of redundant parts. "DTT" btw is a DPCM note table for Nerdtracker 2, it's in the same place in every bank for the benefit of the sound engine.

Also, you can surely disregard me using $C080 instead of $C000. It's due to some little coding quirks with NT2.

Code:
MEMORY {
            ZP: start = $00, size = $100, type = rw;
            RAM: start = $200, size = $600, type = rw;
            PRG: start = $8000, size = $8000, type = ro, file = %O;
            PRG2: start = $C000, size = $4000, type = ro, file = %O;
...
            PRG31: start = $C000, size = $4000, type = ro, file = %O;
            PRG32: start = $C000, size = $4000, type = ro, file = %O;
        }
        SEGMENTS {
            CODE: load = PRG, type = ro, start = $8000;
            RODATA: load = PRG, type = ro;
            DATA: load = RAM, type = bss;
            ZEROPAGE: load = ZP, type = zp;
            BSS: load = RAM, type = bss, define = yes;
            SAMPLES: load = PRG, type = ro, start = $C080;
            DTT1: load = PRG, type = ro, start = $FCF8;
            VECTORS: load = PRG, type = ro, start = $FFFA;
            BANK2: load = PRG2, type = ro, start  = $C080;
            DTT2: load = PRG2, type = ro, start = $FCF8;
            VECTORS2: load = PRG2, type = ro, start = $FFFA;
...
            BANK31: load = PRG31, type = ro, start = $C080;
            DTT31: load = PRG31, type = ro, start = $FCF8;
            VECTORS31: load = PRG31, type = ro, start = $FFFA;
            BANK32: load = PRG32, type = ro, start = $C000;
            VECTORS32: load = PRG32, type = ro, start = $FFFA;
        }

by on (#39789)
Thanks for the info, so the board I'm considering is a TKROM, and I'm looking to swap out $8000 and $a000 keeping $c000 and $e000 fixed.

I think I can build what I need from the snippet memblers provided, but having a TKROM example makefile I think would be useful to the community. (256 or 512kb PRG and 256KB CHR)

by on (#39806)
Memblers wrote:
Here is how my config file looks for my Chipography program. It uses a modified UNROM so the lower 16kB is fixed, and the upper 16kB ($C000+) is banked.

That's the Crazy Climber mapper, right?

For a T*ROM with a 256 KiB PRG and switchable $8000, you'll need to make a MEMORY list that defines 30 areas with size=$2000, fill=yes, and either start=$8000 or start=$A000 depending on where you plan to load the bank. Then you'll need one more MEMORY area with start=$C000, size=$4000. Finally, add corresponding SEGMENTS for each MEMORY area.

by on (#39809)
tepples wrote:
For a T*ROM with a 256 KiB PRG and switchable $8000, you'll need to make a MEMORY list that defines 30 areas with size=$2000, fill=yes, and either start=$8000 or start=$A000 depending on where you plan to load the bank. Then you'll need one more MEMORY area with start=$C000, size=$4000. Finally, add corresponding SEGMENTS for each MEMORY area.

Sorry to interrupt, but I'm scared to death just from reading how complicated this can be. This kind of thing is exactly what scared me away from the common assemblers a while back.

Can anyone point to me the advantage of using segments and have to deal with this whole mess even before one can start coding anything of the game itself? Seriously, I want to get, because I would like to know if it's worth learning all of this before I devote any brainpower to it.

With the assembler I currently use, I can just tell where each bank will be mapped to and I can start coding. ORG statements at the end will define their sizes. I can have as many banks as I want, mapped anywhere I want, of any size I want, without any of this fuss, and the code is still pretty well organized. I'm also working on a TKROM game.

I'm usually not scared of complicated things and will dedicate some effort into understanding them, as long as I see the point. In this case, I fail to see the advantage, so I'm curious to hear what you have to say in favor of the overly complicated assembler setups.

by on (#39810)
One example of how I've used segments, is to keep my code more contained by allowing me to put anything anywhere.

Say I have one subroutine that needs some RAM, some code, and a lookup-table. In a normal assembler you'd end up spreading that crap all over the source file (with all the other RAM definitions, lookup-tables, etc.). With my segments defined I could do this:
Code:
.segment "DATA"
info_index: .res 1

.segment "CODE"
thing_routine:
do_stuff:
 ldx info_index
 lda info_table,x

.segment "RODATA"
info_table: "Example stuff blah"


That just keeps it all together in one place, and lets the linker deal with the final placement.

by on (#39813)
Memblers wrote:
That just keeps it all together in one place, and lets the linker deal with the final placement.

I see... I felt the need to organize things, but I handled it with a decent folder structure, and related things like the ones you mentioned share the same folder. A "master file" includes all the other ones at the proper locations, so it all feels pretty organized and I can easily locate pieces of code and data.

Thanks for the example though. I'll consider looking into it some time, for my next project maybe, since at least for now I'm pretty comfortable with the way I'm currently working.

by on (#39827)
Thanks for all the help! I got it working, the thing that I didn't realize was that I needed to hand assemble the file from the pieces using copy /b.

Here's what I'm doing (64k PRG + up to 256kb CHR):

.cfg file
Code:
MEMORY {
        M_ZEROPAGE: start = $00, size = $100, type = rw;
        M_RAM: start = $200, size = $600, type = rw;
        M_CHAR0:   start = $0, size = $40000, type = ro, file = "sdw.chr";
        M_HEADER: start = $0, size = $10, type = ro, fill = yes, file = "sdw.hdr";
        M_WORKRAM: start = $6000, size = $2000, type = rw;
        M_PRGFIXED: start = $C000, size = $4000, fill = yes, type = ro, file = %O;
        M_PRG_AUDIO_CODE:  start = $8000, size = $2000, type = ro, fill = yes, file = "sdw_audio_code.prg";
        M_PRG_MUSIC0:      start = $A000, size = $2000, type = ro, fill = yes, file = "sdw_music0.prg";
        M_PRG_LEVEL0:  start = $8000, size = $2000, type = ro, fill = yes, file = "sdw_level0.prg";
        M_PRG_MTILE0:  start = $A000, size = $2000, type = ro, fill = yes, file = "sdw_tile0.prg";
        M_PRG_LEVEL1:  start = $8000, size = $2000, type = ro, fill = yes, file = "sdw_level1.prg";
        M_PRG_MTILE1:  start = $A000, size = $2000, type = ro, fill = yes, file = "sdw_tile1.prg";
        M_PRG_LEVEL2:  start = $8000, size = $2000, type = ro, fill = yes, file = "sdw_level2.prg";
        M_PRG_MTILE2:  start = $A000, size = $2000, type = ro, fill = yes, file = "sdw_tile2.prg";
       }

SEGMENTS {
            HEADER:  load = M_HEADER, type = ro;
            CHARS:    load = M_CHAR0, type = ro;
            CODE:    load = M_PRGFIXED, type = ro, start = $C000;
            VECTORS: load = M_PRGFIXED, type = ro, start = $FFF0;
            ZEROPAGE: load = M_ZEROPAGE, type = zp;
            DATA:     load = M_RAM, type = bss;
            PRG_AUDIO_CODE: load = M_PRG_AUDIO_CODE, type = ro, start = $8000;
            PRG_MUSIC0:     load = M_PRG_MUSIC0, type = ro, start = $A000;
            PRG_LEVEL0:     load = M_PRG_LEVEL0, type = ro, start = $8000;
            PRG_MTILE0:     load = M_PRG_MTILE0, type = ro, start = $A000;
            PRG_LEVEL1:     load = M_PRG_LEVEL1, type = ro, start = $8000;
            PRG_MTILE1:     load = M_PRG_MTILE1, type = ro, start = $A000;
            PRG_LEVEL2:     load = M_PRG_LEVEL2, type = ro, start = $8000;
            PRG_MTILE2:     load = M_PRG_MTILE2, type = ro, start = $A000;
        }


And the batch file I use to piece together the .nes file
Code:
call ca65 %1.asm
call ld65 -C tkrom.cfg -o %1.prg %1.o
echo off
call copy /b /y sdw.hdr %1.nes
call copy /b /y %1.nes+sdw_level0.prg %1.nes
call copy /b /y %1.nes+sdw_tile0.prg %1.nes
call copy /b /y %1.nes+sdw_level1.prg %1.nes
call copy /b /y %1.nes+sdw_tile1.prg %1.nes
call copy /b /y %1.nes+sdw_level2.prg %1.nes
call copy /b /y %1.nes+sdw_tile2.prg %1.nes
call copy /b /y %1.nes+%1.prg %1.nes
call copy /b /y %1.nes+sdw.chr %1.nes
echo on

by on (#39829)
That's really awesome! That gives me some ideas that'll hopefully get my UNROM setup working.

EDIT: That totally worked! Manually assembling the NES file from the individual header/PRG files at the end was the crucial step. Thanks!