MMC3 Setup Woes

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
shokwav
Posts: 4
Joined: Fri Jun 04, 2021 1:04 am

MMC3 Setup Woes

Post by shokwav »

Greetings, I am having some issues when it comes to setting up the MMC3 using CA65. I have a larger project that actually already uses MMC3; however, I currently only use 2 ROM banks and was trying to expand that to 4 when I started running into issues. I then created a new test project for the purposes of figuring out what exactly I'm messing up on here. Currently the ROM assembles but Nestopia considers it corrupted; can anyone help me understand what exactly I'm doing wrong here? I understand that the startup code must reside in the last bank ($E000) with MMC3, but I can't seem to actually get it to go there.

Here's my header:

Code: Select all

.byte "NES", $1A ;= "NES\0"
.byte $4, $1 ;= 4*16 KiB PRG ROM & 1*8 KiB CHR ROM
.byte %01000000, $0 ;= horizontal mirroring & MMC3 mapper
.byte $00, $00, $00, $00, $00, $00, $00, $00 ;Unused
Here's my config file, where I believe the problem may lie:

Code: Select all

MEMORY {
    RAM_ZP:  start = $00,    size = $100,     type = rw, file = "";
    RAM:     start = $0300,  size = $500,     type = rw, file = "";

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;
    ROM:     start = $8000,  size = $8000,    type = ro, file = %O, fill = yes;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:     load = ROM_HDR, type = ro;
    CODE:       load = ROM, type = ro, start = $8000;
    RODATA:     load = ROM, type = ro;

    DPCM:       load = ROM, type = ro, start = $C000, optional = yes;

    VECTORS:    load = ROM, type = ro, start = $FFFA;
}
Here's the test source (NESICIDE standard sample program):

Code: Select all

.segment "ZEROPAGE"

.segment "HEADER"
.include "hdr.s"

.segment "VECTORS"
.addr nmi, reset, irq

.segment "CODE"
.include "gfx.s"

; we don't use irqs yet
.proc irq
  rti
.endproc

.proc nmi
  rti
.endproc

.proc reset
  sei

  ; Acknowledge and disable interrupt sources during bootup
  ldx #0
  stx PPUCTRL    ; disable vblank NMI
  stx PPUMASK    ; disable rendering (and rendering-triggered mapper IRQ)
  lda #$40
  sta $4017      ; disable frame IRQ
  stx $4010      ; disable DPCM IRQ
  bit PPUSTATUS  ; ack vblank NMI
  bit $4015      ; ack DPCM IRQ
  cld            ; disable decimal mode to help generic 6502 debuggers
                 ; http://magweasel.com/2009/08/29/hidden-messagin/
  dex            ; set up the stack
  txs

  ; Wait for the PPU to warm up (part 1 of 2)
vwait1:
  bit PPUSTATUS
  bpl vwait1

  ; While waiting for the PPU to finish warming up, we have about
  ; 29000 cycles to burn without touching the PPU.  So we have time
  ; to initialize some of RAM to known values.
  ; Ordinarily the "new game" initializes everything that the game
  ; itself needs, so we'll just do zero page and shadow OAM.
  ldy #$00
  lda #$F0
  ldx #$00
clear_zp:
  sty $00,x
  inx
  bne clear_zp
  ; the most basic sound engine possible
  lda #$0F
  sta $4015

  ; Wait for the PPU to warm up (part 2 of 2)
vwait2:
  bit PPUSTATUS
  bpl vwait2

  ; Draw HELLO WORLD text
  jsr drawHelloWorld

  ; Turn screen on
  lda #0
  sta PPUSCROLL
  sta PPUSCROLL
  lda #PPUCTRL_NMI|PPUCTRL_BG1000
  sta PPUCTRL
  lda #PPUMASK_BG
  sta PPUMASK


mainLoop:
  jmp mainLoop
.endproc


.proc cls
  lda #PPUCTRL_NMI
  sta PPUCTRL
  lda #$20
  ldx #$00
  stx PPUMASK
  sta PPUADDR
  stx PPUADDR
  ldx #240
:
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  dex
  bne :-
  ldx #64
  lda #0
:
  sta PPUDATA
  dex
  bne :-
  rts
.endproc

.proc drawHelloWorld
  jsr cls

  ; set monochrome palette
  lda #$3F
  sta PPUADDR
  lda #$00
  sta PPUADDR
  ldx #8
:
  lda #$17
  sta PPUDATA
  lda #$38
  sta PPUDATA
  sta PPUDATA
  sta PPUDATA
  dex
  bne :-

  ; load source and destination addresses
  lda #>helloWorld
  sta 1
  lda #<helloWorld
  sta 0
  lda #$20
  sta 3
  lda #$62
  sta 2
  ; fall through
