pcehard


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.


Update log



Contents



Preface


.1: Links


.2: Terminology


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:


system


.1: MMU

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:

For example:

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.

.2: memory Map

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.

.3: behavior

The CPU calls the word at the following VIRTUAL addresses when an exception occurs:

yes, VIRTUAL.


On system startup:


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:


interrupt controller


1402h - Interrupt Disable (R/W)

Bit(s)
Description
7-3
Unused
2
TIMER
1
IRQ1 (VDC)
0
IRQ2

Note that this disables an interrupt, not enable.

1403h - Interrupt Status (R/W※)

Bit(s)
Description
7-3
Unused
2
TIMER
1
IRQ1 (VDC)
0
IRQ2

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.


programmable sound generator/PSG


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!

.1: registers

0800h - R0 - Channel Select Register (R?/W)

Bit(s)
Description
2-0
Sound channel select (0-7, 0==ch1,5==ch6)

The sound channel that sound registers R1-R9 will refer to.


0801h - R1 - Main Amplitude Level Register (R?/W)

Bit(s)
Description
7-4
LMAL: Left-ear amplitude
3-0
RMAL: Right-ear amplitude

Controls the volume of the final mix of all channels.


0802h - R2 & R3 - Frequency low byte (R?/W)

Bit(s)
Description
11-8
High byte of the output frequency
7-0
Low byte of the output frequency

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)

Bit(s)
Description
7-6
Wavetable control
5
Unused
4-0
Channel's output volume

For wavetable control:

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)

Bit(s)
Description
7-4
LAL: Left-ear amplitude
3-0
RAL: Right-ear amplitude

Controls the balance of the current channel's output for both ears.


0806h - R6 - Wavetable Data Register (R?/W)

Bit(s)
Description
7-5
Unused
4-0
Waveform data (0-31)

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)

Bit(s)
Description
7
NE: Noise enable
4-0
NFRQ: Noise frequency (0-31)

Only valid for channels ch5 and ch6.


0808h - R8 - LFO Frequency (R?/W)

Bit(s)
Description
7-0
LFO Frequency

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)

Bit(s)
Description
7
LFO Trigger
1-0
LFO Control

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:

.2: usage

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.


video color encoder/VCE (HuC6260)


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:

Bit(s)
Description
15-9
Unused
8-6
Green
5-3
Red
2-0
Blue

To set colors in the color table, set CTA to the specified address, then write the colors you want to upload to CTW.

.1: registers

0400h - CR - Control Register (W)

Bit(s)
Description
15-8
Unused
7
Strip Colorburst (0=Colorburst intact, 1=Strip)
2
Frame/Field configuration (0=262-line frame, 1=263-line frame)
0-1
DCC (0=5.3693175 MHz, 1=7.15909 MHz, 2=10.738635 MHz, 3=10.738635)

0402h - CTA - Color Table Address Register (W)

Bit(s)
Description
15-9
Unused
8-0
Color table index

This register gets incremented after each write to CTW.


0404h - CTW/CTR - Color Table Write/Read Register (R/W)

Bit(s)
Description
15-9
Unused
8-6
Green
5-3
Red
2-0
Blue

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


video display controller/VDC (HuC6270)


.1: registers

0000h - Status Register (R/W)

On read:

Bit(s)
Description
6
BSY - DMA Busy
5
VD - Currently in VBlank
4
DV - VRAM-VRAM DMA ended.
3
DS - VRAM-SATB DMA ended.
2
PR - Scanline interrupt (current scanline matches RCR)
1
OR - Sprite Overflow (>17 sprites on a scanline)
0
CR - Sprite Collision (Sprite 0 collided w/ another sprite)

On write:

Bit(s)
Description
15-5
Unused
4-0
VDC register index

Reading this register also acknowledges any interrupts currently waiting interrupts in 1403h.


0002h - VDC Data Register (R/W)

Bit(s)
Description
15-0
VDC Data

Accessing this accesses the data at the currently selected VDC register.

.2: VDC registers

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

Bit(s)
Description
15-13
Not used
12-11
MAWR/MARR byte increment (0=1,1=32,2=64,3=128)
7
Background enable
6
Sprite enable
3
Enable firing VDC interrupt on VBlank
2
Enable firing VDC interrupt on scanline match
1
Enable firing VDC interrupt on sprite overflow
0
Enable firing VDC interrupt on sprite 0 collision

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

Bit(s)
Description
0-9
Scanline number (plus 64)

If this scanline is reached, and CR has scanline matching interrupts enabled, then the VDC fires an interrupt.


VDC:07h - BXR - Background H Scroll
VDC:08h - BYR - Background V Scroll

The background's scroll registers.


VDC:0Fh - DCR - DMA Control Register

Bit(s)
Description
4
Automatically repeat transfer between VRAM and SATB on vblank
3
Increment/decrement destination address after each unit (0=increment, 1=decrement)
2
Increment/decrement source address after each unit (0=increment, 1=decrement)
1
Enable firing VDC interrupt on end of VRAM->VRAM transfer
0
Enable firing VDC interrupt on end of VRAM->SATB transfer

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


VDC background


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:

Bit(s)
Description
15-12
Palette index (0-15)
0-11
Cel index (0-4095)

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

VDC sprites


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:

The format of said sprites' attributes are as follows:

Bit(s)
Description
15
Y-Flip (0=No vertical mirror, 1=Mirror vertically)
14
Not used
13-12
Sprite height (0=16px, 1=32px, 2=invalid, 3=64px)
11
X-Flip (0=No horizontal mirror, 1=Mirror horizontally)
10-9
Not used
8
Sprite width (0=16px, 1=32px)
7
Priority (1=behind background)
6-4
Not used
3-0
Palette index (0-15)

Yes, the minimum size of each sprite is 16x16. Their data is uniquely arranged in 16x16 tiles, rather than the background's 8x8 tiles.