CPU & MPR Hardware Overview

Just about every tutorial for the PC Engine includes a hardware primer, and I struggled with every one of them! Armed with only a crash course in 6502 assembly, where the only addresses I dealt with corresponded to locations in RAM, all of these tutorials were bound to be confusing. So let’s clear some things up.

Addresses

When we’re talking about addresses, we aren’t just talking about a location in memory. An address can also point to other pieces of hardware. It might point to somewhere within the HuCard ROM, to a gamepad, or to a register in the Video Display Controller. Every little thing you can interact with has a physical address.

Those addresses live in the PC Engine’s 2MB of physical address space. That’s $000000 - $1FFFFFF in hex. The problem is that we can’t access addresses that large with our 8-bit CPU. We’re limited to an address range of $0000 - $FFFF. This is where the MPR comes in.

The mapping registers (MPR)

Physical vs logical addresses

First, let’s split up that 2MB (2048KB) of physical address space into 8KB chunks:

2048 / 8 = 256 chunks

Those 256 chunks can be referenced in hexadecimal as $00 - $FF.

Now we can take some of those chunks and map them into a logical address space of 64KB ($0000 - $FFFF) which our CPU can access. The logical address space is also split into 8KB chunks:

64 / 8 = 8 chunks

These are the eight mapping registers, MPR0 - MPR7.

Here’s a diagram from Hudson’s official hardware manual. What is normally a pretty dense text for beginners actually explains this concept very clearly.

MPR map

Okay, not bad! This one took an embarrassingly long time for me to grasp. It wasn’t obvious that splitting up the 2048KB physical address space into 8KB chunks yields a total of 256 chunks and that those can be represented in hex as $00 - $FF. My brain’s decimal to hex conversion ability just isn’t that strong yet and I guess neither is its ability to divide powers of two… Anyway, that caused the whole house of cards to fall.

Terminology

I’ve been calling these 8KB segments “chunks”. The official docs call them “units” and “blocks”. I’ve seen tutorials refer to them as “segments”, “pages” and “banks”, while also using “page” and “bank” as verbs– that really confused me! So let’s get things straight:

  • PAGE = 8KB chunk of the MPR (MPR0 - MPR7)
  • BANK = 8KB chunk of the physical address space ($00 - $FF)

I will use these two terms going forward.

MPR page map

MPR Page
MPR0 $0000-1FFF
MPR1 $2000-3FFF
MPR2 $4000-5FFF
MPR3 $6000-7FFF
MPR4 $8000-9FFF
MPR5 $A000-BFFF
MPR6 $C000-DFFF
MPR7 $E000-FFFF

Bank map

Bank Description
$00-7F HuCard ROM
$F7 Savegame memory
$F8 RAM
$FF Hardware I/O

Standard mapping practice

PC Engine games tend to follow a convention when mapping banks to the MPR:

Bank MPR Page Description
$FF MPR0 $0000-1FFF Hardware I/O
$F8 MPR1 $2000-3FFF RAM
MPR2 $4000-5FFF user defined
MPR3 $6000-7FFF
MPR4 $8000-9FFF
MPR5 $A000-BFFF
MPR6 $C000-DFFF
$00 MPR7 $E000-FFFF HuCard ROM

As for MPR2-6, I imagine that’s to swap in and out sections of the ROM

Note that $00 → MPR7 happens automatically upon system startup. The rest have to be explicitly mapped in. Once they’re in, we refer to them by their logical addresses.

Example

org $E000   ; Start the program counter at the beginning of MPR7, where our
            ; HuCard has been automatically mapped in.

sei         ; Disable interrupts
csh         ; Set high speed mode
cld         ; Clear the decimal flag

lda #$F8    ; Map the RAM bank to MPR1
tam #1

lda #$FF    ; Map the Hardware I/O bank to MPR0
tam #0

tax         ; Initialize the stack at $FF
txs

stz $2000               ; Clear the RAM by setting its first byte to 0,
tii $2000,$2001,$1FFF   ; and copy src ($2000) to dest ($2001) and repeat
                        ; $1FFF times, incrementing the destination address
                        ; each time. This zeroes out $2000-3FFF.