.endproc
.proc printMsg
dstLo = 2
dstHi = 3
src = 0
  lda dstHi
  sta PPUADDR
  lda dstLo
  sta PPUADDR
  ldy #0
loop:
  lda (src),y
  beq done
  iny
  bne :+
  inc src+1
:
  cmp #10
  beq newline
  sta PPUDATA
  bne loop
newline:
  lda #32
  clc
  adc dstLo
  sta dstLo
  lda #0
  adc dstHi
  sta dstHi
  sta PPUADDR
  lda dstLo
  sta PPUADDR
  jmp loop
done:
  rts
.endproc

.segment "CHRDATA"
helloWorld:
  .byt "HELLO WORLD",0
Finally, here's a visual printout of the memory: https://imgur.com/a/B3pbliO
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: MMC3 Setup Woes

Post by Quietust »

Your config file's MEMORY section says there is only one "ROM" bank of size $8000 (i.e. 32KB), so it's presumably only outputting half of your data. I'm not sure what's the proper way of handling multiple overlapping banks in CA65, but in one of my own projects I basically had one MEMORY entry and one SEGMENTS entry per bank:

Code: Select all

MEMORY {
        ZPAGE:  start = $0000, size = $0100;
        RAM:    start = $0200, size = $0600;
        SRAM:   start = $6000, size = $2000;
        ROM0:   start = $0000, size = $34000, fill = yes;
        ROM1:   start = $8000, size = $4000, fill = yes;
        ROM2:   start = $8000, size = $4000, fill = yes;
        ROM3:   start = $c000, size = $3ffa, fill = yes;
        VECTORS:start = $fffa, size = $0006;
}
SEGMENTS {
    ZEROPAGE: load = ZPAGE,   type = bss;
    RAM:      load = RAM,     type = bss;
    SRAM:     load = SRAM,    type = bss;
    DATA0:    load = ROM0,    type = ro;
    DATA1:    load = ROM1,    type = ro;
    DATA2:    load = ROM2,    type = ro;
    CODE:     load = ROM3,    type = ro;
    VECTORS:  load = VECTORS, type = ro;
}
(in my case, I had a single code bank at $C000-$FFFF and a bunch of data banks at $8000-$BFFF - the "ROM0" segment contained a whole bunch of raw PCM sound samples back-to-back with a table elsewhere to translate the absolute offsets into bank numbers and relative offsets, which is why I defined it with a starting address of $0000 and such a large size)
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
shokwav
Posts: 4
Joined: Fri Jun 04, 2021 1:04 am

Re: MMC3 Setup Woes

Post by shokwav »

Quietust wrote: Fri Jun 04, 2021 8:13 am Your config file's MEMORY section says there is only one "ROM" bank of size $8000 (i.e. 32KB), so it's presumably only outputting half of your data. I'm not sure what's the proper way of handling multiple overlapping banks in CA65, but in one of my own projects I basically had one MEMORY entry and one SEGMENTS entry per bank:

Code: Select all

MEMORY {
        ZPAGE:  start = $0000, size = $0100;
        RAM:    start = $0200, size = $0600;
        SRAM:   start = $6000, size = $2000;
        ROM0:   start = $0000, size = $34000, fill = yes;
        ROM1:   start = $8000, size = $4000, fill = yes;
        ROM2:   start = $8000, size = $4000, fill = yes;
        ROM3:   start = $c000, size = $3ffa, fill = yes;
        VECTORS:start = $fffa, size = $0006;
}
SEGMENTS {
    ZEROPAGE: load = ZPAGE,   type = bss;
    RAM:      load = RAM,     type = bss;
    SRAM:     load = SRAM,    type = bss;
    DATA0:    load = ROM0,    type = ro;
    DATA1:    load = ROM1,    type = ro;
    DATA2:    load = ROM2,    type = ro;
    CODE:     load = ROM3,    type = ro;
    VECTORS:  load = VECTORS, type = ro;
}
(in my case, I had a single code bank at $C000-$FFFF and a bunch of data banks at $8000-$BFFF - the "ROM0" segment contained a whole bunch of raw PCM sound samples back-to-back with a table elsewhere to translate the absolute offsets into bank numbers and relative offsets, which is why I defined it with a starting address of $0000)
That makes sense, so I understand that I need to expand my ROM memory definitions to include all data. I think what I don't fully understand is how the cartridge memory is mapped in relation to the definitions in these config files. I've followed your lead and edited mine, though still no luck with getting the sample program to execute, and the memory visualizer now says that my program contains no memory! I'm quite confused...

Code: Select all

