Bankswitching UNROM - always bank 2 loaded, won't switch

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#185989)
Hello!

For some reason I cannot get any bank other then bank 02 to load (in a 4 bank UNROM).

I've tried reducing it down to the bare essentials, and have had a look at the very helpful sample project nathan posted (thanks!) but I still can't figure out what is wrong.

I'm using CHRAM, and testing it by storing the tiles in different banks before loading them in to the ram. If I load from bank 2, it works fine. If I try any other bank, it does nothing. Switching banks appears to do nothing.

My config file looks like this (I've only recently added the ONCE segment, as I added initlib to my reset.s file):

Code:
MEMORY {

   ZP: start = $00, size = $100, type = rw, define = yes;

   OAM1: start = $0200, size = $0100, define = yes;

   RAM: start = $0400, size = $0500, define = yes;

   HEADER: start = $0, size = $10, file = %O ,fill = yes;

   PRG0: start = $8000, size = $4000, file = %O, fill = yes, define = yes;
   PRG1: start = $8000, size = $4000, file = %O, fill = yes, define = yes;
   PRG2: start = $8000, size = $4000, file = %O, fill = yes, define = yes;

   PRG3: start = $c000, size = $3ffa, file = %O ,fill = yes, define = yes;
   
   VECTORS: start = $fffa, size = $6, file = %O, fill = yes;
   
   #had to add this to get initlib to work
   ONCE: start = $0000, size = $0000;
   
}


SEGMENTS {
    HEADER:   load = HEADER,         type = ro;
   ROM0:      load = PRG0,   type = ro,   define = yes;
   ROM1:      load = PRG1,   type = ro,   define = yes;
   ROM2:      load = PRG2,   type = ro,   define = yes;
    STARTUP:  load = PRG3,            type = ro,  define = yes;
    LOWCODE:  load = PRG3,            type = ro,                optional = yes;
    INIT:     load = PRG3,            type = ro,  define = yes, optional = yes;
    CODE:     load = PRG3,            type = ro,  define = yes;
    RODATA:   load = PRG3,            type = ro,  define = yes;
    DATA:     load = PRG3, run = RAM, type = rw,  define = yes;
    VECTORS:  load = VECTORS,        type = rw;
    BSS:      load = RAM,            type = bss, define = yes;
    HEAP:     load = RAM,            type = bss, optional = yes;
    ZEROPAGE: load = ZP,             type = zp;
   OAM:     load = OAM1,          type = bss, define = yes;
   ONCE: load=PRG3, type=ro, optional=yes;
}

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 {
    __STACK_SIZE__: type = weak, value = $0400;   
   __STACK_START__: type = weak, value = $500;
}


The only clue I've been able to uncover is that if I swap the order of the PRG bank declarations (so I declare them PRG2, PRG1, PRG0 for instance) the program does not seem to automatically load PRG2.

My startup code looks like this (I've removed a bunch of functions to make it clearer - things like vblanks, etc, and the mytiles code which is just the code from the wiki):

Code:
.import _main
.export __STARTUP__:absolute=1, _vblankwait, _get_input, _blanksprite, _bankswitch_a, _bankswitch_nosave
.importzp _NMI_flag, _Frame_Count, _joypad1, _joypad1old, _joypad1test, _joypad2, _joypad2old, _joypad2test, _current_bank
.import initlib,push0,popa,popax,_main,zerobss,copydata

.import __STACK_START__, __STACK_SIZE__
.include "zeropage.inc"


PPUMASK = $2001
PPUADDR = $2006
PPUDATA = $2007
DMC_FREQ   =$4010   ;copied from dougs tutorial


.segment "ZEROPAGE"

current_bank: .res 1

.segment "HEADER"

    .byte $4e,$45,$53,$1a
   .byte 04
   .byte 00
   .byte 02
   .byte 02
   .res 8,0

   
.segment "STARTUP"

reset:
   sei
   cld
   ldx   #$40
   stx   $4017
   ldx   #$ff
   txs   
   inx
   stx   $2000 ; disable NMI
   stx   $2001 ; disable rendering
   stx   $4010 ; disable DMC IRQs
   
   jsr _vblankwait
   
   ;ppu wont stablise for around 30,000 cycles, so may as well clear ram
clear_memory:
   lda #$00
   sta $0000, x
   sta $0100, x
   sta $0200, x
   sta $0300, x
   sta $0400, x
   sta $0500, x
   sta $0600, x
   sta $0700, x
   
   lda #$fe
   sta $0200, x  ;this moves the sprites off screen
   
   inx
   bne clear_memory
   
   jsr _vblankwait
   
   ;ppu should be stable now. copy tiles in.
   lda #$01
   jsr _bankswitch_a
   jsr _copy_mytiles_chr
   
   jsr   zerobss
   jsr   copydata
   
   lda #<(__STACK_START__+__STACK_SIZE__)
    sta   sp
    lda   #>(__STACK_START__+__STACK_SIZE__)
    sta   sp+1            ; Set the c stack pointer

   jsr   initlib
   
   lda $2002
   
   jmp _main


.segment "CODE"

   
_bankswitch_a:
  tay
  sty _current_bank  ; save the current bank in RAM so the NMI handler can restore it
_bankswitch_nosave:
  lda banktable, y      ; read a byte from the banktable
  sta banktable, y      ; and write it back, switching banks
  rts      
   

.segment "ROM0"

.segment "ROM1"

.segment "ROM2"
mytiles_chr: .incbin "tiles.chr"
   
.segment "RODATA"

banktable:              ; Write to this table to switch banks.
  .byte $00, $01, $02, $03, $04, $05, $06

.segment "VECTORS"

   .word nmi
   .word reset
   .word irq


Even though I call lda #$01, the program still seems to have bank 2 attached, as it loads the tiles in to the chr-ram from there. Is there anything obviously wrong with this?
Re: Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#185992)
Are you sure this is UNROM? Your NES header says it's mapper 0, and has some very weird other settings (are you really making something for the PC10 and using battery-backed PRG-RAM?). https://wiki.nesdev.com/w/index.php/INES

Code:
Byte 6 = $02 = %00000010
                ||||||||
                ||||+||+- 0xx0: vertical arrangement/horizontal mirroring (CIRAM A10 = PPU A11)
                |||| ||   0xx1: horizontal arrangement/vertical mirroring (CIRAM A10 = PPU A10)
                |||| ||   1xxx: four-screen VRAM
                |||| |+-- 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
                |||| +--- 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
                ++++----- Lower nybble of mapper number

Byte 7 = $02 = %00000010
                ||||||||
                |||||||+- VS Unisystem
                ||||||+-- PlayChoice-10 (8KB of Hint Screen data stored after CHR data)
                ||||++--- If equal to 2, flags 8-15 are in NES 2.0 format
                ++++----- Upper nybble of mapper number

Edit: whoever depicted the file format in that way (with said ASCII layout) did a horrible, horrible job. Shame on whoever denoted it like that (specifically bits 3-0 of byte 6).
Re: Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#185993)
If you have four 16384 banks of PRG ROM, your header should be
Code:
.byte "NES", $1A, 4, 0, $20, $00


Or this macro pack for ca65 should make mistakes less likely:
Code:
.include "nes2header.inc"
nes2mapper 2
nes2prg 65536
nes2chrram 8192
nes2mirror 'H'
nes2tv 'N'
nes2end
Re: Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#185996)
Ah Genius(es)! I will correct the header and see where that gets me to.

Thank you!
Re: Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#185997)
No prob.

I've also changed that wiki page to reflect things more clearly.
Re: Bankswitching UNROM - always bank 2 loaded, won't switch
by on (#186289)
Also, you may need to ensure that your writes to ROM have data there which matches the values you write. Otherwise emulators like FCEUX will treat it as a bus conflict and refuse to write the value correctly.