PCE target

From llvm-mos

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
  • Memory:
    • 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:

PCE target - Address space
MPR Addresses Bank Description
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:
  • 0xFFF6: IRQ2 (external)
  • 0xFFF8: IRQ1 (VDC)
  • 0xFFFA: IRQ0 (timer)
  • 0xFFFC: NMI (not used)

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:

  1. Create a header file to contain all of your bank definitions (for example, bank.h).
  2. Write your bank definitions, using macros of the format PCE_ROM_BANK_AT(1, 3).
    • 1 refers 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.
    • 3 refers 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.
  3. Create an implementation file (for example, bank.c). Write the following lines:
    • #define PCE_CONFIG_IMPLEMENTATION - this must become before the include,
    • #include <pce.h>
    • #include "bank.h"
  4. Include the header file (bank.h) without defining PCE_CONFIG_IMPLEMENTATION in 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;
  • the __rom_bankN section, which the functions and variables stored inside bank N should be placed in - for example, by using __attribute__((section(__rom_bankN))).

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 PCE_SGX_RAM(n), where 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]

PCE target - LMA format






0x00 Base address space 0x00 .. 0x7F HuCard ROM 6502 address
0xF7 Backup RAM (PC Engine CD)
0xF8 RAM
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 vbank0a and vbank0b.

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 cd/bios.h.

The 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 __rom_bank and PCE_ROM_BANK_AT, __ram_bank and 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. -Tbinary-cd.ld:

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

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