CC65 linker config for MMC1 made up of lots of NROMs?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
CC65 linker config for MMC1 made up of lots of NROMs?
by on (#238180)
Does anyone have a CC65 linker config that could be used to have a custom menu with a bunch of NROMs to load? I have a bunch of development builds of different ROMs I need to be able to quickly switch between.

I see in NesDEV that MMC1 can be coaxed to work in this manner, but have not made a bank switched ROM with CC65, yet.

-Thom
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#238182)
Start with https://github.com/pinobatch/snrom-template and work from there. Note that this is using ca65/ld65 (read: assembler and linker), not cc65/ld65 (read: compiler and linker).
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#238190)
tschak909 wrote:
Does anyone have a CC65 linker config that could be used to have a custom menu with a bunch of NROMs to load?

Are these NROM-128 (16 KiB PRG + 8 KiB CHR), NROM-256 (32 KiB PRG + 8 KiB CHR), NROM modded to use CHR RAM (e.g. Tank Demo; RHDE), or a mixture of the three? And does your MMC1 cart use CHR ROM (SLROM) or CHR RAM (SGROM)?
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#238211)
Standard NROM-128 with 8K CHR-ROM on each.

-Thom
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#238212)
I think the main problem is just finding ~16 bytes of empty space in each NROM for a reset stub.

Once patched you can just .incbin one of those ROMs into each bank, so most of the linker configuration is pretty flexible. You could really just build your menu as a 16k binary and then concatenate all the other ROM banks with a separate tool (e.g. copy /b a.prg + b.prg ab.prg), for which purpose an NROM-128 linker template is probably almost the same as what you'd need. (A generic MMC1 template has a lot more features than you're actually using for this.)
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239078)
Turns out, I really do need the whole 32KB PRG space to be banked out...

