tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
15.25k stars 900 forks source link

Playdate support #3988

Open pattmax00 opened 11 months ago

pattmax00 commented 11 months ago

It would be nice to have Playdate support. It is using a Cortex M7 (ARM) processor which is already supported: https://github.com/tinygo-org/tinygo/issues/512 https://github.com/cmsis-svd/cmsis-svd/pull/82

A good example of a final C project that could maybe help as a reference would be this: https://github.com/ericlewis/playdate-tamagotchi since the final .pdx is quite simple. The .pdx is basically just a folder with

  1. The compiled code in pdex.bin
  2. A pdxinfo (just some text)
  3. And then the the assets are built into .pdi files

You can extract the latest release yourself to see.

Even if we could just compile the pdex.bin would be nice.

Here is the C API reference

I think the GBA implementation is already performing some extra steps to build a .gba ROM file. So being able to compile for the Playdate and build a .pdx shouldn't be outside the scope of this project, but if it is feel free to let me know and close the issue, but I think the Playdate would be a very good candidate for TinyGo.

Playboy26 commented 4 months ago

1716255813612 Hey playdate support I just got my playdate handheld on May the 16th and then on May the 17th I was say up my playdate handheld and it was not looking too good so how can I fix my playdate handheld

judah-caruso commented 1 week ago

I'd also like to see how doable this is. I'm not sure if tinygo should handle the creation of a .pdx file, or if a playdate(-simulator).json target that outputs an object file ready to be used by the playdate supplied Makefile would suffice.

This is what their standard Makefile expects:

HEAP_SIZE      = 8388208
STACK_SIZE     = 61800

PRODUCT = game.pdx

SDK = ${PLAYDATE_SDK_PATH}
ifeq ($(SDK),)
SDK = $(shell egrep '^\s*SDKRoot' ~/.Playdate/config | head -n 1 | cut -c9-)
endif

ifeq ($(SDK),)
$(error SDK path not found; set ENV value PLAYDATE_SDK_PATH)
endif

VPATH += Source

SRC = \
    Source/main.c

UINCDIR =
UASRC =
UDEFS =
UADEFS =
ULIBDIR =
ULIBS =

include $(SDK)/C_API/buildsupport/common.mk

and common.mk:

detected_OS := $(shell uname -s)
detected_OS := $(strip $(detected_OS))

$(info detected_OS is "$(detected_OS)")

ifeq ($(detected_OS), Linux)

  GCCFLAGS = -g
  SIMCOMPILER = gcc $(GCCFLAGS)
  DYLIB_FLAGS = -shared -fPIC
  DYLIB_EXT = so
  PDCFLAGS = -sdkpath $(SDK)
endif

ifeq ($(detected_OS), Darwin)

  CLANGFLAGS = -g
  SIMCOMPILER = clang $(CLANGFLAGS)
  DYLIB_FLAGS = -dynamiclib -rdynamic
  DYLIB_EXT = dylib
  PDCFLAGS=
  # Uncomment to build a binary that works with Address Sanitizer
  #CLANGFLAGS += -fsanitize=address

endif

TRGT = arm-none-eabi-
GCC:=$(dir $(shell which $(TRGT)gcc))

ifeq ($(GCC),)
GCC = /usr/local/bin/
endif

OJBCPY:=$(dir $(shell which $(TRGT)objcopy))

ifeq ($(OJBCPY),)
OJBCPY = /usr/local/bin/
endif

PDC = $(SDK)/bin/pdc

VPATH += $(SDK)/C_API/buildsupport

CC   = $(GCC)$(TRGT)gcc -g3
CP   = $(OJBCPY)$(TRGT)objcopy
AS   = $(GCC)$(TRGT)gcc -x assembler-with-cpp
STRIP= $(GCC)$(TRGT)strip
BIN  = $(CP) -O binary
HEX  = $(CP) -O ihex

MCU  = cortex-m7

