This document is a work in progress. For contact, send mail to
suwa★usamimi.info.
This page is for detailing some information on the PC-Engine's hardware, written with game programmers in mind. It may also contain some tips on writing software.
For ease of reading, larger binary numbers would often be written with :
inserted inbetween every byte (or sometimes, every two bytes).
0b00000001111101001111011000111110 (32831038) would become
0b00000001:11110100:11110110:00111110. A similar notation also exists for
hexadecimal: 0x04000000 is sometimes written as 0x0400:0000. It's the same
principle as how larger decimal numbers are usually written with commas to
separate them, like how you usually see people write 1,000,000 instead of
1000000.
Hexadecimal numbers will often be written either with the notation $XX or
XXh. Numbers not prefixed/suffixed with anything are just regular decimal
numbers.
The terminology used in this document:
$00 to $FF.$0000 to $FFFF.$0000:0000 to $FFFF:FFFF.$00:0000 to $FF:FFFF.Like many consoles of its time, the PC-engine's CPU is limited to only
accessing data in the address space 0000h-FFFFh. However, internally, the
system actually has an address space of 00:0000h-1F:FFFFh. This larger address
space is referred to as the physical address space, and the smaller one from
0000h-FFFFh is referred to as the virtual address space.
The virtual address space is actually just 8 evenly-sized windows into the physical address space. Each "window" is referred to as a page, and is 8kb in size. You can also decide where in the physical address space that each page maps to, by changing the CPU's 8 MPRs (Mapping Registers).
How they're mapped is as follows:
MPR0 decides what page 0000h-1FFFh is mapped to.MPR1 decides what page 2000h-3FFFh is mapped to.MPR2 decides what page 4000h-5FFFh is mapped to.MPR3 decides what page 6000h-7FFFh is mapped to.MPR4 decides what page 8000h-9FFFh is mapped to.MPR5 decides what page A000h-BFFFh is mapped to.MPR6 decides what page C000h-DFFFh is mapped to.MPR7 decides what page E000h-FFFFh is mapped to.For example:
MPR0 as 00h will map 0000h-1FFFh to physical page 00:0000h-00:1FFFh.MPR0 as 01h will map 0000h-1FFFh to physical page 00:2000h-00:3FFFh.MPR0 as 02h will map 0000h-1FFFh to physical page 00:4000h-00:5FFFh.MPR1 as F8h will map 2000h-3FFFh to physical page 1F:0000h-1F:1FFFh.MPR7 as FFh will map E000h-FFFFh to physical page 1F:E000h-1F:FFFFh.The TAMx/TMAx CPU instructions are used to set your MPRs. You set A to your
desired page, then use the TAMx instruction to transfer the contents of the A
register to the MPR x.
Physical Memory Map:
Phys. Address | Segment | Name
00:0000 - 0F:FFFF | 00h-7Fh (1MB) | HuCard ROM
1F:0000 - 1F:7FFF | F8h-FBh (8KB) | Work RAM (WRAM) (mirrored 4 times)
1F:E000 - 1F:FFFF | FFh | I/O Ports
Additional memory, unmapped (only accessible via I/O ports)
VRAM 64KB (32 Kwords)
Palette 1KB (512 words)
Nearly all PCE software uses the following virtual mapping:
Page | Segment | Virt. Address | Name
0 | F8h | 0000h | I/O Ports
1 | FFh | 2000h | Work RAM (WRAM)
2-6 | -- | 4000h | User Defined
7 | 00h | E000h | HuCard ROM
This allows software to use E000h-FFFFh as their main program code, with 4000h-DFFFh as a user-defined section, usually being changed constantly for data to copy or files/tables to read.
The CPU calls the word at the following VIRTUAL addresses when an exception occurs:
FFF6h: IRQ2 (interrupt via BRK CPU instruction)FFF8h: IRQ1 (interrupt via VDC)FFFAh: TIMERFFFCh: NMIFFFEh: RESETyes, VIRTUAL.
On system startup:
00h. This means that E000h is
00:0000h, and 0000h is also 00:0000h, and so on...RESET vector is called. Whatever address is at that vector, gets
jumped to.It's rather annoying to setup an assembler to deal with said memory map correctly, so here's a few pointers for getting you setup:
00h means that you also need to make
sure that your vectors are stored in page 00h, so your software
doesn't bootup -> the RESET vector gets called -> there's invalid
data at FFFEh -> the program crashesa: to make sure it's actually using absolute addressing. For the
opposite (zero-page addressing), use z:1402h - Interrupt Disable (R/W)
Note that this disables an interrupt, not enable.
1403h - Interrupt Status (R/W※)
If said interrupt is waiting, then the corresponding bit is set.
※ Writing to this register merely just acknowledges the internal TIMER interrupt only, instead of setting the actual status. To acknowledge a VDC interrupt, you have to read the VDC's status register instead.
There are a total of 6 sound channels, each playing a wavetable. They are numbered ch1 through ch6. To edit their properties, you must change the R0 register to the desired channel, then adjust registers R2-R7 as you see fit.
Registers R0-R1 and R8-R9 are global, while R2-R7 are unique to each sound channel, with the channel being selected by R0.
Channels ch5 and ch6 can be set to output noise instead of normal waveform data.
Internally, the PSG contains a "wavetable register address" for each channel,
which holds the address of the waveform data that gets written to. R4 can be
used to change the settings of this address. It's referred to by a number of
confusing names: "wave counter", "address counter", "wave address counter",
etc. In this document, it will be abbreviated to "WRA".
The PSG also contains an LFO, which mis-uses ch2 to apply sound envelopes (for smooth transitions to silence) and other various effects to ONLY ch1. LFO only works if both ch1 and ch2 are enabled!
0800h - R0 - Channel Select Register (R?/W)
The sound channel that sound registers R1-R9 will refer to.
0801h - R1 - Main Amplitude Level Register (R?/W)
Controls the volume of the final mix of all channels.
0802h - R2 & R3 - Frequency low byte (R?/W)
Controls the selected channel's frequency. The two bytes combine to form a final 12-bit frequency.
0804h - R4 - Channel amplitude, Wavetable control (R?/W)
For wavetable control:
0: Auto-increment wavetable register address after each write1: Reset wavetable register address to 02: Play sound3: When set, each write to wavetable gets output directly to the system's D/A converter.Can be used to trigger a sound channel, set its volume, or edit the write settings of its wavetable. Setting wavetable control to 0 and then rapidly writing waveform data to R6 can be used to replicate PCM.
0805h - R5 - L/R Amplitude Level Register (R?/W)
Controls the balance of the current channel's output for both ears.
0806h - R6 - Wavetable Data Register (R?/W)
Writing to this register edits the data in the channel's wavetable, using the channel's wavetable register address as an index. It also auto-increments said address, if the valid bit in R4 is set.
0807h - R7 - Noise control (R?/W)
Only valid for channels ch5 and ch6.
0808h - R8 - LFO Frequency (R?/W)
If LFO is enabled (via register R9), this frequency gets multiplied(?) with ch2's frequency to form an LFO.
0809h - R9 - LFO Play (R?/W)
For the LFO Trigger, a write of 0 starts LFO playback, and a write of 1 resets
LFO by setting ch2's WRA to 0.
For the LFO control:
0 disables LFO.1 makes the LFO's output ch1's frequency + (ch2 wavetable data)2 makes the LFO's output ch1's frequency + (ch2 wavetable data << 4)3 makes the LFO's output ch1's frequency + (ch2 wavetable data << 8)To edit the final output volume of the system's sound, use R1. To edit a single sound channel's wavetable, set R4's wavetable control to 1 to reset the address back to the beginning, then set the wavetable control to 0, and then finally write 32 bytes to R6 to fill its wavetable completely.
The color table is 512 colors long. The background uses the first 256 colors of the color table, while sprites use the next 256 colors. Colors are 9-bit RGB, with each component ranging from 0-7. As a result, the system can display a total of 512 different colors. Though, due to various factors, the colors will display rather differently on an actual system compared to simply emulating.
Each color is in the format:
To set colors in the color table, set CTA to the specified address, then write
the colors you want to upload to CTW.
0400h - CR - Control Register (W)
0402h - CTA - Color Table Address Register (W)
This register gets incremented after each write to CTW.
0404h - CTW/CTR - Color Table Write/Read Register (R/W)
Goes by two names: CTW or CTR. Accessing this register increments CTA.
Maybe we should call it something better like CTD (Color Table Data)
instead...
0000h - Status Register (R/W)
On read:
On write:
Reading this register also acknowledges any interrupts currently waiting
interrupts in 1403h.
0002h - VDC Data Register (R/W)
Accessing this accesses the data at the currently selected VDC register.
The following registers are accessed via utilizing the VDC status register's register index and the VDC data register.
VDC:00h - MAWR - Memory Address Write Register
This register sets the address for data writes to VRAM via the VWR register.
VDC:01h - MARR - Memory Address Read Register
This register sets the address for data reads from VRAM via the VRR register. A write to the MSB of this register is required for changes to this register to actually go into effect.
VDC:02h - VRR/VWR - VRAM Data Read/Write Register
When read, returns data pointed by MARR. If the MSB is read, it returns the MSB
of the pointed data, then the system increments MARR by the value in CR's
auto-increment.
When wrote, writes data to the address pointed by MAWR. Data is only actually written if the MSB gets written to. If data is written, then CR's auto-increment value is then added to MAWR.
VDC:05h - CR - Control Register
You can set the MAWR/MARR byte increment to something like 32, if you want
to vertically-fill tilemaps easier.
VDC:06h - RCR - Raster Counter Register
If this scanline is reached, and CR has scanline matching interrupts enabled,
then the VDC fires an interrupt.
VDC:07h - BXR - Background H ScrollVDC:08h - BYR - Background V Scroll
The background's scroll registers.
VDC:0Fh - DCR - DMA Control Register
VDC:10h - SOUR - DMA Source Address
DMA source VRAM address.
VDC:11h - DESR - DMA Destination Address
DMA destination VRAM address.
VDC:12h - LENR - DMA Block Length
DMA data count.
Unlike other systems, DMA cannot transfer from the CPU address space to VRAM. To do a fast CPU->VRAM transfer, use the HuC6280's builtin memory transfer CPU instructions. For transfers of variable lengths and source/output addresses, use self-modifying code by storing your transfer instructions in RAM and editing the bytecode before jumping to your routines.
VDC:13h - SATB - Sprite Attribute Table Address
The address of the sprite attribute table.
"If the background layer and sprite layer are both disabled, then the VDC is set to burst mode on the next frame. While in burst mode, full access to VRAM is permitted."
The background is only displayed if enabled via the VDC's CR register. The tilemap is always located at VRAM:0000h.
Tile indices are absolute, so tile index 0 will always reference the cel data at VRAM:0000h. This also means the first 32*32 words of VRAM are usually not used as cels, since they technically are also treated as map entries, too. There's probaly some software that does so anyway, though.
A tile's data format is the following:
Cels 2048-4095 can't be used, as they'd go past the system's 64KB of VRAM (though they might be used by certain expansions?)
All cels are 8x8px 4bpp (16-color). Each cel is 32 bytes large. The format for cels is the exact same as the super famicom's 4bpp tiles, if you want a better reference.
The first 8 words of a cel consist of the following. Each word corresponds to 1 row of the cel; the low byte determines the bit 0s of each of the rows' dots, and the high byte has the bit 1s of the rows' dots.
Bit | Bit
F . . . . . . 8 | 7 . . . . . . 0
--------------- | ---------------
Bit 0 of row's dots | Bit 1 of row's dots
The next 8 words are the exact same, except the low bytes determine the bit 0s of each of the rows' dots, and the high bytes have the bit 1s of the rows' dots.
Bit | Bit
F . . . . . . 8 | 7 . . . . . . 0
--------------- | ---------------
Bit 2 of row's dots | Bit 3 of row's dots
The sprite layer is only displayed if enabled via the VDC's CR register.
Sprite attributes (as in, the X coordinate/Y coordinate/other info of each
sprites) are stored in an array called the SATB (Sprite Address Table Buffer),
which is stored in VRAM. Where the SATB is located in VRAM depends on the VDC's
SATB register.
"During rendering, however, the VDC requires the SATB to be copied to an internal sprite buffer. This is a buffer that's completely hands-off to anything but the VDC, therefore you need to use the VDC's DMA to copy the SATB to it." Have to double-check...
Like backgrounds, cel indices are also absolute.
The format of each sprite in the SATB is as follows:
0: Y position (0-1023) (bits 0-9)1: X position (0-1023) (bits 0-9)2: Cel index (0-4095) (bits 0-11)3: Sprite attributesThe format of said sprites' attributes are as follows:
Yes, the minimum size of each sprite is 16x16. Their data is uniquely arranged in 16x16 tiles, rather than the background's 8x8 tiles.