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

Generic CA65 project template

Generic CA65 project template
by on (#73745)
I've been thinking about making a generic template CA65 (like I posted here), one where the user could switch mapper, PRG size, CHR size etc easily. I'm tentatively calling this "xnes". It would consist of two files: and xnes.cfg. Thanks to Wave for the inspiration.

It works something like this:
.include ""
    xnes_build_ines 64, 0, xnes::mapper::UxROM | xnes::mirroring::VERTICAL | xnes::SRAM_BATTERY

xnes_build_ines is a macro that takes PRG and CHR size (in kilobytes) and flags as parameters. Based on these it builds iNES header and exports symbols for the linker config file which will make the output file size match the given PRG and CHR size. The macro can give diagnostic warnings for stuff like non-power-of-two PRG sizes or invalid mapper configs.

There are some design decisions to be made, though. I was wondering if anybody would have opinions on them:
  • Should bank (segment) size stay same regardless of mapper (e.g. 8K) or change depending on the chosen mapper? IMO it should vary, otherwise using, for example, NROM would require placing stuff in 4 different segments, possibly wasting space. The con is that switching mappers becomes harder.
  • AxROM has 32K banking, so boot code (for switching to known bank) and IRQ vectors need to be placed in every bank. The macro can automatize all of that if NMI, RESET and IRQ vectors were passed to it... also passing them to the macro is kinda cool because placeholders can be provided if user leaves them empty. This would change the code to something like:
    .include ""
    .import my_nmi, my_reset, my_irq
        xnes_build_ines 64, 0, xnes::mapper::UxROM | xnes::mirroring::VERTICAL | xnes::SRAM_BATTERY, my_nmi, my_reset, my_irq
  • xnes could provide size optimized, well tested reset/init routine that initializes EVERYTHING to known state (incl. PPU/APU) before jumping to user specified entry point. This could be overridden if user so desires.
  • Another problem with 32K banking: IRQ handlers would also need to be duplicated across banks unless the programmer makes sure IRQs can only trip when the correct bank is mapped in. One solution for this would be to have xnes duplicate some trampoline code (indirect JMP, 5 cycles isn't too bad).
  • MMC1 problem: should the programmer be allowed to switch between PRG config (16K/32K and what memory area is fixed) at runtime? One negative side to this is that there's no way to detect (without CPU cost) whether user is using the mapping macros/functions incorrectly.
  • How should the mapping macros be named? I was thinking something like map_4K_CHR_at_0000, map_8K_PRG_at_8000, etc. I like verbose names. User gets a compile time error if the chosen mapper doesn't support the requested functionality.
  • Problem I thought of just now: the mapping macros need to come from the .inc file, so when including it it needs to know what mapper was selected to define the correct macros. So the mapper might have to be specified with a .define/something before including This sucks because it'd have to be done every time was included. :( However programmer could place all of that to a general include file of his own...
    XNES_MAPPER = 2 ; bohoo, don't have .enums to use :(
    .include ""
    ; mapping macros are now defined

    This wouldn't be needed if functions were used instead of macros. I think it might be the best solution.. it's not too often that you can't spare couple of cycles for JSR/RTS. And in that case you can always write to the mapper regs manually.
Sorry for the wall of text, may not be the most coherent thing in the world. :) Pros to using something like this: it's newbie friendly, very easy to setup. Cons: much of the flexibility of CA65 is lost. If somebody is interested in working on this with me I could upload the current (very basic) version to some source control thing somewhere.

by on (#73780)
Woa, seems a lot better than my idea.
In the functions case I use: map_setPrg8K_x_a for example, meaning load in segment x (in 8K partitions) bank a