FAIRY COMPUTER SYSTEM 80 (FCS80) is a new retro game machine equipped with the Z80 and AY-3-8910, the actual technology of the 8-bit era, and VDP (FCS80-VIDEO), which has an easy-to-use interface with the Z80. This repository provides an official emulator for the FCS80 core system.
- CPU: Z80A (3579545Hz)
- VDP: FCS80-VDP (4024320Hz)
- Screen Resolution: 240 x 192
- Background & Foreground 8x8 tiled graphics (both virtual screen: 256 x 256)
- 256 sprites (size: 8x8) & max horizontal limit: 256 (unlimited)
- Number of Scanlines: 262
- Frame Rate: 60 frames per second
- Colors: 256 colors in 65536 colors
- APU: AY-3-8910 (44100Hz)
- RAM: 16KB
- VRAM: 16KB
The FCS80 allocates the upper 32KB of the Z80's memory space (64KB) to the program and the lower 32KB to the random access memory map.
- The program (upper 32KB) is divided into four banks of 8KB each.
- The program area is read-only.
- Bank switching is possible by 3-2. $B0~$B3: Bank switch.
- In the random access memory map (lower 32KB), the upper 16KB is allocated to VRAM and the lower 16KB to RAM.
- The memory map can be read and written.
- There is no mechanism to switch banks of RAM.
Address | Map |
---|---|
$0000 ~ $1FFF | PRG0: ROM Bank #0 (Program) |
$2000 ~ $3FFF | PRG1: ROM Bank #1 (Program or Data) |
$4000 ~ $5FFF | PRG2: ROM Bank #2 (Program or Data) |
$6000 ~ $7FFF | PRG3: ROM Bank #3 (Program or Data) |
$8000 ~ $97FF | VRAM (Name Table, OAM, Registers) |
$9800 ~ $98FF | SCC |
$A000 ~ $BFFF | VRAM (Character Pattern) |
$C000 ~ $FFFF | RAM (16KB) |
The FCS80 does not have Static-RAM (also known as battery backup). This is based on the idea that saves and loads are satisfied by state saves and state loads. There may be cases where you want to keep high scores locally, but for leaderboards, I believe it is preferable to get them from outside the FCS80 (by reading RAM, etc.) and implement them outside the game.
In today's programming environment, it is impossible to develop programs properly with only 16KB of RAM, but in the 8-bit era, 2KB of RAM was the norm for targeted game consoles. Therefore, 16KB is more than enough memory for game development. This memory size is sufficient for full assembly language and even C language game development.
CPU address | VRAM address | Map |
---|---|---|
$8000 ~ $83FF | $0000 ~ $03FF | BG Name Table (32 x 32) |
$8400 ~ $87FF | $0400 ~ $07FF | BG Attribute Table (32 x 32) |
$8800 ~ $8BFF | $0800 ~ $0BFF | FG Name Table (32 x 32) |
$8C00 ~ $8FFF | $0C00 ~ $0FFF | FG Attribute Table (32 x 32) |
$9000 ~ $93FF | $1000 ~ $13FF | OAM; Object Attribute Memory (4 x 256) |
$9400 ~ $95FF | $1400 ~ $15FF | Palette Table (2 x 16 x 16) |
$9600 | $1600 | Register #0: Scanline vertical counter (read only) |
$9601 | $1601 | Register #1: Scanline horizontal counter (read only) |
$9602 | $1602 | Register #2: BG Scroll X |
$9603 | $1603 | Register #3: BG Scroll Y |
$9604 | $1604 | Register #4: FG Scroll X |
$9605 | $1605 | Register #5: FG Scroll Y |
$9606 | $1606 | Register #6: IRQ scanline position (NOTE: 0 is disable) |
$9607 | $1607 | Register #7: Status (read only) |
$A000 ~ $BFFF | $2000 ~ $3FFF | Character Pattern Table (32 x 256) |
In the case of typical 8-bit console VDPs (such as the PPU of the Family Computer and the VDP of the MSX), it is common to set the VRAM access address by writing a value twice to the address setting port, and then access it via the write and read ports.
In FCS80-VIDEO, since all addresses of VRAM are mapped to CPU memory, it is possible to write and read VRAM with instructions such as LD and LDIR, just as when accessing RAM.
Priority | Name | Description |
---|---|---|
1 | FG; Foreground Graphics | Graphics to be drawn at the highest priority. It is intended for use in drawing game screen frames, displaying scores, etc. |
2 | Sprites | These graphics are designed to be used for drawing characters that move around on the screen. Unlike BG and FG, drawing data is managed by OAM instead of Name Table. |
3 | BG; Background Graphics | This is the graphics with the lowest drawing priority, intended for drawing the background of the game screen. |
- 32 x 32 characters = 256 x 256 pixels
- masked the edge 8px of left, right and top
- masked the bottom edge 56px
- scrollable with Scroll Register (#2 ~ #5)
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
VI | UD | LR | - | P3 | P2 | P1 | P0 |
- VI: 0 = Hidden, 1 = Visible (NOTE: ignored if BG and always as Visible)
- UD: 1 = Flipping pattern upside down
- LR: 1 = Flipping pattern left to right
- P0 ~ P3: palette number (0 ~ 15)
struct OAM {
unsigned char y;
unsigned char x;
unsigned char pattern;
unsigned char attribute;
} oam[256];
- FCS80 has 16 palettes
- 1 palette has 16 colors
- 1 color = RGB555 (16bit / little endian)
Bit-layout:
(low)
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
- | r4 | r3 | r2 | r1 | r0 | g4 | g3 |
(high)
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
g2 | g1 | g0 | b4 | b3 | b2 | b1 | b0 |
- r4 ~ r0 : Red (0 ~ 31)
- g4 ~ g0 : Green (0 ~ 31)
- b4 ~ b0 : Blue (0 ~ 31)
- $00 ~ $07 : Top blanking (0 ~ 7)
- $08 ~ $C7 : Active scanline (8 ~ 199)
- $FF : Bottom blanking (200 ~ 261)
- $00 ~ $07 : Left blanking
- $08 ~ $F7 : Active (rendering pixel position: 8 ~ 247)
- $F8 ~ $FF : Right blanking
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
BL | - | - | - | - | - | - | - |
- BL: 1 = start vblank
NOTE: Status register always reset after read.
(How to detect VBlank)
.wait_vblank
ld hl, $9607
wait_vblank_loop:
ld a, (hl)
and $80
jp nz, wait_vblank_loop
ret
- Character Pattern Table contains 256 pieces of 8x8 character data.
- One character is 32 bytes.
- Size of table: 32 x 256 = 8192 bytes
Bit layout:
px0 | px1 | px2 | px3 | px4 | px5 | px6 | px7 | Line number |
---|---|---|---|---|---|---|---|---|
H00 | L00 | H01 | L01 | H02 | L02 | H03 | L03 | Line 0 |
H04 | L04 | H05 | L05 | H06 | L06 | H07 | L07 | Line 1 |
H08 | L08 | H09 | L09 | H10 | L10 | H11 | L11 | Line 2 |
H12 | L12 | H13 | L13 | H14 | L14 | H15 | L15 | Line 3 |
H16 | L16 | H17 | L17 | H18 | L18 | H19 | L19 | Line 4 |
H20 | L20 | H21 | L21 | H22 | L22 | H23 | L23 | Line 5 |
H24 | L24 | H25 | L25 | H26 | L26 | H27 | L27 | Line 6 |
H28 | L28 | H29 | L29 | H30 | L30 | H31 | L31 | Line 7 |
Hxx
: High 4bit (0 ~ 15 = Color Code) of byte index at xx (0 ~ 31)Lxx
: Low 4bit (0 ~ 15 = Color Code) of byte index at xx (0 ~ 31)- Color code 0 is transparent in FG and/or Sprite (not transparent in BG)
Waveform memory sound sources compatible with KONAMI's SCC can be used in FCS80.
Address | r | w | Desciption |
---|---|---|---|
$9800~$981F | o | o | 32 bytes signed to define the envelope (waveform) of the channel 1 |
$9820~$983F | o | o | 32 bytes signed to define the envelope (waveform) of the channel 2 |
$9840~$985F | o | o | 32 bytes signed to define the envelope (waveform) of the channel 3 |
$9860~$987F | o | o | 32 bytes signed to define the envelope (waveform) of the channel 4 and 5 |
$9880~$9881 | - | o | Channel 1 frequency on 12bit |
$9882~$9883 | - | o | Channel 2 frequency on 12bit |
$9884~$9885 | - | o | Channel 3 frequency on 12bit |
$9886~$9887 | - | o | Channel 4 frequency on 12bit |
$9888~$9889 | - | o | Channel 5 frequency on 12bit |
$988A | - | o | Channel 1 volume (bits 4~7 are ignored) |
$988B | - | o | Channel 2 volume (bits 4~7 are ignored) |
$988C | - | o | Channel 3 volume (bits 4~7 are ignored) |
$988D | - | o | Channel 4 volume (bits 4~7 are ignored) |
$988E | - | o | Channel 5 volume (bits 4~7 are ignored) |
$988F | - | o | ON/OFF switch for each channel from 1 to 5 (bits 0 |
$9890~$989F | - | o | Same as $9880~$988F (mirrors) |
$98A0~$98DF | o | - | Channel 5 envelope data |
$98E0~$98FF | - | - | Reserved (for Deformation register) |
NOTES:
- Frequencies ($9880~$9889) are the same format as for the PSG frequency (registers 0~5)
- The address map is the same as most of KONAMI's MSX game software (*except Snatcher).
CPU address | RAM address | Map |
---|---|---|
$C000 ~ $FFFF | $0000 ~ $3FFF | Random Access Memory |
Port | I | O | Description |
---|---|---|---|
$A0 | o | - | AY-3-8910: latch register number |
$A1 | - | o | AY-3-8910: write register |
$A2 | - | o | AY-3-8910: read register |
$B0 | o | o | PRG0 bank switch (default: $00) |
$B1 | o | o | PRG1 bank switch (default: $01) |
$B2 | o | o | PRG2 bank switch (default: $02) |
$B3 | o | o | PRG3 bank switch (default: $03) |
$C0 | - | o | High Speed DMA (ROM to VRAM: Character Pattern Table) |
$C1 | o | o | CPU boost flag |
$C2 | - | o | High Speed DMA (memset) |
$C3 | - | o | High Speed DMA (memcpy) |
$D0 ~ $DF | o | o | Direct read / write AY-3-8910 registers: #0 ($D0) ~ #15 ($DF) |
Register | I | O | Feature |
---|---|---|---|
0 | o | o | Ch0 tone generator (low 8bit) |
1 | o | o | Ch0 tone generator (high 4bit) |
2 | o | o | Ch1 tone generator (low 8bit) |
3 | o | o | Ch1 tone generator (high 4bit) |
4 | o | o | Ch2 tone generator (low 8bit) |
5 | o | o | Ch2 tone generator (high 4bit) |
6 | o | o | Noise generator |
7 | o | o | Mixing |
8 | o | o | Ch0 volume |
9 | o | o | Ch1 volume |
10 | o | o | Ch2 volume |
11 | o | o | Envelope period (low 8bit) |
12 | o | o | Envelope period (high 8bit) |
13 | o | o | Envelope pattern |
14 | o | - | JoyPad 1 |
15 | o | - | JoyPad 2 |
The FCS80 supports 16-ports access to simplify the creation of new programs, while also supports 3-ports access for simplify the porting of programs with using legacy AY-3-8910 register access.
- $A0~$A2: General AY-3-8910 register access port
- $D0~$DF: Direct AY-3-8910 register access port
Tone | Oct-1 | Oct-2 | Oct-3 | Oct-4 | Oct-5 | Oct-6 | Oct-7 | Oct-8 |
---|---|---|---|---|---|---|---|---|
C | $D5D | $6AF | $357 | $1AC | $D6 | $6B | $35 | $1B |
C# | $C9C | $64E | $327 | $194 | $CA | $65 | $32 | $19 |
D | $BE7 | $5F4 | $2FA | $17D | $BE | $5F | $30 | $18 |
D# | $B3C | $59E | $2CF | $168 | $B4 | $5A | $2D | $16 |
E | $A9B | $54E | $2A7 | $153 | $AA | $55 | $2A | $15 |
F | $A02 | $501 | $281 | $140 | $A0 | $50 | $28 | $14 |
F# | $973 | $4BA | $25D | $12E | $97 | $4C | $26 | $13 |
G | $8EB | $476 | $23B | $11D | $8F | $47 | $24 | $12 |
G# | $86B | $436 | $21B | $10D | $87 | $43 | $22 | $11 |
A | $7F2 | $3F9 | $1FD | $FE | $7F | $40 | $20 | $10 |
A# | $780 | $3C0 | $1E0 | $F0 | $78 | $3C | $1E | $0F |
B | $714 | $38A | $1C5 | $E3 | $71 | $39 | $1C | $0E |
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
- | - | - | n4 | n3 | n2 | n1 | n0 |
Bit-7 | Bit-6 | Bit-5 | Bit-4 | Bit-3 | Bit-2 | Bit-1 | Bit-0 |
---|---|---|---|---|---|---|---|
1 | 0 | NE2 | NE1 | NE0 | TE2 | TE1 | TE0 |
- NE2: Ch2 noise enable (1: enable, 0: disable)
- NE1: Ch1 noise enable (1: enable, 0: disable)
- NE0: Ch0 noise enable (1: enable, 0: disable)
- TE2: Ch2 tone enable (1: enable, 0: disable)
- TE1: Ch1 tone enable (1: enable, 0: disable)
- TE0: Ch0 tone enable (1: enable, 0: disable)
(TODO: need describe)
(TODO: need describe)
(TODO: need describe)
(TODO: need describe)
The ROM of the bank number corresponding to the value written to the port is transferred to the Character Pattern Table of VRAM.
ld a, $03
out ($C0), a
Normally, consumeClock is called back at an interval of 4Hz or 3Hz (fixed at 4Hz in the case of LR35902), but by always fixing it to 1Hz during cpuBoostFlag is not zero, so the CPU execution speed can be accelerated by 4 to 3 times.
It is a DMA equivalent to memset
in C language:
- Register A: set value
- Register BC: destination address
- Register HL: transfer count in byte
It is a DMA equivalent to memcpy
in C language:
- Register A: ignored
- Register BC: destination address
- Register DE: source address
- Register HL: transfer count in byte
Bank No. | Size | Content |
---|---|---|
0 | 8KB | Program (initial PRG0) |
1 | 8KB | Program or Data (initial PRG1) |
2 | 8KB | Program or Data (initial PRG2) |
3 | 8KB | Program or Data (initial PRG3) |
: | : | : |
255 | 8KB | Program, Data (switchable) |
- The FCS80 ROM file is a set of 8 KB-delimited banks.
- The first bank (Bank No. 0) must contain the program.
- The second of later banks (Bank No. 1 to 255) contain the program or data.
- The size of the ROM file ranges from a minimum of 8 KB (1 bank) to 2 MB (256 banks)
This chapter will guide you on how to implement a game that runs on the FCS80.
Name | Language | Description |
---|---|---|
Hello, World! | Z80, SDCC | Display "Hello, World!" in the BG, and scroll it by the input of JoyPad. |
Sprite Test | Z80, SDCC | Render and move sprites |
Map Scroll | Z80, SDCC | View and scroll through map data created with the Tiled Map Editor. |
Raster Scroll | Z80, SDCC | Shake the screen by rewriting the scroll X for each scanline. |
PSG Test (normal port) | Z80 | Play 8 bars of Twinkle Twinkle Little Star on PSG with accompaniment. (using PSG port $A0 ~ $A2) |
PSG Test (direct port) | Z80 | Play 8 bars of Twinkle Twinkle Little Star on PSG with accompaniment. (using PSG port $Dx) |
- Do not access to the Reserved area of VRAM
- Do not access to the undocumented I/O
- Do not use the Refresh Register (
LD A, R
instruction)- RefreshRegister (RR) has different values depending on the type of Z80 CPU. Compatibility will be maintained as long as you continue to use the official emulator, but there is a possibility that compatibility will not be maintained when the actual device or an unofficial emulator appears in the future.
This repository provides a CLI toolchain that is useful for developing games that run on the FCS80.
Name | Description |
---|---|
bmp2chr | Converts a 128x128 pixel 8-bit color Bitmap to a Character Pattern Table format binary file. |
csv2bin | Converts a CSV file output by Tiled Map Editor to a binary file. |
makerom | Concatenate binary files into a single file (ROM file) with mod 8KB |
This chapter guides you through the implementation of a program to run the FCS80 emulator. The FCS80 emulator provided in this repository can be compiled on any platform with GNU Compiler Collection or LLVM using the clang compiler. (ex: Windows, macOS, Linux, Android, iOS, Nintendo Switch, Sony PlayStation 3/4/5, Microsoft XBOX etc.)
Please incorporate the modules under the src directory of this repository into your project.
Since all FCS80 emulator modules are provided in C++ header files (hpp), there is no need to add the modules to be compiled to the project. In other words, you can incorporate them by simply adding a reference to the include path (-I option in clang++ or include_directories in cmake) as a minimum support method.
#include "fcs80.hpp"
{
// make instance of FCS80 emulator
FCS80 fcs80;
// load ROM image
fcs80.loadRomFile("path-of-rom-file");
// tick 1 frame
unsigned char pad1 = 0;
unsigned char pad2 = 0;
pad1 |= FCS80_JOYPAD_UP | FCS80_JOYPAD_T1; // ex: pushing UP and A button of 1P side
fcs80.tick(pad1, pad2);
// get display buffer (240x192 of RGB555)
unsigned short* display = fcs80.getDisplay();
// dequeue sound buffer (44100Hz 2ch 16bit PCM data)
size_t sizeBytesOfSoundData;
void* sound = fcs80.dequeuSoundBuffer(&sizeBytesOfSoundData);
// save state
size_t stateSize = fcs80.getStateSize();
void* stateBuffer = malloc(stateSize);
fcs80.saveState(stateBuffer);
// load state
fcs80.loadState(stateBuffer);
// reset
fcs80.reset();
}
Name | Platform | Description |
---|---|---|
FCS80 for SDL2 | macOS & Linux | FCS80 for SDL2 (Support macOS and Linux both) |
FCS80 for RPI0 | RaspberryPi Zero | Bare metal environment of RaspberryPi Zero |
FCS80 for RPI0-2W | RaspberryPi Zero 2W | Bare metal environment of RaspberryPi Zero 2W |
FCS80 for RPI1 | RaspberryPi | Bare metal environment of RaspberryPi (Same as RPI0) |
FCS80 for RPI3 | RaspberryPi 3 | Bare metal environment of RaspberryPi 3 (Same as RPI0-2W) |
FCS80 for Cocoa | macOS | An example HAL implementation of FCS80 running on macOS. |
sndrec | Free | Starts the specified ROM file and recording the audio to a wav file. |