MEMORY {
    RAM_ZP:  start = $00,    size = $100,     type = rw, file = "";
    RAM:     start = $0300,  size = $500,     type = rw, file = "";

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;

    ROM:     start = $0000, size = $64000,    fill = yes;
    ROM0:    start = $8000, size = $2000,     fill = yes;
    ROM1:    start = $A000, size = $2000,     fill = yes;
    ROM2:    start = $C000, size = $2000,     fill = yes;
    ROM3:    start = $E000, size = $2000-$6,  fill = yes;
    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:   load = ROM_HDR, type = ro;
    DATA0:    load = ROM0,    type = ro;
    DATA1:    load = ROM1,    type = ro;
    DATA2:    load = ROM2,    type = ro;
    CODE:     load = ROM3,    type = ro;
    VECTORS:  load = VECTORS, type = ro;
}
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: MMC3 Setup Woes

Post by Quietust »

shokwav wrote: Fri Jun 04, 2021 8:56 am That makes sense, so I understand that I need to expand my ROM memory definitions to include all data. I think what I don't fully understand is how the cartridge memory is mapped in relation to the definitions in these config files. I've followed your lead and edited mine, though still no luck with getting the sample program to execute, and the memory visualizer now says that my program contains no memory! I'm quite confused...

Code: Select all

MEMORY {
    RAM_ZP:  start = $00,    size = $100,     type = rw, file = "";
    RAM:     start = $0300,  size = $500,     type = rw, file = "";

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;

    ROM:     start = $0000, size = $64000,    fill = yes;
    ROM0:    start = $8000, size = $2000,     fill = yes;
    ROM1:    start = $A000, size = $2000,     fill = yes;
    ROM2:    start = $C000, size = $2000,     fill = yes;
    ROM3:    start = $E000, size = $2000-$6,  fill = yes;
    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:   load = ROM_HDR, type = ro;
    DATA0:    load = ROM0,    type = ro;
    DATA1:    load = ROM1,    type = ro;
    DATA2:    load = ROM2,    type = ro;
    CODE:     load = ROM3,    type = ro;
    VECTORS:  load = VECTORS, type = ro;
}
My configuration was specific to the program I wrote, and I wasn't suggesting that you actually use it - indeed, your new configuration is demanding 432KB of PRG ROM (which isn't even a power of 2), which probably explains the different error you're now getting.

In order to make a proper configuration, you need to know exactly how many banks you're going to have, what their sizes are, where they're going to be mapped, and what you're going to be putting in them.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
shokwav
Posts: 4
Joined: Fri Jun 04, 2021 1:04 am

Re: MMC3 Setup Woes

Post by shokwav »

Quietust wrote: Fri Jun 04, 2021 9:03 am
shokwav wrote: Fri Jun 04, 2021 8:56 am That makes sense, so I understand that I need to expand my ROM memory definitions to include all data. I think what I don't fully understand is how the cartridge memory is mapped in relation to the definitions in these config files. I've followed your lead and edited mine, though still no luck with getting the sample program to execute, and the memory visualizer now says that my program contains no memory! I'm quite confused...

Code: Select all

MEMORY {
    RAM_ZP:  start = $00,    size = $100,     type = rw, file = "";
    RAM:     start = $0300,  size = $500,     type = rw, file = "";

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;

    ROM:     start = $0000, size = $64000,    fill = yes;
    ROM0:    start = $8000, size = $2000,     fill = yes;
    ROM1:    start = $A000, size = $2000,     fill = yes;
    ROM2:    start = $C000, size = $2000,     fill = yes;
    ROM3:    start = $E000, size = $2000-$6,  fill = yes;
    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:   load = ROM_HDR, type = ro;
    DATA0:    load = ROM0,    type = ro;
    DATA1:    load = ROM1,    type = ro;
    DATA2:    load = ROM2,    type = ro;
    CODE:     load = ROM3,    type = ro;
    VECTORS:  load = VECTORS, type = ro;
}
My configuration was specific to the program I wrote, and I wasn't suggesting that you actually use it - indeed, your new configuration is demanding 432KB of PRG ROM (which isn't even a power of 2), which probably explains the different error you're now getting.

In order to make a proper configuration, you need to know exactly how many banks you're going to have, what their sizes are, where they're going to be mapped, and what you're going to be putting in them.
I suppose I don't fully understand how these config files work. Here's what I am looking for:
64 KB total PRG ROM
4 banks, in MMC3 format (8 KB each):
Bank 0 @ $8000-$9FFF
Bank 1 @ $A000-$BFFF
Bank 2 @ $C000-$DFFF
Bank 3 @ $E000-$FFFF
Reset function in last bank ($E000)

