split/wrapping backgrounds in Marble Madness and Battletoads

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
split/wrapping backgrounds in Marble Madness and Battletoads
by on (#68426)
sorry for all the threads in a short time. didn't want to throw this in the unrelated APU thread.

when i play Marble Madness and Battletoads in my emulator, they both have a similar problem. in MM, the background seems to revert to a scroll value of zero 3/4 of the way down the screen. sprites are also not being shown below this point. other than this, it seems to play okay.

in BT, sprites all show just fine everywhere but the background starts too low at the top of the frame and then wraps around. i have a feeling they're both PPU frame timing. in blargg's VBL basics rom i fail with "VBL period is way off" but from what i know, i thought i was doing it right and am not sure where to go from here. might be some stupid oversight. probably is.

Image

Image

this is the meat of my main frame loop. it passes the VBL clear flag tests after tonight. i did some fiddling with it moving the clear point around inside of scanline 261.

Code:
    memcpy(&SpriteBuffer[0], &SpriteRAM[0], 256);
    savedhscroll = hscroll; savedvscroll = vscroll;
    for (scanline=0; scanline<240; scanline++) {
      renderscanline(scanline); exec6502(28);
      totalscanlines++;
    }

    PPUdata.vblank = 1;
    if (PPUdata.nmivblank) nmi6502();
    for (scanline=240; scanline<261; scanline++) {     
      exec6502(113);
      totalscanlines++;
    }
    exec6502(70);
    PPUdata.vblank = 0;
    PPUdata.spr0hit = 0;
    if (totalframes&1) exec6502(44); else exec6502(43);


and this is the renderscanline function:

Code:
void renderscanline(WORD scanline) {
     WORD tmpx;
     for (tmpx=0; tmpx<256; tmpx++) { sprback[tmpx] = 0; backgnd[tmpx] = 0; sprfront[tmpx] = 0; spr0collide[tmpx] = 0; }
     if (PPUdata.sprvisible) drawsprites(scanline, 1);
     if (PPUdata.bgvisible) drawbackground(scanline);
     if (PPUdata.sprvisible) drawsprites(scanline, 0);
     for (tmpx=0; tmpx<256; tmpx++) {
       if (tmpx==hit0x && scanline==hit0y) PPUdata.spr0hit = 1;
       if ((tmpx%3)==0) exec6502(1);
       if (sprback[tmpx]==0) outputNES[scanline][tmpx] = PPUread(0x3F00);
         else outputNES[scanline][tmpx] = PPUread(sprback[tmpx]);
       if (backgnd[tmpx]>0) {
         outputNES[scanline][tmpx] = PPUread(backgnd[tmpx]);
         if (spr0collide[tmpx]==1 && tmpx<255) { hit0x = tmpx; hit0y = scanline+1; }
       }
       if (sprfront[tmpx]>0) outputNES[scanline][tmpx] = PPUread(sprfront[tmpx]);
     }
}


i have not run into any issues remotely like this in any other games. in fact, everything else i run plays more or less flawlessly. (with a small handful of exceptions)

one other thing that kind of bugs me is the status bar in SMB1 tends to flicker often. not sure if it's related.

oh and for the life of me i CANNOT figure out why i fail blargg's NMI control test ROM with "Should occur when enabled and VBL begins"

........ because it certainly looks to me like it occurs when enabled and VBL begins, unless it's saying that because of some other prerequisite the test relies on but doesnt check for isn't being met? am i beginning vblank in the wrong place?

by on (#68428)
I think Battletoads needs you to support mid-frame loopy_t/loopy_v modifications and may require fairly precise timing of these updates. Battletoads is known to be a picky game about timing. The second level commonly freezes up when the sprite hit zero fails due to some sort of timing error causing the background not to be in exactly the right place at the beginning of the frame.

So I'd check your timing and the loopy_t/loopy_v (relates to $2000, $2005, $2006). See Loopy's document on scrolling.

by on (#68430)
- Unless you're using a timestamp-thing, the cpuexecute(cycles) seems to be a waste of accuracy.
Re: split/wrapping backgrounds in Marble Madness and Battlet
by on (#68432)
miker00lz wrote:
Code:
exec6502(113);

Your timing appears to be really off. A scanline has 113.666666... CPU cycles, did you just round it down to 113? After 21 scanlines that error adds up to 14 whole CPU cycles. I'm not sure about Marble Madness, but Battletoads is known for needing accurate timing, so you have to make your emulator better in that department.

In this post Disch suggests an interesting way to deal with the fractional numbers, which even makes it easier to emulate both NTSC and PAL systems. He basically scales all the numbers up until there are no fractions.

by on (#68441)
- Like I said, unless there's a good timestamp system for PPU/APU/whatver read/write, cpuexec(cycles) is a waste. Assuming only NTSC will be emulated, yes, 1 NTSC CPU cycle = 3 PPU cycles.

- In my emulator, that means for each CPU cycle, the PPU burns 3 cycles, meaning 3 dots. On other hand, as blargg have mentioned, it's better to have something working good firstly.

by on (#68456)
hey thanks for all the helpful responses. this place is win.

i've completely redone how my code times frames now... here is a log i had it write to a file of the ticks per frame it ran for the first few seconds. not PERFECT but it's getting very very close now:

Code:
Frame #0: 29833
Frame #1: 29827
Frame #2: 29832
Frame #3: 29832
Frame #4: 29828
Frame #5: 29834
Frame #6: 29833
Frame #7: 29830
Frame #8: 29830
Frame #9: 29834
Frame #10: 29831
Frame #11: 29834
Frame #12: 29831
Frame #13: 29834
Frame #14: 29831
Frame #15: 29834
Frame #16: 29831
Frame #17: 29834
Frame #18: 29831
Frame #19: 29834
Frame #20: 29831
Frame #21: 29834
Frame #22: 29831
Frame #23: 29834
Frame #24: 29832
Frame #25: 29832
Frame #26: 29832
Frame #27: 29832
Frame #28: 29829
Frame #29: 29835
Frame #30: 29829
Frame #31: 29829
Frame #32: 29835
Frame #33: 29829
Frame #34: 29833
Frame #35: 29833
Frame #36: 29833
Frame #37: 29833
Frame #38: 29833
Frame #39: 29833
Frame #40: 29833
Frame #41: 29833
Frame #42: 29833
Frame #43: 29833
Frame #44: 29831
Frame #45: 29832
Frame #46: 29832
Frame #47: 29832
Frame #48: 29832
Frame #49: 29832
Frame #50: 29832
Frame #51: 29832
Frame #52: 29832
Frame #53: 29832
Frame #54: 29832
Frame #55: 29832
Frame #56: 29832
Frame #57: 29832
Frame #58: 29832
Frame #59: 29832
Frame #60: 29833
Frame #61: 29833
Frame #62: 29833
Frame #63: 29829
Frame #64: 29829
Frame #65: 29833
Frame #66: 29834
Frame #67: 29838
Frame #68: 29834
Frame #69: 29836
Frame #70: 29834
Frame #71: 29838
Frame #72: 29834
Frame #73: 29836
Frame #74: 29834
Frame #75: 29838
Frame #76: 29834
Frame #77: 29836
Frame #78: 29832
Frame #79: 29837
Frame #80: 29832
Frame #81: 29834
Frame #82: 29834
Frame #83: 29831
Frame #84: 29828
Frame #85: 29830
Frame #86: 29830
Frame #87: 29831
Frame #88: 29831
Frame #89: 29831
Frame #90: 29831
Frame #91: 29833
Frame #92: 29830
Frame #93: 29832
Frame #94: 29828
Frame #95: 29831
Frame #96: 29830
Frame #97: 29830
Frame #98: 29830
Frame #99: 29830
Frame #100: 29830
Frame #101: 29830
Frame #102: 29830
Frame #103: 29830
Frame #104: 29830
Frame #105: 29830
Frame #106: 29830
Frame #107: 29834
Frame #108: 29831
Frame #109: 29830
Frame #110: 29833
Frame #111: 29834
Frame #112: 29833
Frame #113: 29838
Frame #114: 29830
Frame #115: 29834
Frame #116: 29833
Frame #117: 29838
Frame #118: 29830
Frame #119: 29834
Frame #120: 29833
Frame #121: 29838
Frame #122: 29830
Frame #123: 29834
Frame #124: 29833
Frame #125: 29838
Frame #126: 29832
Frame #127: 29831
Frame #128: 29830
Frame #129: 29830
Frame #130: 29830
Frame #131: 29837
Frame #132: 29832
Frame #133: 29830
Frame #134: 29832
Frame #135: 29828
Frame #136: 29828
Frame #137: 29828
Frame #138: 29828
Frame #139: 29828
Frame #140: 29828
Frame #141: 29830
Frame #142: 29828
Frame #143: 29829
Frame #144: 29834
Frame #145: 29831
Frame #146: 29831
Frame #147: 29831
Frame #148: 29831
Frame #149: 29831
Frame #150: 29831
Frame #151: 29833
Frame #152: 29830
Frame #153: 29832
Frame #154: 29830
Frame #155: 29832
Frame #156: 29831
Frame #157: 29834
Frame #158: 29828
Frame #159: 29832
Frame #160: 29832
Frame #161: 29832
Frame #162: 29832
Frame #163: 29836
Frame #164: 29832
Frame #165: 29830
Frame #166: 29832
Frame #167: 29830
Frame #168: 29832
Frame #169: 29828
Frame #170: 29828
Frame #171: 29828
Frame #172: 29828
Frame #173: 29828
Frame #174: 29828
Frame #175: 29830
Frame #176: 29829
Frame #177: 29830
Frame #178: 29829
Frame #179: 29830
Frame #180: 29829
Frame #181: 29830


do i need to be even more accurate than this? :)

i wouldn't think that's possible because of some tick counts of certain CPU instructions can be too large to get more accurate. i think? but i'm still having the exact same issues in those 2 games.

should i upload my entire source code?

by on (#68459)
The Battletoads screenshot looks like a mirroring problem.

by on (#68461)
tepples wrote:
The Battletoads screenshot looks like a mirroring problem.


huh, i'll check my mirroring code for it's mapper. that might be right. as far as marble madness, if i compile it with a quick hack to not have the PPU check for disabled BG and sprite rendering, and lock hscroll and vscroll to temp varibles at the beginning of the frame and have the PPU use those - the game plays perfectly. that's not acceptable though, i need to figure out what's causing this.

by on (#68465)
here's a short video of it playing battletoads actually. there are other glitches before the gameplay starts.

http://www.youtube.com/watch?v=6mHQxn40718

by on (#68466)
- Right, it's really a mirroring problem. I don't remember by heart of what mirroring type Battletoads uses though. -_-;;

by on (#68467)
also, blargg's 01-vbl_basics.nes ROM still reports "VBL period is way off" even though this is what the frame CPU cycle count along with what tick in the frame the VBI occured at:

Code:
Frame #0: 29831 (VBI at 27281)
Frame #1: 29829 (VBI at 27282)
Frame #2: 29828 (VBI at 27280)
Frame #3: 29836 (VBI at 27279)
Frame #4: 29828 (VBI at 27282)
Frame #5: 29830 (VBI at 27280)
Frame #6: 29834 (VBI at 27278)
Frame #7: 29832 (VBI at 27280)
Frame #8: 29837 (VBI at 27278)
Frame #9: 29828 (VBI at 27282)
Frame #10: 29835 (VBI at 27278)


is the VBI trigger point wrong?

by on (#68468)
Zepper wrote:
- Right, it's really a mirroring problem. I don't remember by heart of what mirroring type Battletoads uses though. -_-;;

IIRC it uses 1-screen miroring. It uses one of those AxROM boards with a name table selection bit, isn't it?

by on (#68469)
Zepper wrote:
- Right, it's really a mirroring problem. I don't remember by heart of what mirroring type Battletoads uses though. -_-;;


it uses horizontal, but when i turn on my debug overlay while playing it the mirroring is remaining horizontal as it should. it almost looks to me like the game is just setting the incorrect vscroll value for some reason.

by on (#68470)
tokumaru wrote:
Zepper wrote:
- Right, it's really a mirroring problem. I don't remember by heart of what mirroring type Battletoads uses though. -_-;;

IIRC it uses 1-screen miroring.


oh, the mapper list at tuxnes.sourceforge.net says horizontal but it could easily be wrong. i'm going to make sure i'm reading the iNES header properly.

by on (#68471)
miker00lz wrote:
it uses horizontal

If you got that from the header, don't trust it. That information is useless in games with mapper controlled mirroring.

by on (#68472)
Yup, Battletoads is AOROM. If you read about this mapper here, you'll see that it has a bit that selects which name table gets displayed.

The mirroring information in the iNES header doesn't mean anything for games with mapper-controlled mirroring, just ignore it.

by on (#68473)
tokumaru wrote:
miker00lz wrote:
it uses horizontal

If you got that from the header, don't trust it. That information is useless in games with mapper controlled mirroring.


ah, you're right. this is the register for mapper 7:

Code:
  $8000-FFFF:  [...M .PPP]
    M = Mirroring:
        0 = 1ScA
        1 = 1ScB

    P = PRG Reg  (only 2 bits wide on AMROM/ANROM)


the other mapper 7 doc i read when putting my code together i must have not understood properly. i thought it was saying if the bit is on, it's 1-screen at 0x2000, otherwise i should use the mirroring layout specified by the header.

it now looks like this:

Code:
       case 7:
            if (addr>=0x8000) {
              prgbank1 = (value&0xF)<<2;
              prgbank2 = prgbank1 + 1;
              prgbank3 = prgbank1 + 2;
              prgbank4 = prgbank1 + 3;             
              if ((value>>4)&1) {
                nametablemap[0] = 0x2400;
                nametablemap[1] = 0x2400;
                nametablemap[2] = 0x2400;
                nametablemap[3] = 0x2400;                               
              } else {
                nametablemap[0] = 0x2000;
                nametablemap[1] = 0x2000;
                nametablemap[2] = 0x2000;
                nametablemap[3] = 0x2000;
              }
              return;             


but it's still doing the same, ALTHOUGH it did fix the glitch on the screen where their ship is flying down to the planet in the intro. that part looks right now. the rest? still glitched the same.

is that code not correct?

by on (#68479)
How are you handling nametablemap in whatever handles writes to $2007?

by on (#68485)
miker00lz wrote:
also, blargg's 01-vbl_basics.nes ROM still reports "VBL period is way off" even though this is what the frame CPU cycle count along with what tick in the frame the VBI occured at:

Code:
Frame #0: 29831 (VBI at 27281)
Frame #1: 29829 (VBI at 27282)
Frame #2: 29828 (VBI at 27280)
Frame #3: 29836 (VBI at 27279)
Frame #4: 29828 (VBI at 27282)
Frame #5: 29830 (VBI at 27280)
Frame #6: 29834 (VBI at 27278)
Frame #7: 29832 (VBI at 27280)
Frame #8: 29837 (VBI at 27278)
Frame #9: 29828 (VBI at 27282)
Frame #10: 29835 (VBI at 27278)


is the VBI trigger point wrong?


- It's not a matter of cycles, but where in the current instruction (or one before) the VBlank flag has raised. AFAIK, your frame timings aren't much helpful though.

by on (#68499)
tepples wrote:
How are you handling nametablemap in whatever handles writes to $2007?


well, this is my code to handle PPU reg writes:

Code:
void PPUwritereg(WORD addr, WORD value) {
     value = value % 256;
     #ifdef DEBUGMODE
       printf("PPU register write: %x <- %u\n", addr, value);
     #endif
     PPUregs[addr-0x2000] = value;
     switch (addr) {
       case 0x2000:
            if (value&128) PPUdata.nmivblank = 1; else PPUdata.nmivblank = 0;
            if (value&32) PPUdata.sprsize = 16; else PPUdata.sprsize = 8;
            if (value&16) PPUdata.bgtable = 0x1000; else PPUdata.bgtable = 0x0000;
            if (value&8) PPUdata.sprtable = 0x1000; else PPUdata.sprtable = 0x0000;
            if (value&4) PPUdata.addrinc = 32; else PPUdata.addrinc = 1;
            PPUdata.nametable = value&3;
            break;
       case 0x2001:
            if (value&16) PPUdata.sprvisible = 1; else PPUdata.sprvisible = 0;
            if (value&8) PPUdata.bgvisible = 1; else PPUdata.bgvisible = 0;
            if (value&4) PPUdata.sprclip = 1; else PPUdata.sprclip = 0;
            if (value&2) PPUdata.bgclip = 1; else PPUdata.bgclip = 0;
            break;
       case 0x2002:
           PPUwrite(VRAMaddr, value);
            break;
       case 0x2003:
            SpriteAddr = value;
            break;
       case 0x2004:
            SpriteRAM[SpriteAddr] = value;
            SpriteAddr = (SpriteAddr+1) % 256;
            break;
       case 0x2005:
            if (ScrollLatch) {
              hscroll = value;
              ScrollLatch = 0;
              break;
            } else {
              vscroll = value;
              ScrollLatch = 1;
              break;
            }
       case 0x2006:
            if (PPULatch) {
              VRAMaddr = (VRAMaddr & 0xFF) + (value<<8);
              PPULatch = 0;
              break;
            } else {
              VRAMaddr = (VRAMaddr & 0xFF00) + value;
              PPULatch = 1;
              break;
            }
       case 0x2007:
            PPUwrite(VRAMaddr, value);
            VRAMaddr = (VRAMaddr + PPUdata.addrinc) % 16384;
            break;
     }
}




and reg reads:

Code:
WORD PPUreadreg(WORD addr) {
     WORD tmpword;
     switch (addr) {
       case 0x2002:
            tmpword = (PPUdata.vblank*128)+(PPUdata.spr0hit*64)+(PPUdata.sprcount*32)+(PPUdata.vramwrite*16)+(PPUregs[2]&15);
            PPUdata.vblank = 0;
            PPULatch = 1;
            ScrollLatch = 1;
            if (tracelog==1) printf("PPU register read: %x (value is %x)\n", addr, tmpword);
            return tmpword;
       case 0x2004:
            return SpriteRAM[SpriteAddr];
       case 0x2007:
            tmpword = PPUread(VRAMaddr);
            VRAMaddr = (VRAMaddr + PPUdata.addrinc) % 16384;
            return tmpword;
     }
     return PPUregs[addr-0x2000];
}



the name table address mapping is taken care of in the general PPU write function:

Code:
void PPUwrite(WORD addr, WORD value){
  value = value % 256;
  if (addr>=0x3F00 && addr<=0x3FFF) addr = (addr&0x3F)+0x3F00;
  if (addr==0x3F00 || addr==0x3F10) { VRAM[0x3F00] = value; VRAM[0x3F10] = value; return; }
  if (addr==0x3F04 || addr==0x3F14) { VRAM[0x3F04] = value; VRAM[0x3F14] = value; return; }
  if (addr==0x3F08 || addr==0x3F18) { VRAM[0x3F08] = value; VRAM[0x3F18] = value; return; }
  if (addr==0x3F0C || addr==0x3F1C) { VRAM[0x3F0C] = value; VRAM[0x3F1C] = value; return; }
  if (addr>0x2FFF && addr<0x3F00) addr = (addr&0xEFF) + 0x3000;
  if (addr>0x3FFF) addr = addr - 0x4000;
  if (addr>=0x2000 && addr<0x2400) addr = (addr - 0x2000) + nametablemap[0];
    else if (addr>=0x2400 && addr<0x2800) addr = (addr - 0x2400) + nametablemap[1];
      else if (addr>=0x2800 && addr<0x2C00) addr = (addr - 0x2800) + nametablemap[2];
        else if (addr>=0x2C00 && addr<0x3000) addr = (addr - 0x2C00) + nametablemap[3];
  VRAM[addr] = value;
  if (addr<0x2000 && ROM.chrcount==0) makeCHRcache(addr>>4);
}



and here's the how the background rendering function reads the NT data:

Code:
        if (x>=st) {
         switch (PPUdata.nametable) {
           case 0: calcx = x; calcy = scanline; break;
           case 1: calcx = x + 256; calcy = scanline; break;
           case 2: calcx = x; calcy = scanline + 240; break;
           case 3: calcx = x + 256; calcy = scanline + 240; break;
         }
         calcx = (calcx + hscroll) % 512;
         calcy = (calcy + vscroll) % 480;
          attribbyte = attriblookup[calcx>>5][calcy>>5];
         if (calcx<256 && calcy<240) usent = 0;
         if (calcx>255 && calcy<240) usent = 1;
         if (calcx<256 && calcy>239) usent = 2;
         if (calcx>255 && calcy>239) usent = 3;
         calcx = calcx % 256; calcy = calcy % 240;
         ntval = nametablemap[usent] + ((calcy>>3)<<5) + (calcx>>3);
         if (PPUdata.bgtable==0) ntval = PPUread(ntval); else ntval = PPUread(ntval) + 256;
         curpixel = CHRcache[ntval][calcx%8][calcy%8];
         if ((curpixel&3)>0) {
           attribx = calcx>>5; attriby = calcy>>5;
           attriboffset = (attriby<<3) + attribx;
           attribbyte = PPUread(nametablemap[usent] + 0x3C0 + attriboffset);
           xmod = (calcx%32); ymod = (calcy%32);
           if (xmod<=15 && ymod<=15) attribbyte = attribbyte&0x3;
           if (xmod>=16 && ymod<=15) attribbyte = (attribbyte&0xC)>>2;
           if (xmod<=15 && ymod>=16) attribbyte = (attribbyte&0x30)>>4;
           if (xmod>=16 && ymod>=16) attribbyte = (attribbyte&0xC0)>>6;
           backgnd[x] = 0x3F00 + curpixel + (attribbyte<<2);
          }
         }

by on (#68505)
Your "PPULatch" and "ScrollLatch" should be the same variable. There isn't two of them operating independently.

As stated, Battletoads uses "One Screen Mirroring". This means all 4 nametables in PPU space point to the same memory. Battletoads can and does change which nametable its drawing from and will do so mid-frame. So you need to have your "Latch" behavior correct.

Also, your implementation of $2000 and $2005 doesn't look accurate. It should work for alot of games or most depending on how you render, but it won't work for all. Refer to Loopy's document on scrolling for why.

by on (#68506)
Zepper wrote:
miker00lz wrote:
also, blargg's 01-vbl_basics.nes ROM still reports "VBL period is way off" even though this is what the frame CPU cycle count along with what tick in the frame the VBI occured at:

Code:
Frame #0: 29831 (VBI at 27281)
Frame #1: 29829 (VBI at 27282)
Frame #2: 29828 (VBI at 27280)
Frame #3: 29836 (VBI at 27279)
Frame #4: 29828 (VBI at 27282)
Frame #5: 29830 (VBI at 27280)
Frame #6: 29834 (VBI at 27278)
Frame #7: 29832 (VBI at 27280)
Frame #8: 29837 (VBI at 27278)
Frame #9: 29828 (VBI at 27282)
Frame #10: 29835 (VBI at 27278)


is the VBI trigger point wrong?


- It's not a matter of cycles, but where in the current instruction (or one before) the VBlank flag has raised. AFAIK, your frame timings aren't much helpful though.


mikeroolz, it seems to me that what is being tested is the period where VBLANK is high. If I interpret your (useful...not sure why zepper finds them unhelpful?) cycle logs above I see that you're VBLANK is ~2550 cycles long instead of the expected 6820/3 [for NTSC] which is 2273.3333. So, blargg's test rom is telling you your VBLANK is active too long.

However, I don't seem to have 01_vbl_timing.nes in my repository. I'll look for it.

by on (#68508)
It seems to me like you're too attached to the conceptual way in which the PPU appears to work, rather than the actual hardware implementation.

Like MottZilla said, there's only one latch, shared by $2005 and $2006. Also, writes to $2000 (the lower 2 bits), $2005 and $2006 all affect the internal VRAM address, but it looks like you are keeping things separated. You have a set of variables for VRAM access and another for name table rendering, but the NES has actually only one pointer it uses for PPU accesses. To better emulate the PPU you should be using VRAMaddr for that, and letting writes to $2000 and $2005 affect VRAMaddr. So take a look at loopy's document so you know how the VRAM address is affected by writes to those 3 registers.

IIRC, Batletoads keeps rendering disabled for a few scanlines after VBlank ends in order to write more data to VRAM, so it most likely uses tricks (mixed writes to $2005 and $2006) in order to set the scroll, and since you are not handling these registers correctly the scroll gets messed up.

by on (#68529)
thanks for all the clarification, that explains a lot. micro machines has pretty glitchy graphics as well during the menus. during actual gameplay, it looks great though. that might also be related to these problems.

other than those 3 games, everything else i've tried to play seems to work pretty much flawlessly. i'm going to get to work fixing these bugs.

as long as i'm fixing bugs, i also have an unrelated question about mapper 1. i've read multiple docs on it, including on the wiki, on kevtris.org, and a few on zophar's domain but i just can't ever seem to get my mapper 1 code working at all.

some games seem to enable the background, as the screen goes black from the initial grey but then they stop exection in it's tracks and PC sits at 0 until i quit the emulator.

other mapper 1 games keep the grey screen on, but appear to go into a short infinite loop until execution is stopped. hopefully somebody can point out the error(s?) in this code for me, because i've i've been struggling with this one for a while.

keep in mind, when parsing the iNES header, i multiply the PRG count by 16 so any ROM.prgcount>>4 isn't a mistake.

Code:
       case 1:
            if (prgramenabled==0 && addr>0x4017 && addr<0x8000) return;
            if (addr>=0x8000) {
              if (value&128) {
                map1reg[0] = (map1reg[0]&0xF3)+0xC; //bits 2, 3 set - others unchanged
                map1bitpos = 0; map1accum = 0;
                SwapPRG(1, (ROM.prgcount>>1)-1, 16384);
                return;
              }
              map1accum = (map1accum<<1) + value&1; //(map1accum>>1) + (value&1)<<4;
              if (map1bitpos==4) {
                if (addr>=0xE000) {
                  map1reg[3] = map1accum;
                  } else if (addr>=0xC000) {
                    map1reg[2] = map1accum;
                    } else if (addr>=0xA000) {
                      map1reg[1] = map1accum;
                      } else map1reg[0] = map1accum;
                map1calc();
                map1bitpos = 0; map1accum = 0;
                return;
              }
              map1bitpos = (map1bitpos + 1) % 5;
            }
            break;




and here is the map1calc() routine that it calls after getting all 5 bits:

Code:
void map1calc() {
  WORD prgval, chrval;
  switch (map1reg[0]&3) {
    case 0:
         nametablemap[0] = 0x2000;
         nametablemap[1] = 0x2000;
         nametablemap[2] = 0x2000;
         nametablemap[3] = 0x2000; break;
    case 1:
         nametablemap[0] = 0x2400;
         nametablemap[1] = 0x2400;
         nametablemap[2] = 0x2400;
         nametablemap[3] = 0x2400; break;
    case 2:
         nametablemap[0] = 0x2000;
         nametablemap[1] = 0x2400;
         nametablemap[2] = 0x2000;
         nametablemap[3] = 0x2400; break;
    case 3:
         nametablemap[0] = 0x2000;
         nametablemap[1] = 0x2000;
         nametablemap[2] = 0x2400;
         nametablemap[3] = 0x2400; break;
  }
  if (map1reg[0]&8) {
    if (map1reg[0]&4) { SwapPRG(0, map1reg[3]&0xF, 16384); SwapPRG(1, (ROM.prgcount>>4)-1, 16384); }
      else { SwapPRG(0, 0, 16384); SwapPRG(1, map1reg[3]&0xF, 16384); }
  } else {
    SwapPRG(0, (map1reg[3]&0xF)>>1, 32768);
  }
  if (map1reg[0]&16) {
    SwapCHR(0, map1reg[1]&1, 4096);
    SwapCHR(1, map1reg[2]&1, 4096);   
  } else {
    SwapCHR(0, (map1reg[1]>>1)&1, 8192);
  }
}





and for completeness, here are the PRG/CHR bank swap functions - but those 100% perfectly, no problems with mapper 2 and 7 using them.

Code:
void SwapPRG(BYTE banknum, WORD newchunk, WORD banksize) {
     WORD tmpstart, tmplen, tmpcur, tmpcur2;
     tmpstart = (newchunk*banksize)>>10;
     tmplen = banksize>>10;
     tmpcur2 = (banknum*banksize)>>10;
     for (tmpcur=0; tmpcur<tmplen; tmpcur++) PRGbank[tmpcur2++] = tmpstart++;
}

void SwapCHR(BYTE banknum, WORD newchunk, WORD banksize) {
     WORD tmpstart, tmplen, tmpcur, tmpcur2;
     tmpstart = (newchunk*banksize)>>10;
     tmplen = banksize>>10;
     tmpcur2 = (banknum*banksize)>>10;
     for (tmpcur=0; tmpcur<tmplen; tmpcur++) CHRbank[tmpcur2++] = tmpstart++;
}




yesterday i went through and modified the way my emu stores PRG and CHR ROM data to 32 1KB bank chunks for PRG, and 8 1KB bank chunks for CHR, and then wrote those two routines. i thought this would make implementing the various mappers a much simpler task without having to worry about varying bank sizes. just call the function supplying the bank size and have it do the math automatically.


EDIT: i forgot to mention, after loading the ROM into memory i have this code run before emulation begins if it's a mapper 1 ROM:

Code:
SwapPRG(0, 0, 16384); SwapPRG(1, (ROM.prgcount>>4)-1, 16384);

by on (#68559)
ah never mind i finally got mapper 1 going.

this line in the first chunk of pasted code:
SwapPRG(1, (ROM.prgcount>>1)-1, 16384);

should have:
A) been >>4 not >>1
and more importantly
B) shouldn't have been in there at all in the first place heh.


and i changed the shift register code to:
map1accum |= (value&1) << map1bitpos;

all is good now. a couple dumb mistakes.

by on (#68562)
- This is a way of masking the PRG page value written. Switching banks should use the following:

Code:
* if PRG pages is a power of 2:

08k: (PRG >> 1) - 1
16k: PRG - 1
32k: (PRG << 1) - 1


- You're messing up the things I suppose. The PRG page value for mapper 1 is composed of 4 bits. Masking the value isn't really required. If you got the mapper working, perhaps it was lucky only.

- Regarding the number of cycles per frame, perhaps I was trying to imply a finetunning in the timing, as I had to do a lot of times.

by on (#68565)
Zepper wrote:
The PRG page value for mapper 1 is composed of 4 bits. Masking the value isn't really required.

For one thing, the PRG ROM bank number shares a register address with a PRG RAM enable. For another, in the SUROM board (MMC1, 512 KiB PRG ROM, 0 byte CHR ROM), the bits from this register are combined with a bit from another register to form the effective PRG RAM address.

by on (#68603)
Zepper wrote:
- This is a way of masking the PRG page value written. Switching banks should use the following:

Code:
* if PRG pages is a power of 2:

08k: (PRG >> 1) - 1
16k: PRG - 1
32k: (PRG << 1) - 1


- You're messing up the things I suppose. The PRG page value for mapper 1 is composed of 4 bits. Masking the value isn't really required. If you got the mapper working, perhaps it was lucky only.

- Regarding the number of cycles per frame, perhaps I was trying to imply a finetunning in the timing, as I had to do a lot of times.


- 1? i thought the values in registers were already base zero. ROMs seem to run fine when switching banks. megaman 2 for example, runs perfectly. ninja gaiden doesn't have any PRG switching problems, although it seems to choose the wrong CHR banks for some screens. (title and cut scenes)

metroid does the same thing. i think i might need to AND the CHR banks to ensure only up to a 4-bit value. megaman 2 uses no CHR ROM, so that would explain it running perfectly.


EDIT: oh, i see what you meant. but no, i have the PRG banks divided up into 1 KB chunks, and i multiply the PRG bank total count by 16 to match. when i load the ROM. that might have been confusing if you didn't realize that.

don't worry i do know basic math. ;)

my SwapPRG and SwapCHR functions take care of any conversions necessary when i give it the bank size it needs to deal with in bytes. i thought this would be the simplest way to implement other matters later that would work in all kinds of various swap sizes.