diff --git a/README.md b/README.md index 3445c82b..4969c3f9 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For build instructions, see [Build instructions](./COMPILING.md). * other Commodore computers: C16, Plus/4, C128, PET, VIC-20 (stock or with RAM extensions) - * other 6502-based machines: Famicom/NES, Atari 8-bit computers, BBC Micro, Apple II+/IIe/Enhanced IIe, Atari 2600 (experimental) + * other 6502-based machines: Famicom/NES, Atari 8-bit computers, BBC Micro, Apple II+/IIe/Enhanced IIe, Atari 2600 (experimental), Commander X16 (experimental) * Z80-based machines: ZX Spectrum 48k, NEC PC-88, Amstrad CPC, MSX diff --git a/docs/README.md b/docs/README.md index 87c99b42..504ddf94 100644 --- a/docs/README.md +++ b/docs/README.md @@ -60,6 +60,8 @@ * [Game Boy–only modules](stdlib/gb.md) +* [X16–only modules](stdlib/x16.md) + ## Implementation details * [Variable storage](abi/variable-storage.md) diff --git a/docs/doc_index.md b/docs/doc_index.md index 87c99b42..504ddf94 100644 --- a/docs/doc_index.md +++ b/docs/doc_index.md @@ -60,6 +60,8 @@ * [Game Boy–only modules](stdlib/gb.md) +* [X16–only modules](stdlib/x16.md) + ## Implementation details * [Variable storage](abi/variable-storage.md) diff --git a/docs/lang/preprocessor.md b/docs/lang/preprocessor.md index 20fe7e13..02c04710 100644 --- a/docs/lang/preprocessor.md +++ b/docs/lang/preprocessor.md @@ -112,6 +112,8 @@ See [the ROM vs RAM guide](../api/rom-vs-ram.md) for more information. * `NULLPTR` – physical value of `nullptr`, default 0 +* `VERA_VERSION` – on Commander X16, the version of the VERA chip: `7` for 0.7, `8` for 0.8 + ### Built-in preprocessor functions and operators The `defined` function returns 1 if the feature is defined, 0 otherwise. diff --git a/docs/stdlib/x16.md b/docs/stdlib/x16.md new file mode 100644 index 00000000..ee9b9ca6 --- /dev/null +++ b/docs/stdlib/x16.md @@ -0,0 +1,122 @@ +[< back to index](../doc_index.md) + +# Commander X16-oriented modules + +**WARNING!** Commander X16 is not yet a finalised design. +Therefore, both the device itself and the modules for its support may change at any moment. + +## x16_kernal module + +The `x16_kernal` module is imported automatically on the X16 target. + +Currently, it automatically imports the [`c64_kernal` module](./c64.md). + +## `x16_hardware` module + +The `x16_hardware` module is imported automatically on the X16 target. + +#### `void set_ram_bank(byte)` + +Switches the RAM segment at $A000-$BFFF. + +#### `void set_rom_bank(byte)` + +Switches the ROM segment at $C000-$DFFF. + +#### `void vera_poke(int24 address, byte value)` + +Writes a byte into the VERA memory space. + +#### `void vera_fill(int24 address, byte value, word size)` + +Writes `size` bytes into the VERA memory space. + +#### `void vera_upload(int24 address, pointer source, byte size)` +#### `void vera_upload_large(int24 address, pointer source, word size)` + +Copies `size` bytes from the RAM at address `source` into the VERA memory space at address `address`. + +#### `struct vera_layer_setup` + +Hardware register values for a video layer: + + byte ctrl0 + byte ctrl1 + word map_base + word tile_base + word hscroll + word vscroll + +#### `void set_vera_layer1(pointer.vera_layer_setup)` + +Sets up the layer 1. + +#### `void set_vera_layer2(pointer.vera_layer_setup)` + +Sets up the layer 2. + +#### `struct vera_sprite_data` + +Hardware register values for a sprite: + + word address + word x + word y + byte ctrl0 + byte ctrl1 + +#### `const int24 VERA_COMPOSER_CTRL` +#### `const int24 VERA_PALETTE` +#### `const int24 VERA_LAYER_1` +#### `const int24 VERA_LAYER_2` +#### `const int24 VERA_SPRITE_CTRL` +#### `const int24 VERA_SPRITES` + +Various addresses in the VERA memory space. + +## `x16_joy` module + +The `x16_joy` module implements a joystick driver compatible with the `joy` module. + +#### `void read_joy1()` + +Reads the joystick from the port 1. + +#### `void read_joy2()` + +Reads the joystick from the port 1. + +#### `void read_also_joy1()` + +Reads the joystick from the port 1 and adds its readouts to the current readouts. + +#### `void read_also_joy2()` + +Reads the joystick from the port 2 and adds its readouts to the current readouts. + +#### `byte input_*` + +The following variables have the value 1 if the key is pressed and 0 if not: + +**Warning:** The assignment of NES controller buttons and keyboard keys may change in the future. + +Variable | SNES controller | NES controller | Keyboard (joy 1 only) +---------------|-----------------|----------------|---------------------- +`input_a` | A | | +`input_b` | B | A | Ctrl +`input_x` | X | B | Alt +`input_y` | Y | | +`input_start` | Start | Start | Enter +`input_select` | Select | Select | Space +`input_l` | L | | +`input_r` | R | | + + +`input_b` is an alias for `input_btn`. Single-button games should use `input_btn` for compatibility. + +## x16_joy1_default module + +Defines the joystick in port 1 as the default joystick. + +#### `alias read_joy = read_joy1` + diff --git a/examples/x16/balls.mfk b/examples/x16/balls.mfk index 09f5d23a..b439cd01 100644 --- a/examples/x16/balls.mfk +++ b/examples/x16/balls.mfk @@ -33,9 +33,9 @@ void main () { vy[i] = 1 - (rand() & 2) } vera_upload_large($10000, sprite_bitmap.addr, sizeof(sprite_bitmap)) - vera_upload_large($40800, sprites.addr, sizeof(sprites)) + vera_upload_large(VERA_SPRITES, sprites.addr, sizeof(sprites)) // enable sprites: - vera_poke($40020, 1) + vera_poke(VERA_SPRITE_CTRL, 1) while true { for i,0,paralleluntil,SPRITE_COUNT { p = sprites[i].pointer @@ -50,7 +50,7 @@ void main () { if y == 0 { vy[i] = 1 } if y >= 480-32 { vy[i] = 0-1 } } - vera_upload_large($40800, sprites.addr, sizeof(sprites)) + vera_upload_large(VERA_SPRITES, sprites.addr, sizeof(sprites)) } } diff --git a/examples/x16/palette.mfk b/examples/x16/palette.mfk index 0c39ae2d..82656ad0 100644 --- a/examples/x16/palette.mfk +++ b/examples/x16/palette.mfk @@ -2,12 +2,12 @@ void main() { word i byte hn, ln // 256-colour text mode for layer 0 - vera_poke($40000, $21) + vera_poke(VERA_LAYER_1, $21) // 8×8 tiles, 64×64 tile map - vera_poke($40001, $35) + vera_poke(VERA_LAYER_1+1, $35) // 2× zoom in - vera_poke($40041, $40) - vera_poke($40042, $40) + vera_poke(VERA_COMPOSER_CTRL+1, $40) + vera_poke(VERA_COMPOSER_CTRL+2, $40) i = 0 while i < $2000 { vera_poke(i, $A0) diff --git a/include/x16_experimental.ini b/include/x16_experimental.ini index 2938502d..b16edd45 100644 --- a/include/x16_experimental.ini +++ b/include/x16_experimental.ini @@ -7,29 +7,39 @@ arch=cmos encoding=petscii screen_encoding=petscr -modules=loader_0801,c64_kernal,x16_hardware,c64_panic,stdlib +modules=loader_0801,x16_kernal,x16_hardware,c64_panic,stdlib [allocation] -; list of free zp pointer locations is the same as C64 -zp_pointers=$FB,$FD,$43,$45,$47,$4B,$F7,$F9,$9E,$9B,$3D -segments=default,himem0 +; Let's not use the BASIC: +zp_pointers=0-$8F,$FB-$FF +segments=default,himem_00,himem_ff default_code_segment=default segment_default_start=$80D segment_default_codeend=$9eff segment_default_datastart=after_code segment_default_end=$9eff -segment_himem0_start=$c000 -segment_himem0_codeend=$dfff -segment_himem0_datastart=after_code -segment_himem0_end=$dfff +segment_himem_00_start=$a000 +segment_himem_00_codeend=$bfff +segment_himem_00_datastart=after_code +segment_himem_00_end=$bfff +segment_himem_00_bank=$00 + +segment_himem_ff_start=$a000 +segment_himem_ff_codeend=$bfff +segment_himem_ff_datastart=after_code +segment_himem_ff_end=$bfff +segment_himem_ff_bank=$ff [define] CBM=1 +COMMANDER_X16=1 WIDESCREEN=1 KEYBOARD=1 -JOYSTICKS=0 +JOYSTICKS=2 HAS_BITMAP_MODE=1 +; Use VERA 0.7, as this is what the emulator implements: +VERA_VERSION=7 [output] style=single diff --git a/include/x16_hardware.mfk b/include/x16_hardware.mfk index e1ae8d88..70168f46 100644 --- a/include/x16_hardware.mfk +++ b/include/x16_hardware.mfk @@ -1,9 +1,35 @@ -// TODO: bankswitching +#if VERA_VERSION == 7 + +const int24 VERA_COMPOSER_CTRL = $40040 +const int24 VERA_PALETTE = $40200 +const int24 VERA_LAYER_1 = $40000 +const int24 VERA_LAYER_2 = $40010 +const int24 VERA_SPRITE_CTRL = $40020 +const int24 VERA_SPRITES = $40800 volatile byte vera_addr_hi @ $9f20 volatile byte vera_addr_mi @ $9f21 volatile byte vera_addr_lo @ $9f22 + +#elseif VERA_VERSION == 8 + +const int24 VERA_COMPOSER_CTRL = $F0000 +const int24 VERA_PALETTE = $F1000 +const int24 VERA_LAYER_1 = $F2000 +const int24 VERA_LAYER_2 = $F3000 +const int24 VERA_SPRITE_CTRL = $F4000 +const int24 VERA_SPRITES = $F5000 + +volatile byte vera_addr_hi @ $9f22 +volatile byte vera_addr_mi @ $9f21 +volatile byte vera_addr_lo @ $9f20 +volatile int24 vera_addr @ $9f20 + +#else +#error Unsupported VERA_VERSION +#endif + volatile byte vera_data1 @ $9f23 volatile byte vera_data2 @ $9f24 volatile byte vera_ctrl @ $9f25 @@ -23,10 +49,19 @@ asm void set_vera_layer_internal(pointer.vera_layer_setup ax, byte y) { sta __reg stx __reg+1 stz vera_ctrl +#if VERA_VERSION == 7 lda $14 sta vera_addr_hi stz vera_addr_mi sty vera_addr_lo +#elseif VERA_VERSION == 8 + lda $1F + sta vera_addr_hi + sty vera_addr_mi + stz vera_addr_lo +#else +#error Unsupported VERA_VERSION +#endif ldy #0 __set_layer_internal_loop: lda (__reg),y @@ -37,13 +72,25 @@ asm void set_vera_layer_internal(pointer.vera_layer_setup ax, byte y) { ? rts } -asm void set_vera_layer0(pointer.vera_layer_setup ax) { +asm void set_vera_layer1(pointer.vera_layer_setup ax) { +#if VERA_VERSION == 7 ? ldy #0 +#elseif VERA_VERSION == 8 + ? ldy #$20 +#else +#error Unsupported VERA_VERSION +#endif ? jmp set_vera_layer_internal } -asm void set_vera_layer1(pointer.vera_layer_setup ax) { - ? ldy #0 +asm void set_vera_layer2(pointer.vera_layer_setup ax) { +#if VERA_VERSION == 7 + ? ldy #$10 +#elseif VERA_VERSION == 8 + ? ldy #$30 +#else +#error Unsupported VERA_VERSION +#endif ? jmp set_vera_layer_internal } @@ -87,7 +134,7 @@ inline void vera_upload(int24 address, pointer source, byte size) { ? ldy #0 __vera_upload_loop: ? lda (__reg),y - ? sta vera_data1 + ! sta vera_data1 ? iny ? cpy size ? bne __vera_upload_loop @@ -101,3 +148,13 @@ struct vera_sprite_data { byte ctrl0 byte ctrl1 } + +inline asm void set_ram_bank(byte a) { + ! STA $9F61 + ? RTS +} + +inline asm void set_rom_bank(byte a) { + ! STA $9F60 + ? RTS +} diff --git a/include/x16_joy.mfk b/include/x16_joy.mfk new file mode 100644 index 00000000..49ae1010 --- /dev/null +++ b/include/x16_joy.mfk @@ -0,0 +1,89 @@ + +void x16_reset_joy() { + input_dx = 0 + input_dy = 0 + input_a = 0 + input_b = 0 + input_x = 0 + input_y = 0 + input_start = 0 + input_select = 0 + input_l = 0 + input_r = 0 +} + +alias reset_joy = x16_reset_joy! + +// TODO: be more controller-agnostic? + +// SNES:B NES:A keyboard:Ctrl +alias input_b = input_btn + +// SNES:A +byte input_a + +// SNES:X +byte input_x + +// SNES:Y NES:B keyboard:Alt +byte input_y + +// SNES/NES:Start keyboard:Enter +byte input_start + +// SNES/NES:Select keyboard:Space +byte input_select + +// SNES:L +byte input_l + +// SNES:R +byte input_r + +void read_joy1() { + x16_reset_joy() + read_also_joy1() +} +void read_joy2() { + x16_reset_joy() + read_also_joy2() +} +asm void read_also_joy1() { + JSR $FF06 + LDA $F1 + BNE __read_also_joy1_skip + LDA $EF + JSR x16_joy_byte0 + LDA $F0 + JSR x16_joy_byte1 +__read_also_joy1_skip: + RTS +} +asm void read_also_joy2() { + JSR $FF06 + LDA $F4 + BNE __read_also_joy2_skip + LDA $F2 + JSR x16_joy_byte0 + LDA $F3 + JSR x16_joy_byte1 +__read_also_joy2_skip: + RTS +} + +void x16_joy_byte0(byte value) { + if value & 8 == 0 { input_dy -= 1 } + if value & 4 == 0 { input_dy += 1 } + if value & 2 == 0 { input_dx -= 1 } + if value & 1 == 0 { input_dx += 1 } + if value & 16 == 0 { input_start += 1 } + if value & 32 == 0 { input_select += 1 } + if value & 64 == 0 { input_y += 1 } + if value & 128 == 0 { input_b += 1 } +} +void x16_joy_byte1(byte value) { + if value & 16 == 0 { input_r += 1 } + if value & 32 == 0 { input_l += 1 } + if value & 64 == 0 { input_x += 1 } + if value & 128 == 0 { input_a += 1 } +} diff --git a/include/x16_joy1_default.mfk b/include/x16_joy1_default.mfk new file mode 100644 index 00000000..3e338f4b --- /dev/null +++ b/include/x16_joy1_default.mfk @@ -0,0 +1,5 @@ +// set X16 joypad in port 1 as the default + +import x16_joy + +alias read_joy = read_joy1 diff --git a/include/x16_kernal.mfk b/include/x16_kernal.mfk new file mode 100644 index 00000000..cc44aa18 --- /dev/null +++ b/include/x16_kernal.mfk @@ -0,0 +1,2 @@ +// Let's be lazy: +import c64_kernal diff --git a/mkdocs.yml b/mkdocs.yml index 3ee1536d..cecd1827 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ nav: - C64-only modules: stdlib/c64.md - NES-only modules: stdlib/nes.md - Game Boy–only modules: stdlib/gb.md + - X16–only modules: stdlib/x16.md - Implementation details: - Calling convention: abi/calling-convention.md - Generated labels: abi/generated-labels.md