This is what I am using so far, and it produces a legal cart that boots up in Nestopia (fceux doesn't like it yet):

Code:
MEMORY {

    ZP:                 start = $0000, size = $0100, type = rw, define = yes;
    HEADER:             start = $0000, size = $0010, file = %O ,fill = yes;
    PRG:                start = $8000, size = $7fc0, file = %O ,fill = yes, define = yes;
    PRG01:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG02:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG03:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG04:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG05:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;

        DMC:            start = $ffc0, size = $003a, file = %O, fill = yes, define = yes;
        VECTORS:        start = $fffa, size = $0006, file = %O, fill = yes;
    CHR00:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR01:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR02:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR03:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR04:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR05:              start = $0000, size = $2000, file = %O, fill = yes;
    RAM:                start = $0300, size = $0500, define = yes;

          # Use this definition instead if you going to use extra 8K RAM
          # RAM: start = $6000, size = $2000, define = yes;

}

SEGMENTS {

    HEADER:   load = HEADER,         type = ro;
    STARTUP:  load = PRG,            type = ro,  define = yes;
    LOWCODE:  load = PRG,            type = ro,                optional = yes;
    INIT:     load = PRG,            type = ro,  define = yes, optional = yes;
    CODE:     load = PRG,            type = ro,  define = yes;
    RODATA:   load = PRG,            type = ro,  define = yes;
    # STUB00:   load = PRG, type = ro, start = $BFF0;
    # STUB01:   load = PRG01, type = ro, start = $BFF0;
    # STUB02:   load = PRG02, type = ro, start = $BFF0;
    # STUB03:   load = PRG03, type = ro, start = $BFF0;
    # STUB04:   load = PRG04, type = ro, start = $BFF0;
    # STUB05:   load = PRG05, type = ro, start = $BFF0;
    ONCE:     load = PRG,            type = ro, optional = yes;
    DATA:     load = PRG, run = RAM, type = rw,  define = yes;
    VECTORS:  load = VECTORS,        type = ro;
    SAMPLES:  load = DMC,            type = ro;
    PRG1:    load = PRG01,            type = ro, align=$100;
    PRG2:    load = PRG02,            type = ro, align=$100;
    PRG3:    load = PRG03,            type = ro, align=$100;
    PRG4:    load = PRG04,            type = ro, align=$100;
    PRG5:    load = PRG05,            type = ro, align=$100;
    CHR0:    load = CHR00,            type = ro, align=$100;
    CHR1:    load = CHR01,            type = ro, align=$100;
    CHR2:    load = CHR02,            type = ro, align=$100;
    CHR3:    load = CHR03,            type = ro, align=$100;
    CHR4:    load = CHR04,            type = ro, align=$100;
    CHR5:    load = CHR05,            type = ro, align=$100;
    BSS:      load = RAM,            type = bss, define = yes;
    HEAP:     load = RAM,            type = bss, optional = yes;
    ZEROPAGE: load = ZP,             type = zp;
}

FEATURES {

    CONDES: segment = INIT,
            type = constructor,
            label = __CONSTRUCTOR_TABLE__,
            count = __CONSTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
            type = destructor,
            label = __DESTRUCTOR_TABLE__,
            count = __DESTRUCTOR_COUNT__;
    CONDES: type = interruptor,
            segment = RODATA,
            label = __INTERRUPTOR_TABLE__,
            count = __INTERRUPTOR_COUNT__;

}

SYMBOLS {

    __STACKSIZE__: type = weak, value = $0500;          # 5 pages stack

        NES_MAPPER: type = weak, value = 1;                     # mapper number
        NES_PRG_BANKS: type = weak, value = 12;                 # number of 16K PRG banks, change to 2 for NROM256
        NES_CHR_BANKS: type = weak, value = 6;                  # number of 8K CHR banks
        NES_MIRRORING: type = weak, value = 1;                  # 0 horizontal, 1 vertical, 8 four screen

}


and including the chr and prg banks via an .incbin declaration for each.

and I can switch CHR banks, this is good.
now if I can switch PRG banks, and jump back to the RESET vector, without falling off a cliff.. that'd be perfect..

I tried injecting the stub bits from snrom-template, but am wondering if I need to basically cut off the last 16 bytes of the ROM binary I am injecting, to do this? (my rom prg's are exactly 32768 bytes, and chr rom's are 8192 bytes, and contain reset vectors at the end of the 32k PRG..dunno how I am going to shove that into the end of a 16K bank...sigh)

-Thom
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239080)
Most NES mappers are only well-defined with ROMs that are powers of two in size. I'm surprised Nestopia is working correctly with only 6x32 KB PRG; it really should reject that.
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239082)
Yeah, I kinda figured, so I have padded it out, and it now sees it as an SJROM:

Attachment:
sjrom.PNG
sjrom.PNG [ 6.91 KiB | Viewed 7294 times ]
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239084)
Does it work in FCEUX? If not, you probably need to have a boot stub in all the "padding" PRG banks too.

MMC1 looks like it was intended to power up on its own in the "last 16K bank is at the top", but hardware testing has shown that it's not very robust.
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239085)
lidnariq wrote:
Does it work in FCEUX? If not, you probably need to have a boot stub in all the "padding" PRG banks too.

MMC1 looks like it was intended to power up on its own in the "last 16K bank is at the top", but hardware testing has shown that it's not very robust.


Yeah, I got it to work in FCEUX as well, by re-ordering the segments so that the menu program is the very last segment.

Code:
MEMORY {

    ZP:                 start = $0000, size = $0100, type = rw, define = yes;
    HEADER:             start = $0000, size = $0010, file = %O ,fill = yes;
    PRG:                start = $8000, size = $7fc0, file = %O ,fill = yes, define = yes;
    PRG01:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG02:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG03:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG04:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG05:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG06:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;
    PRG07:              start = $8000, size = $8000, file = %O ,fill = yes, fillval = $FF;

        DMC:            start = $ffc0, size = $003a, file = %O, fill = yes, define = yes;
        VECTORS:        start = $fffa, size = $0006, file = %O, fill = yes;
    CHR00:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR01:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR02:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR03:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR04:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR05:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR06:              start = $0000, size = $2000, file = %O, fill = yes;
    CHR07:              start = $0000, size = $2000, file = %O, fill = yes;
    RAM:                start = $0300, size = $0500, define = yes;

          # Use this definition instead if you going to use extra 8K RAM
          # RAM: start = $6000, size = $2000, define = yes;

}