# List all default C defines here, like -D_DEBUG=1
DDEFS = -DTARGET_PLAYDATE=1 -DTARGET_EXTENSION=1

# List all default directories to look for include files here
DINCDIR = . $(SDK)/C_API

# List all default ASM defines here, like -D_DEBUG=1
DADEFS =

# List the default directory to look for the libraries here
DLIBDIR =

# List all default libraries here
DLIBS =

OPT = -O2 -falign-functions=16 -fomit-frame-pointer

#
# Define linker script file here
#
LDSCRIPT = $(patsubst ~%,$(HOME)%,$(SDK)/C_API/buildsupport/link_map.ld)

#
# Define FPU settings here
#
FPU = -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D__FPU_USED=1

INCDIR  = $(patsubst %,-I %,$(DINCDIR) $(UINCDIR))
LIBDIR  = $(patsubst %,-L %,$(DLIBDIR) $(ULIBDIR))
OBJDIR  = build
DEPDIR  = $(OBJDIR)/dep

DEFS    = $(DDEFS) $(UDEFS)

ADEFS   = $(DADEFS) $(UADEFS) -D__HEAP_SIZE=$(HEAP_SIZE) -D__STACK_SIZE=$(STACK_SIZE)

SRC += $(SDK)/C_API/buildsupport/setup.c

# Original object list
_OBJS   = $(SRC:.c=.o)

# oject list in build folder
OBJS    = $(addprefix $(OBJDIR)/, $(_OBJS))

LIBS    = $(DLIBS) $(ULIBS)
MCFLAGS = -mthumb -mcpu=$(MCU) $(FPU)

ASFLAGS  = $(MCFLAGS) $(OPT) -g3 -gdwarf-2 -Wa,-amhls=$(<:.s=.lst) $(ADEFS)

CPFLAGS  = $(MCFLAGS) $(OPT) -gdwarf-2 -Wall -Wno-unused -Wstrict-prototypes -Wno-unknown-pragmas -fverbose-asm -Wdouble-promotion -mword-relocations -fno-common
CPFLAGS += -ffunction-sections -fdata-sections -Wa,-ahlms=$(OBJDIR)/$(notdir $(<:.c=.lst)) $(DEFS)

LDFLAGS  = -nostartfiles $(MCFLAGS) -T$(LDSCRIPT) -Wl,-Map=$(OBJDIR)/pdex.map,--cref,--gc-sections,--no-warn-mismatch,--emit-relocs $(LIBDIR)

# Generate dependency information
CPFLAGS += -MD -MP -MF $(DEPDIR)/$(@F).d

#
# makefile rules
#

all: device_bin simulator_bin
    $(PDC) $(PDCFLAGS) Source $(PRODUCT)

debug: OPT = -O0
debug: all

print-%  : ; @echo $* = $($*)

MKOBJDIR:
    mkdir -p $(OBJDIR)

MKDEPDIR:
    mkdir -p $(DEPDIR)

device: device_bin
    $(PDC) $(PDCFLAGS) Source $(PRODUCT)

simulator: simulator_bin
    $(PDC) $(PDCFLAGS) Source $(PRODUCT)

device_bin: $(OBJDIR)/pdex.elf
    cp $(OBJDIR)/pdex.elf Source

simulator_bin: $(OBJDIR)/pdex.${DYLIB_EXT}
    cp $(OBJDIR)/pdex.${DYLIB_EXT} Source

# pdc is deprecated but in the old docs, alias to simulator
pdc: simulator

# for external builds (Xcode)
pdx:
    $(PDC) $(PDCFLAGS) Source $(PRODUCT)

$(OBJDIR)/%.o : %.c | MKOBJDIR MKDEPDIR
    mkdir -p `dirname $@`
    $(CC) -c $(CPFLAGS) -I . $(INCDIR) $< -o $@

$(OBJDIR)/%.o : %.s | MKOBJDIR MKDEPDIR
    $(AS) -c $(ASFLAGS) $< -o $@