I have continued to edit my config file:

Code: Select all

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;

    ROM:     start = $0000, size = $64000 - ($2000*$4),  fill = yes;
    ROM_BANK0:    start = $8000, size = $2000,     		 fill = yes;
    ROM_BANK1:    start = $A000, size = $2000,     		 fill = yes;
    ROM_BANK2:    start = $C000, size = $2000,     		 fill = yes;
    ROM_BANK3:    start = $E000, size = $2000-$6,  		 fill = yes;

    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:   load = ROM_HDR, type = ro;

    CODE:    load = ROM_BANK3,    type = ro;

    VECTORS:  load = VECTORS, type = ro;
}
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: MMC3 Setup Woes

Post by Quietust »

shokwav wrote: Fri Jun 04, 2021 9:24 am I suppose I don't fully understand how these config files work. Here's what I am looking for:
64 KB total PRG ROM
4 banks, in MMC3 format (8 KB each):
Bank 0 @ $8000-$9FFF
Bank 1 @ $A000-$BFFF
Bank 2 @ $C000-$DFFF
Bank 3 @ $E000-$FFFF
Reset function in last bank ($E000)
Those numbers don't add up - four 8KB banks is only 32KB, not the 64KB you say you want to use.

Perhaps I should clarify by saying that the "banks" are not the memory ranges - rather, they are the blocks of data which are inserted into those memory ranges. Thus, if you have 64KB of PRG ROM, you will have exactly 6 banks located at either $8000-$9FFF* or $A000-$BFFF, one bank located at $C000-$DFFF*, and one bank located at $E000-$FFFF containing your reset code.

(* if you're using $C000-$DFFF for DPCM, you'll probably be putting the 2nd-last bank at $8000-$9FFF instead)
shokwav wrote: Fri Jun 04, 2021 9:24 am I have continued to edit my config file:

Code: Select all

    ROM_HDR: start = $0000,  size = $10,      type = ro, file = %O, fill = yes;

    ROM:     start = $0000, size = $64000 - ($2000*$4),  fill = yes;
    ROM_BANK0:    start = $8000, size = $2000,     		 fill = yes;
    ROM_BANK1:    start = $A000, size = $2000,     		 fill = yes;
    ROM_BANK2:    start = $C000, size = $2000,     		 fill = yes;
    ROM_BANK3:    start = $E000, size = $2000-$6,  		 fill = yes;

    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000,    type = ro, file = %O, fill = yes;
At the very least, you need to get rid of that "ROM" bank, since it's totally meaningless (especially the "$64000" size, which is not 64KB but 400KB) - the only reason my own configuration file had a line like that is because I had 13 consecutive banks which I was storing within a single segment in order to simplify how I accessed them (i.e. so data could flow freely between banks).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
shokwav
Posts: 4
Joined: Fri Jun 04, 2021 1:04 am

Re: MMC3 Setup Woes

Post by shokwav »

Ahhh I think I finally understand thanks to you! I have followed what you said about bank placement and have created the following config file:

Code: Select all

MEMORY {
    RAM_ZP:  start = $00,    size = $100,     type = rw, file = "";
    RAM:     start = $0300,  size = $500,     type = rw, file = "";

    ROM_HDR: start = $0000,  size = $10,   type = ro, file = %O, fill = yes;
    ROM_BANK0:    start = $8000, size = $2000,     		 fill = yes;
    ROM_BANK1:    start = $8000, size = $2000,     		 fill = yes;
    ROM_BANK2:    start = $8000, size = $2000,     		 fill = yes;
    ROM_BANK3:    start = $A000, size = $2000,  		 fill = yes;
    ROM_BANK4:    start = $A000, size = $2000,     		 fill = yes;
    ROM_BANK5:    start = $A000, size = $2000,     		 fill = yes;

    ROM_BANK6:    start = $C000, size = $2000,     		 fill = yes;
    ROM_BANK7:    start = $E000, size = $2000-$6,  		 fill = yes;
    VECTORS: start = $FFFA, size = $6;

    CHR:     start = $0000,  size = $2000, type = ro, file = %O, fill = yes;
}

SEGMENTS {
    CHRDATA:    load = CHR, type = ro;

    ZEROPAGE:   load = RAM_ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    BSSPERS:    load = RAM, type = bss, start = $0700, optional = yes;

    HEADER:   load = ROM_HDR, type = ro;

    CODE:    load = ROM_BANK7,    type = ro;

    VECTORS:  load = VECTORS, type = ro;
}
Which runs the test program flawlessly, and when viewed through the memory editor it seems that everything is in the correct bank! Thank you for the help! I've lost sleep trying to figure this out lol.
Post Reply