Mapper #118 question

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Mapper #118 question
by on (#127266)
Hi im trying to implement mapper #118 to my emu and there are things i don't understand.
Disch's doc say:

Code:
Regs:
---------------------------
$8000:  [CP.. .AAA]
   C = CHR Mode
   P = PRG Mode
   A = Address for $8001


This register operates exactly like it does on your normal MMC3.  It is mentioned here because the 'C' bit
has another usage for mirroring.

The normal mirroring reg ($A000) is totally ignored, and the CHR regs select nametables:

When 'C' is set:
   [ R:2 ][ R:3 ]
   [ R:4 ][ R:5 ]

When 'C' is clear:
   [ R:0 ][ R:0 ]
   [ R:1 ][ R:1 ]

For mirroring, only bit 7 of the CHR regs is significant.  Bit 7 of the appropriate reg selects either NTA or
NTB.


It sounds a pretty easy to emulate mapper, but i can't get it to work.
In other words i don't understand how a reg can be assigned to a NT whenever "C" is set or clear.
thnks in advance.
Re: Mapper #118 question
by on (#127267)
Due to how the board is wired, the register controlling CHR banking at $0000-$03ff also controls NT0 at $2000-$23ff, the register controlling CHR banking at $400-$7ff also controls NT1 at $2400-$27ff, and so on.

If 'C' is clear, it just happens that the same register controls both CHR banking at $0000-$03ff and $400-$7ff, so the first two nametables, as well as the last 2, must be identical.
Re: Mapper #118 question
by on (#127268)
Bregalad wrote:
Due to how the board is wired, the register controlling CHR banking at $0000-$03ff also controls NT0 at $2000-$23ff, the register controlling CHR banking at $400-$7ff also controls NT1 at $2400-$27ff, and so on.If 'C' is clear, it just happens that the same register controls both CHR banking at $0000-$03ff and $400-$7ff, so the first two nametables, as well as the last 2, must be identical.


Ok, that's clear, but how does the mapper changes NT mirroring? I think i need more verbosity...
Re: Mapper #118 question
by on (#127269)
The mapper doesn't change anything. It's simply that the PPU access different regions of memory depending on which nametables and which tiles are fetched, and the mapper redirect those addresses to different part of physical memory.

On a standard (mapper #4) MMC3 cart, the MMC3 distinguishes reads from $0000-$1fff (pattern table fetches) and reads from $2000-$2fff (nametable fetches), and does a different "remapping" in function of which fetch it is : On pattern table fetches it uses the CHR banking registers, but on nametable fetches it uses the mirroring bit.

On mapper #118, the mirroring wire is not connected and is connected to a CHR banking address line instead. It's as if all fetches were from $0000-$1fff, even if they are actually nametable fetches at $2000-$2ffff, that's why/how the CHR banking regs also control mirroring in a weird way.
Re: Mapper #118 question
by on (#127274)
To divide the pattern tables into individual 1KB and 2Kb slots, the MMC3 watches which CHR addresses are accessed and selects the upper address lines accordingly. When any access to PPU $0000-$1FFF is made, the PPU will detect which slot is being accessed and look up the appropriate bank number for that slot to drive the upper CHR lines, effectively making tiles from the selected bank visible to the PPU.

Mapper 118 makes use of the fact that the MMC3 still keeps that logic working when addresses $2000 and up are accessed. To keep the chip simple, the MMC3 can't tell the difference between accesses to $0000-$1FFF and $2000-$3FFF. As far as it's concerned, all of these accesses look the same and it will drive the upper CHR address lines normally even if addresses above $1FFF are used. Normally, that is harmless because the upper CHR address lines are completely ignored by the NES during those accesses, because the NES is using its own internal CIRAM rather than the CHR chip (which is actually disabled during this time).

So, by repurposing the upper address line, mapper 118 reduces the maximum supported CHR-ROM size to 128KB (technically, CHR-ROM could still be 256KB, but tiles would have to be carefully arranged to match the mirroring options that will be active when the tiles are used, and that would be a pain to do) and feeds that bit directly to CIRAM A10. This makes it possible to do name table bankswitching exactly like you do CHR banswitching. There are 2 banks (NT0 and NT1), and the slots are configured according to the CHR mode bit. If the bit is clear, the will be 2 2KB slots at $2000-$27FF and $2800-$2FFF, if it's set, there will be 4 1KB slots at $2000-$23FF, $2400-$27FF, $2800-$2BFF, $2C00-$2FFF.

When PPU $2000-$2FFF is accessed, the PPU will detect which chunk is being accessed and and will look up which bank is mapped to that chunk, exactly like it would do if $0000-$0FFF was being accessed, there's absolutely no difference. Just handle any access to $2000-$2FFF EXACTLY like you do access to $0000-$0FFF and use the upper bit of the selected CHR bank to decide which name table to display.
Re: Mapper #118 question
by on (#127276)
tokumaru wrote:
mapper 118 reduces the maximum supported CHR-ROM size to 128KB (technically, CHR-ROM could still be 256KB, but tiles would have to be carefully arranged to match the mirroring options that will be active when the tiles are used, and that would be a pain to do)

I don't see how it's so painful. Put playfield tiles in the bottom megabit, status bar and menu tiles in the top megabit, and sprite tiles in whatever space remains. Then run the game with a 1-screen mirrored playfield in NT0 and the status bar in NT1, and use NT1 during menus.
Re: Mapper #118 question
by on (#127277)
To restate everything one more time:

The MMC3 IC itself completely ignores PPU A13.

Instead, it provides two entirely disjoint functions:

One- it controls the line usually connected to CIRAM A10 as a function of PPU A10 and PPU A11 and a bit set by the CPU
Two- it controls the lines CHR A10…A17 as a function of PPU A10…A12 and 46 bits set by the CPU.

Mapper 118 or TKSROM/TLSROM is what happens when someone notices that if they just connected CIRAM A10 to the second function, they can get a dramatically more versatile way to control the layout of nametables.
Re: Mapper #118 question
by on (#127280)
tepples wrote:
I don't see how it's so painful. Put playfield tiles in the bottom megabit, status bar and menu tiles in the top megabit, and sprite tiles in whatever space remains. Then run the game with a 1-screen mirrored playfield in NT0 and the status bar in NT1, and use NT1 during menus.

Yeah, this way is not painful, but 1-screen mirroring introduces other problems, like scrolling glitches. My point is that using the full 256KB of CHR-ROM will result in limitations on how that memory can be used, and the programmer will have to work around those.

lidnariq wrote:
Mapper 118 or TKSROM/TLSROM is what happens when someone notices that if they just connected CIRAM A10 to the second function, they can get a dramatically more versatile way to control the layout of nametables.

Yes, mapper 118 uses an unmodified MMC3 chip, there's only one pin connected differently in the board. It steals 1 bit of from the CHR banking in order to have as much control over NT banking as it has over CHR banking.
Re: Mapper #118 question
by on (#127307)
tokumaru wrote:
When PPU $2000-$2FFF is accessed, the PPU will detect which chunk is being accessed and and will look up which bank is mapped to that chunk, exactly like it would do if $0000-$0FFF was being accessed, there's absolutely no difference. Just handle any access to $2000-$2FFF EXACTLY like you do access to $0000-$0FFF and use the upper bit of the selected CHR bank to decide which name table to display.


I can't get it to work.
Does it mean that when the PPU fetches for example $2012 is the same that $0012?
How is that thing of the upper bit selecting NT??
Re: Mapper #118 question
by on (#127310)
If all you're doing is emulating mapper 118, then all you realy need to do is take the topmost bank bit from the first four CHR banks and then use them as nametable select bits. For example, if your last value written to $8000 has the topmost bit set (which is pretty much guaranteed in any game that uses mapper 118), then your 4 nametables will be set from the topmost bits of MMC3 registers 2 (1KB CHR at $1000/$0000), 3 (1KB CHR at $1400/$0400), 4 (1KB CHR at $1800/$0800), and 5 (1KB CHR at $1C00/$0C00).
Re: Mapper #118 question
by on (#127331)
I think everytime i get confused more and more.
All i can say is that i have four functions for name tables ReadNT0, ReadNT1, ReadNT2, ReadNT3 and the "writes" that corresponds to each one. They write to unique 0x400 chunk of mem i.e i have four 0x400 array wich holds NT data. Then if a game requires changing mirroring i only change functions pointers. I tought that would be enough for all games, but now appears this mapper with properties that change all my structure.
Any help?
Re: Mapper #118 question
by on (#127332)
Quote:
All i can say is that i have four functions for name tables ReadNT0, ReadNT1, ReadNT2, ReadNT3 and the "writes" that corresponds to each one. They write to unique 0x400 chunk of mem i.e i have four 0x400 array wich holds NT data.


This system sounds like it'd work just fine.

Though you'd only need two 0x400 byte arrays, since the NES only has memory for two nametables (I call these nametables NTA and NTB in the docs).

So when the game reads/writes one of the four "slots":
2000-23FF
2400-27FF
2800-2BFF
2C00-2FFF
... you have it going to one of these four functions? And when you need to change mirroring modes, you just map each of those slots to different functions?

If my understanding is correct, this will work just fine.

Code:
// assuming you have an array of 4 function pointers... one for each "slot"
//   'rdfuncs' and 'wrfuncs'

// horizontal mirroring
rdfuncs[0] = &ReadNT0;  // slots 0 and 1 use NTA
rdfuncs[1] = &ReadNT0;
rdfuncs[2] = &ReadNT1;  // slots 2 and 3 use NTB
rdfuncs[3] = &ReadNT1;

// vertical mirroring
rdfuncs[0] = &ReadNT0;  // slots 0 and 2 use NTA
rdfuncs[2] = &ReadNT0;
rdfuncs[1] = &ReadNT1;  // slots 1 and 3 use NTB
rdfuncs[3] = &ReadNT1;

// mapper 118 mirroring
if(reg_8000 & 0x80)
{
       // use NTA/NTB as selected by high bit of R:2
    rdfuncs[0] = (reg_R2 & 0x80) ? &ReadNT1 : &ReadNT0;
    rdfuncs[1] = (reg_R3 & 0x80) ? &ReadNT1 : &ReadNT0; // ditto, but R:3
    rdfuncs[2] = (reg_R4 & 0x80) ? &ReadNT1 : &ReadNT0; // R:4
    rdfuncs[3] = (reg_R5 & 0x80) ? &ReadNT1 : &ReadNT0; // R:5
}
else
{
  ...
}

// -- and do the same with wrfunc
Re: Mapper #118 question
by on (#127334)
Great Disch!!

I think now NT mirroring is ok, but i get this in Armadillo and Goal 2:

Image
Image

Any idea?
Re: Mapper #118 question
by on (#127343)
Nevermind... i found the error. Thanks for your support!!