.PRECIOUS: $(OBJDIR)/%elf
.PRECIOUS: $(OBJDIR)/%bin
.PRECIOUS: $(OBJDIR)/%hex
$(OBJDIR)/pdex.elf: $(OBJS) $(LDSCRIPT)
    $(CC) $(OBJS) $(LDFLAGS) $(LIBS) -o $@

$(OBJDIR)/pdex.hex: $(OBJDIR)/pdex.elf
    $(HEX) $< $@

$(OBJDIR)/pdex.bin: $(OBJDIR)/pdex.elf
    $(BIN) $< $@

$(OBJDIR)/pdex.${DYLIB_EXT}: $(SRC) | MKOBJDIR
    $(SIMCOMPILER) $(DYLIB_FLAGS) -lm -DTARGET_SIMULATOR=1 -DTARGET_EXTENSION=1 $(INCDIR) -o $(OBJDIR)/pdex.${DYLIB_EXT} $(SRC)

clean:
    -rm -rf $(OBJDIR)
    -rm -fR $(PRODUCT)
    -rm -fR Source/pdex.bin
    -rm -fR Source/pdex.dylib
    -rm -fR Source/pdex.so
    -rm -fR Source/pdex.dll
    -rm -fR Source/pdex.elf
#
# Include the dependency files, should be the last of the makefile
#
-include $(wildcard $(DEPDIR)/*)

# *** EOF ***

link_map.ld is their custom linker script:

ENTRY(eventHandlerShim)
GROUP(libgcc.a libc.a libm.a)

SECTIONS
{
    .text :
    {
        *(.text)
        *(.text.*)

        KEEP(*(.init))
        KEEP(*(.fini))

        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)

        /* .dtors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)

        *(.rodata*)

        KEEP(*(.eh_frame*))

    }

    .data :
    {
        __etext = .;

        __data_start__ = .;
        *(vtable)
        *(.data*)

        . = ALIGN(4);
        /* preinit data */
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP(*(.preinit_array))
        PROVIDE_HIDDEN (__preinit_array_end = .);

        . = ALIGN(4);
        /* init data */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);

        . = ALIGN(4);
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE_HIDDEN (__fini_array_end = .);

        . = ALIGN(4);
        /* All data end */
        __data_end__ = .;

    }

    .bss :
    {
        . = ALIGN(4);
        __bss_start__ = .;
        *(.bss*)
        *(COMMON)
        *(COM)
        . = ALIGN(4);
        __bss_end__ = .;

    }

  /DISCARD/ :
  {
        *(.ARM.exidx)
  }

}

setup.c is simple and just has a few externs and forward decls, notably eventHandlerShim that calls into user code:

#include "pd_api.h"

typedef int (PDEventHandler)(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg);

extern PDEventHandler eventHandler;

static void* (*pdrealloc)(void* ptr, size_t size);

int eventHandlerShim(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
{
    if ( event == kEventInit )
        pdrealloc = playdate->system->realloc;

    return eventHandler(playdate, event, arg);
}

#if TARGET_PLAYDATE

void* _malloc_r(struct _reent* _REENT, size_t nbytes) { return pdrealloc(NULL,nbytes); }
void* _realloc_r(struct _reent* _REENT, void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); }
void _free_r(struct _reent* _REENT, void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); }

#else

void* malloc(size_t nbytes) { return pdrealloc(NULL,nbytes); }
void* realloc(void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); }
void  free(void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); }

#endif

I've been trying to figure out how to create a target.json (mostly copying the cortex-m7 target and modifying where needed. Currently running into undefined references to things like mmap, tinygo_scanCurrentStack, _write_r, _kill_r, etc.

Luckily there's a free simulator created by the playdate team that would let this be tested without the need for hardware. I can also volunteer my playdate console for testing as well.

aykevl commented 1 week ago

You would need to add support for the STM32F746 as well as specific board support for the Playdate. A PR you could use as an example, is this one: https://github.com/tinygo-org/tinygo/pull/4219