diff --git a/Makefile b/Makefile index 239a2dfa..659a7d53 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,7 @@ smoketest: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1 tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2 tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s + tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display ifneq ($(AVR), 0) tinygo build -size short -o test.elf -target=arduino examples/blinky1 tinygo build -size short -o test.elf -target=digispark examples/blinky1 diff --git a/src/examples/gba-display/gba-display.go b/src/examples/gba-display/gba-display.go new file mode 100644 index 00000000..5a16591e --- /dev/null +++ b/src/examples/gba-display/gba-display.go @@ -0,0 +1,21 @@ +package main + +// Draw a red square on the GameBoy Advance screen. + +import ( + "image/color" + "machine" +) + +var display = machine.Display + +func main() { + display.Configure() + + for x := int16(30); x < 50; x++ { + for y := int16(80); y < 100; y++ { + display.SetPixel(x, y, color.RGBA{255, 0, 0, 255}) + } + } + display.Display() +} diff --git a/src/machine/machine_gameboyadvance.go b/src/machine/machine_gameboyadvance.go new file mode 100644 index 00000000..7c6ffcb8 --- /dev/null +++ b/src/machine/machine_gameboyadvance.go @@ -0,0 +1,44 @@ +// +build gameboyadvance + +package machine + +import ( + "image/color" + "runtime/volatile" + "unsafe" +) + +// Make it easier to directly write to I/O RAM. +var ioram = (*[0x400]volatile.Register8)(unsafe.Pointer(uintptr(0x04000000))) + +type PinMode uint8 + +// Set has not been implemented. +func (p Pin) Set(value bool) { + // do nothing +} + +var Display = FramebufDisplay{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(0x06000000)))} + +type FramebufDisplay struct { + port *[160][240]volatile.Register16 +} + +func (d FramebufDisplay) Configure() { + // Write into the I/O registers, setting video display parameters. + ioram[0].Set(0x03) // Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) + ioram[1].Set(0x04) // Enable BG2 (BG0 = 1, BG1 = 2, BG2 = 4, ...) +} + +func (d FramebufDisplay) Size() (x, y int16) { + return 240, 160 +} + +func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) { + d.port[y][x].Set(uint16(c.R)&0x1f | uint16(c.G)&0x1f<<5 | uint16(c.B)&0x1f<<10) +} + +func (d FramebufDisplay) Display() error { + // Nothing to do here. + return nil +} diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go new file mode 100644 index 00000000..b2d1263d --- /dev/null +++ b/src/runtime/runtime_arm7tdmi.go @@ -0,0 +1,101 @@ +// +build arm7tdmi + +package runtime + +import ( + "device/arm" + "unsafe" +) + +const TargetBits = 32 + +const GOARCH = "arm" + +type timeUnit int64 + +const tickMicros = 1 + +func putchar(c byte) { + // dummy, TODO +} + +//go:extern _sbss +var _sbss unsafe.Pointer + +//go:extern _ebss +var _ebss unsafe.Pointer + +//go:extern _sdata +var _sdata unsafe.Pointer + +//go:extern _sidata +var _sidata unsafe.Pointer + +//go:extern _edata +var _edata unsafe.Pointer + +// Entry point for Go. Initialize all packages and call main.main(). +//go:export main +func main() { + // Initialize .data and .bss sections. + preinit() + + // Run initializers of all packages. + initAll() + + // Compiler-generated call to main.main(). + callMain() +} + +func preinit() { + // Initialize .bss: zero-initialized global variables. + ptr := unsafe.Pointer(&_sbss) + for ptr != unsafe.Pointer(&_ebss) { + *(*uint32)(ptr) = 0 + ptr = unsafe.Pointer(uintptr(ptr) + 4) + } + + // Initialize .data: global variables initialized from flash. + src := unsafe.Pointer(&_sidata) + dst := unsafe.Pointer(&_sdata) + for dst != unsafe.Pointer(&_edata) { + *(*uint32)(dst) = *(*uint32)(src) + dst = unsafe.Pointer(uintptr(dst) + 4) + src = unsafe.Pointer(uintptr(src) + 4) + } +} + +func ticks() timeUnit { + // TODO + return 0 +} + +const asyncScheduler = false + +func sleepTicks(d timeUnit) { + // TODO +} + +func abort() { + // TODO + for { + } +} + +func getCurrentStackPointer() uintptr { + return arm.ReadRegister("sp") +} + +// Implement memset for LLVM and compiler-rt. +//go:export memset +func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) { + for i := uintptr(0); i < size; i++ { + *(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c + } +} + +// Implement memmove for LLVM and compiler-rt. +//go:export memmove +func libc_memmove(dst, src unsafe.Pointer, size uintptr) { + memmove(dst, src, size) +} diff --git a/targets/gameboy-advance.json b/targets/gameboy-advance.json new file mode 100644 index 00000000..7b83183f --- /dev/null +++ b/targets/gameboy-advance.json @@ -0,0 +1,29 @@ +{ + "llvm-target": "thumb4-none-eabi", + "cpu": "arm7tdmi", + "build-tags": ["gameboyadvance", "arm7tdmi", "baremetal", "linux", "arm"], + "goos": "linux", + "goarch": "arm", + "compiler": "clang", + "linker": "ld.lld", + "cflags": [ + "-g", + "--target=thumb4-none-eabi", + "-mcpu=arm7tdmi", + "-Oz", + "-Werror", + "-fshort-enums", + "-Wno-macro-redefined", + "-Qunused-arguments", + "-fno-exceptions", "-fno-unwind-tables", + "-ffunction-sections", "-fdata-sections" + ], + "ldflags": [ + "--gc-sections", + "-Ttargets/gameboy-advance.ld" + ], + "extra-files": [ + "targets/gameboy-advance.s" + ], + "emulator": ["mgba-qt"] +} diff --git a/targets/gameboy-advance.ld b/targets/gameboy-advance.ld new file mode 100644 index 00000000..139e4c07 --- /dev/null +++ b/targets/gameboy-advance.ld @@ -0,0 +1,72 @@ +OUTPUT_ARCH(arm) +ENTRY(_start) + +MEMORY { + ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */ + iwram : ORIGIN = 0x03000000, LENGTH = 32K /* in-chip work RAM (faster) */ + rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */ +} + +__iwram_top = ORIGIN(iwram) + LENGTH(iwram);; +_stack_size = 3K; +__sp_irq = _stack_top; +__sp_usr = _stack_top - 1K; + +SECTIONS +{ + .text : + { + KEEP (*(.init)) + *(.text) + . = ALIGN(4); + } >rom + + /* Put the stack at the bottom of RAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ */ + .stack : + { + . = ALIGN(4); + . += _stack_size; + _stack_top = .; + } >iwram + + /* Start address (in flash) of .data, used by startup code. */ + _sidata = LOADADDR(.data); + + /* Globals with initial value */ + .data : + { + . = ALIGN(4); + _sdata = .; /* used by startup code */ + *(.data) + *(.data*) + *(.iwram .iwram.*) + . = ALIGN(4); + _edata = .; /* used by startup code */ + } >iwram AT>rom + + /* Zero-initialized globals */ + .bss : + { + . = ALIGN(4); + _sbss = .; /* used by startup code */ + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; /* used by startup code */ + } >iwram + + /DISCARD/ : + { + *(.ARM.exidx) /* causes 'no memory region specified' error in lld */ + *(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */ + } +} + +/* For the memory allocator. */ +_heap_start = ORIGIN(ewram); +_heap_end = ORIGIN(ewram) + LENGTH(ewram); +_globals_start = _sdata; +_globals_end = _ebss; diff --git a/targets/gameboy-advance.s b/targets/gameboy-advance.s new file mode 100644 index 00000000..d00ec879 --- /dev/null +++ b/targets/gameboy-advance.s @@ -0,0 +1,33 @@ +.section .init +.global _start +.align +.arm + +_start: + b start_vector + .fill 156,1,0 // Nintendo Logo Character Data (8000004h) + .fill 16,1,0 // Game Title + .byte 0x30,0x31 // Maker Code (80000B0h) + .byte 0x96 // Fixed Value (80000B2h) + .byte 0x00 // Main Unit Code (80000B3h) + .byte 0x00 // Device Type (80000B4h) + .fill 7,1,0 // unused + .byte 0x00 // Software Version No (80000BCh) + .byte 0xf0 // Complement Check (80000BDh) + .byte 0x00,0x00 // Checksum (80000BEh) + +start_vector: + mov r0, #0x4000000 // REG_BASE + str r0, [r0, #0x208] + + mov r0, #0x12 // Switch to IRQ Mode + msr cpsr, r0 + ldr sp, =__sp_irq // Set IRQ stack + mov r0, #0x1f // Switch to System Mode + msr cpsr, r0 + ldr sp, =__sp_usr // Set user stack + + // Jump to user code (switching to Thumb mode) + ldr r3, =main + bx r3 +