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.
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.