From 664baf56fd2bad541f4471c10c6d58e34a4eae9d Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Wed, 6 Jul 2022 12:00:41 -0700 Subject: [PATCH] Initial support for the dodo platform (#49) * First attempt at dodo support Successfully compiles 0xDEADBEEF (see https://play.dodolabs.io/#), but doesn't compile any of the other sample games due to missing itoa, srand, and rand implementations. * Add version check * Fix CLEAR_SPRITE comment formatting * Fix missing newline at end of file * Remove redundant #pragma once --- mos-platform/CMakeLists.txt | 1 + mos-platform/dodo/CMakeLists.txt | 24 ++ mos-platform/dodo/api.h | 143 ++++++++++++ mos-platform/dodo/api.s | 375 +++++++++++++++++++++++++++++++ mos-platform/dodo/crt0.s | 10 + mos-platform/dodo/link.ld | 31 +++ 6 files changed, 584 insertions(+) create mode 100644 mos-platform/dodo/CMakeLists.txt create mode 100644 mos-platform/dodo/api.h create mode 100644 mos-platform/dodo/api.s create mode 100644 mos-platform/dodo/crt0.s create mode 100644 mos-platform/dodo/link.ld diff --git a/mos-platform/CMakeLists.txt b/mos-platform/CMakeLists.txt index 418cff72c..141c488a3 100644 --- a/mos-platform/CMakeLists.txt +++ b/mos-platform/CMakeLists.txt @@ -46,3 +46,4 @@ add_subdirectory(nes-nrom-128) add_subdirectory(nes-nrom-256) add_subdirectory(nes-slrom) add_subdirectory(osi-c1p) +add_subdirectory(dodo) diff --git a/mos-platform/dodo/CMakeLists.txt b/mos-platform/dodo/CMakeLists.txt new file mode 100644 index 000000000..d067f33b6 --- /dev/null +++ b/mos-platform/dodo/CMakeLists.txt @@ -0,0 +1,24 @@ +platform(dodo COMPLETE PARENT common) + +if(NOT CMAKE_CROSSCOMPILING) + return() +endif() + +install(FILES api.h TYPE INCLUDE) + +add_platform_object_file(dodo-crt0-o crt0.o crt0.s) + +add_platform_library(dodo-crt0) +merge_libraries(dodo-crt0 + common-zero-bss + common-init-stack + common-exit-loop +) + +add_platform_library(dodo-c api.s) +merge_libraries(dodo-c + common-c +) + +target_include_directories(dodo-c SYSTEM BEFORE PUBLIC .) +target_compile_options(dodo-c PUBLIC -mcpu=mos65c02) diff --git a/mos-platform/dodo/api.h b/mos-platform/dodo/api.h new file mode 100644 index 000000000..d0ce26a77 --- /dev/null +++ b/mos-platform/dodo/api.h @@ -0,0 +1,143 @@ +#ifndef _API_H +#define _API_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Specify version compiled against. Semantic versioning enforcement takes place +// in CHECK_VERSION +#define MAJOR 1 +#define MINOR 1 +#define REVISION 0 + +#define byte unsigned char + +/* + * Parameter Type Description + * sprite *byte pointer to the sprite image data + * x byte x coordinate + * y byte y coordinate + * w byte width of sprite + * h byte height of sprite, must be multiple of 8 + * f byte boolean that specifies whether or not to flip horizontally + * m mode drawing mode, see below + */ +void DRAW_SPRITE(byte *sprite, byte x, byte y, byte w, byte h, byte f, byte m); +/* normal, replaces everything underneath the sprite */ +#define DRAW_NOP 0x0 +/* logical OR, fastest mode */ +#define DRAW_OR 0x1 +/* logical AND */ +#define DRAW_AND 0x2 +/* logical XOR */ +#define DRAW_XOR 0x4 + +/* Push video memory to the OLED (expensive) */ +void DISPLAY(); + +/* +Erases the rectangular portiion of the screen defined by the parameters. Note +that background graphics will be erased as well. + +Parameter Type Description +x byte x coordinate +y byte y coordinate +w byte width +h byte height, must be multiple of 8 +*/ +void CLEAR_SPRITE(byte x, byte y, byte w, byte h); + +/* +Sets a pixel to a specific color +Parameter Type Description +x byte x coordinate +y byte y coordinate +c byte color, 0 for black, 1 for white +*/ +void SET_PIXEL(byte x, byte y, byte c); + +/* +Bresenham line algorithm + +Parameter Type Description +x0 byte x coordinate of first point +y0 byte y coordinate of first point +x1 byte x coordinate of second point +y1 byte y coordinate of second point +c byte color, 0 for black, 1 for white +Note: Computationally expensive, it is recommended to draw lines sparingly. +*/ +void DRAW_LINE(byte x0, byte y0, byte x1, byte y1, byte c); + +void DELAY_MS(byte delay); +void LED_ON(); +void LED_OFF(); + +/* Waits for an interrupt to fire. WAIT() should be called at the end of the + * game loop in order to synchronize the frame rate to a consistent 20 FPS. */ +void WAIT(); + +void LOAD_MUSIC(byte *music); +void PLAY_EFFECT(byte *effect); +void PLAY_EFFECT_ONCE(byte *effect); +void SPI_ENABLE(); +void SPI_DISABLE(); +void SPI_WRITE(byte v); + +/* Clear the graphics in video memory */ +void CLEAR(); + +/* +Copying the background back and forth between video memory and a buffer is +useful for games with background graphics. This technique would be used +instead of calling CLEAR_SPRITE(). Typically a game should copy the +background where a sprite will be drawn, draw the sprite, call DISPLAY() to +show the graphics, and then erase the sprite by copying the buffer back into +video memory. + +The buffer needs to be a page taller than the sprite. For instance, if the +sprite is 24x16 pixels (2 pages tall, 48 total bytes). The buffer needs to +be 24*24 pixels (3 pages tall, 72 total bytes) + +Parameter Type Description +data *byte pointer to byte array +x byte x coordinate +y byte y coordinate +w byte width +h byte height +dir byte direction, 0 = vmem -> buffer, 1 = buffer -> vmem +*/ +void COPY_BACKGROUND(byte *data, byte x, byte y, byte w, byte h, byte dir); + +void DRAW_STRING(const char *text); +void SET_CURSOR(byte row, byte col); + +/* Returns a byte that is packed with the button state. For each bit that is + * unset the corresponding button is pushed. + * Bit Position Mask Button + * 1 1 up + * 2 2 down + * 3 4 left + * 4 8 right + * 5 16 a + * 6 32 b + */ +byte READ_BUTTONS(); + +void GET_PIXEL(byte x, byte y); + +void GET_VERSION(byte *p); +void CHECK_VERSION(byte major, byte minor, byte revision); + +void LOAD_PERSISTENT(byte *buffer); +void SAVE_PERSISTENT(byte *buffer); + +// This will spin forever if there is a version mismatch +#define api_init() CHECK_VERSION(MAJOR, MINOR, REVISION) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/mos-platform/dodo/api.s b/mos-platform/dodo/api.s new file mode 100644 index 000000000..2e08bc4c2 --- /dev/null +++ b/mos-platform/dodo/api.s @@ -0,0 +1,375 @@ + .text + + .section .text.pusha,"ax",@progbits +pusha: + ldy mos8(__rc0) + beq .L1 + dec mos8(__rc0) + ldy #0 + sta (mos8(__rc0)), y + rts +.L1: + dec mos8(__rc1) + dec mos8(__rc0) + sta (mos8(__rc0)), y + rts + + .section .text.pushax,"ax",@progbits +pushax: + pha + lda mos8(__rc0) + sec + sbc #2 + sta mos8(__rc0) + bcs .L2 + dec mos8(__rc1) +.L2: + ldy #1 + txa + sta (mos8(__rc0)), y + pla + dey + sta (mos8(__rc0)), y + rts + + .section .text.DRAW_SPRITE,"ax",@progbits + .globl DRAW_SPRITE ; -- Begin function DRAW_SPRITE + .type DRAW_SPRITE,@function +DRAW_SPRITE: ; @DRAW_SPRITE +; %bb.0: + phx + pha + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + pla + jsr pusha + pla + jsr pusha + lda mos8(__rc4) + jsr pusha + lda mos8(__rc5) + jsr pusha + lda mos8(__rc6) + jsr pusha + lda mos8(__rc7) + jsr pusha + lda #0 + jmp ($FFF8) +.Lfunc_end0: + .size DRAW_SPRITE, .Lfunc_end0-DRAW_SPRITE + ; -- End function + .section .text.DISPLAY,"ax",@progbits + .globl DISPLAY ; -- Begin function DISPLAY + .type DISPLAY,@function +DISPLAY: ; @DISPLAY +; %bb.0: + lda #1 + jmp ($FFF8) +.Lfunc_end1: + .size DISPLAY, .Lfunc_end1-DISPLAY + ; -- End function + .section .text.CLEAR_SPRITE,"ax",@progbits + .globl CLEAR_SPRITE ; -- Begin function CLEAR_SPRITE + .type CLEAR_SPRITE,@function +CLEAR_SPRITE: ; @CLEAR_SPRITE +; %bb.0: + jsr pusha + txa + jsr pusha + lda mos8(__rc2) + jsr pusha + lda mos8(__rc3) + jsr pusha + lda #2 + jmp ($FFF8) +.Lfunc_end2: + .size CLEAR_SPRITE, .Lfunc_end2-CLEAR_SPRITE + ; -- End function + .section .text.SET_PIXEL,"ax",@progbits + .globl SET_PIXEL ; -- Begin function SET_PIXEL + .type SET_PIXEL,@function +SET_PIXEL: ; @SET_PIXEL +; %bb.0: + jsr pusha + txa + jsr pusha + lda mos8(__rc2) + jsr pusha + lda #3 + jmp ($FFF8) +.Lfunc_end3: + .size SET_PIXEL, .Lfunc_end3-SET_PIXEL + ; -- End function + .section .text.DRAW_LINE,"ax",@progbits + .globl DRAW_LINE ; -- Begin function DRAW_LINE + .type DRAW_LINE,@function +DRAW_LINE: ; @DRAW_LINE +; %bb.0: + jsr pusha + txa + jsr pusha + lda mos8(__rc2) + jsr pusha + lda mos8(__rc3) + jsr pusha + lda mos8(__rc4) + jsr pusha + lda #4 + jmp ($FFF8) +.Lfunc_end4: + .size DRAW_LINE, .Lfunc_end4-DRAW_LINE + ; -- End function + .section .text.DELAY_MS,"ax",@progbits + .globl DELAY_MS ; -- Begin function DELAY_MS + .type DELAY_MS,@function +DELAY_MS: ; @DELAY_MS +; %bb.0: + jsr pusha + lda #5 + jmp ($FFF8) +.Lfunc_end5: + .size DELAY_MS, .Lfunc_end5-DELAY_MS + ; -- End function + .section .text.LED_ON,"ax",@progbits + .globl LED_ON ; -- Begin function LED_ON + .type LED_ON,@function +LED_ON: ; @LED_ON +; %bb.0: + lda #6 + jmp ($FFF8) +.Lfunc_end6: + .size LED_ON, .Lfunc_end6-LED_ON + ; -- End function + .section .text.LED_OFF,"ax",@progbits + .globl LED_OFF ; -- Begin function LED_OFF + .type LED_OFF,@function +LED_OFF: ; @LED_OFF +; %bb.0: + lda #7 + jmp ($FFF8) +.Lfunc_end7: + .size LED_OFF, .Lfunc_end7-LED_OFF + ; -- End function + .section .text.WAIT,"ax",@progbits + .globl WAIT ; -- Begin function WAIT + .type WAIT,@function +WAIT: ; @WAIT +; %bb.0: + lda #8 + jmp ($FFF8) +.Lfunc_end8: + .size WAIT, .Lfunc_end8-WAIT + ; -- End function + .section .text.LOAD_MUSIC,"ax",@progbits + .globl LOAD_MUSIC ; -- Begin function LOAD_MUSIC + .type LOAD_MUSIC,@function +LOAD_MUSIC: ; @LOAD_MUSIC +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #9 + jmp ($FFF8) +.Lfunc_end9: + .size LOAD_MUSIC, .Lfunc_end9-LOAD_MUSIC + ; -- End function + .section .text.PLAY_EFFECT,"ax",@progbits + .globl PLAY_EFFECT ; -- Begin function PLAY_EFFECT + .type PLAY_EFFECT,@function +PLAY_EFFECT: ; @PLAY_EFFECT +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #1 + jsr pusha + lda #10 + jmp ($FFF8) +.Lfunc_end10: + .size PLAY_EFFECT, .Lfunc_end10-PLAY_EFFECT + ; -- End function + .section .text.PLAY_EFFECT_ONCE,"ax",@progbits + .globl PLAY_EFFECT_ONCE ; -- Begin function PLAY_EFFECT_ONCE + .type PLAY_EFFECT_ONCE,@function +PLAY_EFFECT_ONCE: ; @PLAY_EFFECT_ONCE +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #0 + jsr pusha + lda #10 + jmp ($FFF8) +.Lfunc_end11: + .size PLAY_EFFECT_ONCE, .Lfunc_end11-PLAY_EFFECT_ONCE + ; -- End function + .section .text.SPI_ENABLE,"ax",@progbits + .globl SPI_ENABLE ; -- Begin function SPI_ENABLE + .type SPI_ENABLE,@function +SPI_ENABLE: ; @SPI_ENABLE +; %bb.0: + lda #11 + jmp ($FFF8) +.Lfunc_end12: + .size SPI_ENABLE, .Lfunc_end12-SPI_ENABLE + ; -- End function + .section .text.SPI_DISABLE,"ax",@progbits + .globl SPI_DISABLE ; -- Begin function SPI_DISABLE + .type SPI_DISABLE,@function +SPI_DISABLE: ; @SPI_DISABLE +; %bb.0: + lda #12 + jmp ($FFF8) +.Lfunc_end13: + .size SPI_DISABLE, .Lfunc_end13-SPI_DISABLE + ; -- End function + .section .text.SPI_WRITE,"ax",@progbits + .globl SPI_WRITE ; -- Begin function SPI_WRITE + .type SPI_WRITE,@function +SPI_WRITE: ; @SPI_WRITE +; %bb.0: + jsr pusha + lda #13 + jmp ($FFF8) +.Lfunc_end14: + .size SPI_WRITE, .Lfunc_end14-SPI_WRITE + ; -- End function + .section .text.CLEAR,"ax",@progbits + .globl CLEAR ; -- Begin function CLEAR + .type CLEAR,@function +CLEAR: ; @CLEAR +; %bb.0: + lda #14 + jmp ($FFF8) +.Lfunc_end15: + .size CLEAR, .Lfunc_end15-CLEAR + ; -- End function + .section .text.COPY_BACKGROUND,"ax",@progbits + .globl COPY_BACKGROUND ; -- Begin function COPY_BACKGROUND + .type COPY_BACKGROUND,@function +COPY_BACKGROUND: ; @COPY_BACKGROUND +; %bb.0: + phx + pha + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + pla + jsr pusha + pla + jsr pusha + lda mos8(__rc4) + jsr pusha + lda mos8(__rc5) + jsr pusha + lda mos8(__rc6) + jsr pusha + lda #15 + jmp ($FFF8) +.Lfunc_end16: + .size COPY_BACKGROUND, .Lfunc_end16-COPY_BACKGROUND + ; -- End function + .section .text.DRAW_STRING,"ax",@progbits + .globl DRAW_STRING ; -- Begin function DRAW_STRING + .type DRAW_STRING,@function +DRAW_STRING: ; @DRAW_STRING +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #16 + jmp ($FFF8) +.Lfunc_end17: + .size DRAW_STRING, .Lfunc_end17-DRAW_STRING + ; -- End function + .section .text.SET_CURSOR,"ax",@progbits + .globl SET_CURSOR ; -- Begin function SET_CURSOR + .type SET_CURSOR,@function +SET_CURSOR: ; @SET_CURSOR +; %bb.0: + jsr pusha + txa + jsr pusha + lda #17 + jmp ($FFF8) +.Lfunc_end18: + .size SET_CURSOR, .Lfunc_end18-SET_CURSOR + ; -- End function + .section .text.READ_BUTTONS,"ax",@progbits + .globl READ_BUTTONS ; -- Begin function READ_BUTTONS + .type READ_BUTTONS,@function +READ_BUTTONS: ; @READ_BUTTONS +; %bb.0: + lda #18 + jmp ($FFF8) +.Lfunc_end19: + .size READ_BUTTONS, .Lfunc_end19-READ_BUTTONS + ; -- End function + .section .text.GET_PIXEL,"ax",@progbits + .globl GET_PIXEL ; -- Begin function GET_PIXEL + .type GET_PIXEL,@function +GET_PIXEL: ; @GET_PIXEL +; %bb.0: + jsr pusha + txa + jsr pusha + lda #19 + jmp ($FFF8) +.Lfunc_end20: + .size GET_PIXEL, .Lfunc_end20-GET_PIXEL + ; -- End function + .section .text.GET_VERSION,"ax",@progbits + .globl GET_VERSION ; -- Begin function GET_VERSION + .type GET_VERSION,@function +GET_VERSION: ; @GET_VERSION +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #20 + jmp ($FFF8) +.Lfunc_end21: + .size GET_VERSION, .Lfunc_end21-GET_VERSION + ; -- End function + + .globl LOAD_PERSISTENT ; -- Begin function LOAD_PERSISTENT + .type LOAD_PERSISTENT,@function + .section .text.CHECK_VERSION,"ax",@progbits + .globl CHECK_VERSION ; -- Begin function CHECK_VERSION + .type CHECK_VERSION,@function +CHECK_VERSION: ; @CHECK_VERSION +; %bb.0: + jsr pusha + txa + jsr pusha + lda mos8(__rc2) + jsr pusha + lda #21 + jmp ($FFF8) +.Lfunc_end22: + .size CHECK_VERSION, .Lfunc_end22-CHECK_VERSION + ; -- End function +LOAD_PERSISTENT: ; @LOAD_PERSISTENT +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #22 + jmp ($FFF8) +.Lfunc_end23: + .size LOAD_PERSISTENT, .Lfunc_end23-LOAD_PERSISTENT + ; -- End function + .section .text.SAVE_PERSISTENT,"ax",@progbits + .globl SAVE_PERSISTENT ; -- Begin function SAVE_PERSISTENT + .type SAVE_PERSISTENT,@function +SAVE_PERSISTENT: ; @SAVE_PERSISTENT +; %bb.0: + lda mos8(__rc2) + ldx mos8(__rc3) + jsr pushax + lda #23 + jmp ($FFF8) +.Lfunc_end24: + .size SAVE_PERSISTENT, .Lfunc_end24-SAVE_PERSISTENT + ; -- End function diff --git a/mos-platform/dodo/crt0.s b/mos-platform/dodo/crt0.s new file mode 100644 index 000000000..2d0421026 --- /dev/null +++ b/mos-platform/dodo/crt0.s @@ -0,0 +1,10 @@ +.section .init.40,"axR",@progbits + ; make sure flags are in a sane state + cld + cli + ; tell dodo where the software stack pointer is + lda #__rc0 + sta $00 + ; Init hardware stack pointer + ldx #$FF + txs diff --git a/mos-platform/dodo/link.ld b/mos-platform/dodo/link.ld new file mode 100644 index 000000000..1b95087e0 --- /dev/null +++ b/mos-platform/dodo/link.ld @@ -0,0 +1,31 @@ +MEMORY { + ram (rw) : ORIGIN = 0x200, LENGTH = 0x5600 + fram (rw) : ORIGIN = 0x5800, LENGTH = 0x2000 +} + +SECTIONS { + /* dodo jumps to $5900 to start the game */ + .text 0x5900 : { + INCLUDE text-sections.ld + } >fram + .rodata : { + INCLUDE rodata-sections.ld + } >fram + .data : { + INCLUDE data-sections.ld + } >fram + INCLUDE bss.ld + INCLUDE noinit.ld +} + +__rc0 = 0x2a; +INCLUDE imag-regs.ld +ASSERT(__rc0 == 0x2a, "Inconsistent zero page map.") +ASSERT(__rc31 == 0x49, "Inconsistent zero page map.") + +/* Set initial soft stack address to just above last ram address. (It grows down.) */ +__stack = ORIGIN(ram) + LENGTH(ram); + +OUTPUT_FORMAT { + FULL(fram) +}