The PCE platform corresponds to the NEC PC Engine, also known as the TurboGrafx-16 in the US.
Hardware description[edit | edit source]
- CPU: HuC6280 (Rockwell 65C02 derivative with additional opcodes) @ 7.16MHz
- Redirected zero page at 0x2000, stack at 0x2100
- Interrupt control and timer
- Joypad port input/output
- 21-bit address space (8-bit bank index, 8 kilobytes per bank)
- 8 kilobytes of RAM (32 kilobytes on SuperGrafx), bank $F8 onwards
- Up to 1 megabyte of ROM, banks $00-$7F
- Memory mapped I/O, bank $FF
- VDC / VCE: Video Display Controller (handles drawing background and sprites) / Video Color Encoder (converts color indexes to RGB colors)
- PSG: Programmable Sound Generator
Banking[edit | edit source]
The PC Engine's address space consists of eight consecutive 8KB banks, numbered from 0 to 7. llvm-mos follows target convention and maps them as follows:
|0||$0000 - $1FFF||$FF||Memory mapped I/O|
|1||$2000 - $3FFF||$F8||PC Engine RAM|
|2||$4000 - $5FFF||User||Controlled by the software developer|
|3||$6000 - $7FFF|
|4||$8000 - $9FFF|
|5||$A000 - $BFFF|
|6||$C000 - $DFFF|
|7||$E000 - $FFFF||$00||Fixed bank; contains IRQ/reset vectors:
Bank mapping[edit | edit source]
Given that each 8KB bank can be mapped to five different locations by the user (and more if you're careful), the following method is provided to configure this mapping for LLVM-MOS emitted code:
- Create a header file to contain all of your bank definitions (for example,
- Write your bank definitions, using macros of the format
1refers to the ID of the bank; the linker script currently supports IDs from 1 to 127 inclusive. The ROM will automatically be padded with empty banks to fill in any gaps - this is useful if you want to use an external tool to inject data outside of LLVM-MOS later.
3refers to the MPR of the mapping, or the bank's location in the address space (see above) in 8KB units, from 2 to 6 inclusive.
- This example, therefore, tells the compiler to map code in bank 1 between $4000 and $5FFF.
- Create an implementation file (for example,
bank.c). Write the following lines:
#define PCE_CONFIG_IMPLEMENTATION- this must become before the include,
- Include the header file (
bank.h) without defining
PCE_CONFIG_IMPLEMENTATIONin any other file in which you wish to use virtual banks.
A defined virtual bank provides the following convenience methods:
pce_rom_bankN_map()- maps bank N to the CPU's address space;
pce_rom_bankN_call(void (*method)(void))- safely maps bank N and calls a function in it;
__rom_bankNsection, which the functions and variables stored inside bank N should be placed in - for example, by using
Fixed area[edit | edit source]
Index 7 is fixed to bank $00, which is where all code and data not located in any bank is put in. Such code and data is always accessible.
Some code may, however, need a larger fixed area than eight kilobytes; for this, one can use the macro
PCE_ROM_FIXED_BANK_SIZE(n) , where
n is a value between 1 (8KB) and 6 (48KB). In this configuration, bank indexes starting from 7 and counting down will be dedicated to this fixed area; for example, for a size of
3 (24 KB), banks 5, 6, and 7 will be fixed.
Note that fixed areas larger than 8KB are non-contiguous in the ROM: the last bank of the fixed area is the first ROM bank, followed by the other banks. For example, for a size of
4 (32KB), bank
$00 will contain the last 8KB,
$01 the first 8KB,
$02 the second 8KB, and
$03 the third 8KB.
SuperGrafx RAM[edit | edit source]
The SuperGrafx provides 32 kilobytes of RAM rather than the console's default 8.
Currently, this may be taken advantage of by using the macro
n is a value between 2 (16KB) and 4 (32KB). This will dedicate bank indexes starting from 2 and counting up for SuperGrafx memory, and allow using them in C code; for example,
PCE_SGX_RAM(4) will allocate indexes 1 through 4 (addresses $2000 to $9FFF) to C code-visible RAM.
Interrupts[edit | edit source]
Interrupts are provided in a manner similar to the NES target. The following entrypoints are defined:
irq_external- IRQ2 (external interrupt),
irq_vdc- IRQ1 (VDC),
irq_timer- IRQ0 (timer),
nmi- NMI (not emitted on PC Engine).
Interrupt handlers can be written in C by using
__attribute__((interrupt)) . This subject is explained in more detail on the C interrupts page.
Linker details[edit | edit source]
ELF address space[edit | edit source]
|0x00||Base address space||0x00 .. 0x7F||HuCard ROM||6502 address|
|0xF7||Backup RAM (PC Engine CD)|
|0xF9 .. 0xFB||RAM (SuperGrafx)|
|0xFF||Memory mapped I/O|
|0x01||Additional RAM||0x40 .. 0x5F||RAM ("Populous")|
|0x68 .. 0x7F||RAM (Super System Card)|
|0x80 .. 0x87||RAM (PC Engine CD)|
|0x02 .. 0x11||"SF2" mapper
(Additional ROM banks)
|0x40 .. 0x7F||HuCard ROM|
Note that, for a fixed bank larger than 8KB, the LMA's Bank value refers to its lowest bank. To recover the actual bank and in-bank address from the ELF data, one can use the following equation:
FirstBank = ((LMA >> 16) & 0xFF)
PhysBank = FirstBank + ((LMA - BankLMA) >> 13)
PhysAddress = (LMA & 0x1FFF)
where BankLMA refers to the nearest lower or equal, in terms of value,
__(.*vbank.+)_lma symbol - the fixed bank is split into
PC Engine CD[edit | edit source]
PC Engine CD support is provided via the
pce-cd target. Developing for it has a few important differences from developing a cartridge-based project, outlined below.
Additional functionality[edit | edit source]
The PC Engine CD architecture provides an extensive BIOS, the functionality of which is documented in
pce-cd target does not support resizing the fixed bank or mapping SuperGrafx RAM by default.
Banking[edit | edit source]
On the PC Engine CD, all data is stored in RAM; as such, instead of
PCE_RAM_BANK_AT is used itself. The range is also different:
- PC Engine CD, internal RAM: bank indexes 128 - 135
- Super System Card, additional RAM: bank indexes 104 - 127
The banks are populated from CD in their totality. To not duplicate RAM usage, the C
.data section is stored alongside code in the first RAM bank, while
.bss is stored in the console RAM by default.
The first RAM bank used is expected to be fixed at $4000.
Linking[edit | edit source]
There are four link scripts available, which can be specified by using f.e.
binary-cd.ld- "raw" PCE CD-ROM binary (64K RAM);
binary-scd.ld- "raw" PCE Super CD-ROM binary (256K RAM);
ipl.ld- Initial Program Loader (up to 40KB starting at bank 128), default;
ipl-ram.ld- Initial Program Loader, stored in console RAM only (up to 1904 bytes, fits in one CD sector).
"Raw" binaries can be loaded by using the
pce_cdb_cd_exec routine; "IPL" binaries can additionally be placed as the first file loaded on a CD image.
One can also link to contents of other files contained on a CD - see the below section for more information.
Building an ISO[edit | edit source]
To build an ISO, use the
pce-mkcd tool bundled with llvm-mos-sdk. Provide it with an output filename (f.e.
output.iso), followed by input files (either ELFs or any binary files). The first input file must be the IPL program.
pce-mkcd tool resolves the additional symbols in specified ELF files:
__cd_[filename]_sector- the 24-bit sector index of a given file,
__cd_[filename]_sector_count- the size of a given file in sectors,
__cd_[filename]_bank_start- the first bank of a given executable file,
__cd_[filename]_bank_end- the last bank of a given executable file,
__cd_[filename]_bank_count- the bank count of a given executable file,
__cd_[filename]_sym_[main]- for executable files, this can be used to read the value of a symbol from another file.
In addition, one can pass
@list.txt as any of the input arguments to
pce-mkcd - this will read
list.txt's contents and use them as filenames.
Note that, as llvm-mos-sdk does not have permission to distribute this file,
pce-mkcd requires a file called
ipl.bin in its current working directory - it should be 2048 bytes in size and contain the contents of the first data sector of any PC Engine CD disc.
To add CD audio tracks or run the ISO in most PC Engine CD emulators, one should write a
.cue file to accompany the ISO; an example file is provided below:
FILE output.iso BINARY TRACK 01 MODE1/2048 INDEX 01 00:00:00 FILE output.wav WAV TRACK 02 AUDIO INDEX 01 00:00:00
FAQ[edit | edit source]
- Q: Is the "SF2" mapper supported?
- A: Not at this time. While the LMA is prepared to support it, a dedicated linker configuration and VBank macros have to be written for this configuration.
- Q: Is the SuperGrafx supported?
- A: Yes! The VDC routines support using either VDC1 or VDC2, register definitions for the VPC are also available, and the SuperGrafx's extended RAM can be opted into (albeit - at this time - without banking support).
- Q: Can I safely remap non-User bank indexes?
- A: It depends:
- Bank index 0 (memory-mapped I/O) can be unmapped, provided that no user code - including library calls and interrupts - makes use of it.
- Bank index 2 (RAM) generally cannot be unmapped, as it contains the zero page's imaginary registers and the soft stack.
- Bank index 7 (fixed ROM) can probably be unmapped safely if IRQs are disabled, provided that no user code - including library calls - makes use of it.
- Q: What is the ".mlb" file generated alongside the ROM output?
- A: This generated file is a Mesen label file, containing symbol definitions compatible with Mesen 2's debugger.