SEGMENTS {

    HEADER:   load = HEADER,         type = ro;
    PRG1:    load = PRG01,            type = ro, align=$100;
    PRG2:    load = PRG02,            type = ro, align=$100;
    PRG3:    load = PRG03,            type = ro, align=$100;
    PRG4:    load = PRG04,            type = ro, align=$100;
    PRG5:    load = PRG05,            type = ro, align=$100;
    PRG6:    load = PRG06,            type = ro, align=$100;
    PRG7:    load = PRG07,            type = ro, align=$100;
    STARTUP:  load = PRG,            type = ro,  define = yes;
    INIT:     load = PRG,            type = ro,  define = yes, optional = yes;
    CODE:     load = PRG,            type = ro,  define = yes;
    RODATA:   load = PRG,            type = ro,  define = yes;
    ONCE:     load = PRG,            type = ro, optional = yes;
    DATA:     load = PRG, run = RAM, type = rw,  define = yes;
    VECTORS:  load = VECTORS,        type = ro;
    SAMPLES:  load = DMC,            type = ro;
    CHR0:    load = CHR00,            type = ro, align=$100;
    CHR1:    load = CHR01,            type = ro, align=$100;
    CHR2:    load = CHR02,            type = ro, align=$100;
    CHR3:    load = CHR03,            type = ro, align=$100;
    CHR4:    load = CHR04,            type = ro, align=$100;
    CHR5:    load = CHR05,            type = ro, align=$100;
    CHR6:    load = CHR06,            type = ro, align=$100;
    CHR7:    load = CHR07,            type = ro, align=$100;
    BSS:      load = RAM,            type = bss, define = yes;
    HEAP:     load = RAM,            type = bss, optional = yes;
    ZEROPAGE: load = ZP,             type = zp;
}

FEATURES {

    CONDES: segment = INIT,
            type = constructor,
            label = __CONSTRUCTOR_TABLE__,
            count = __CONSTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
            type = destructor,
            label = __DESTRUCTOR_TABLE__,
            count = __DESTRUCTOR_COUNT__;
    CONDES: type = interruptor,
            segment = RODATA,
            label = __INTERRUPTOR_TABLE__,
            count = __INTERRUPTOR_COUNT__;

}

SYMBOLS {

    __STACKSIZE__: type = weak, value = $0500;          # 5 pages stack

        NES_MAPPER: type = weak, value = 1;                     # mapper number
        NES_PRG_BANKS: type = weak, value = 16;                 # number of 16K PRG banks, change to 2 for NROM256
        NES_CHR_BANKS: type = weak, value = 8;                  # number of 8K CHR banks
        NES_MIRRORING: type = weak, value = 1;                  # 0 horizontal, 1 vertical, 8 four screen

}
Re: CC65 linker config for MMC1 made up of lots of NROMs?
by on (#239087)
MMC1 has nuances/complexities relating to its fixed bank (read: different chip revisions behave differently or erratically). Tepples' snrom-template code for MMC1, specifically how resetstub_entry fits into the picture, does its best to work around these quirks.

This is probably why moving your "startup bits" into the last/final bank fixed your problems -- odds are it was breaking in FCEUX as a result of valid CPU vectors not being present in $FFFA-FFFF. In contrast, Mesen gives you a visual layout depicting what PRG-ROM and CHR-ROM bank is mapped to what portions of CPU and PPU addressing space (see bottom of debugger window).