From 0c7265cf0de9d4ec95d28c5e103c00a63f4a1697 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Tue, 18 May 2021 16:40:24 +0200 Subject: Proof of concept of UXN on the GBA --- .gitignore | 1 + Makefile | 74 ++++++ src/bd-font.c | 130 +++++++++ src/bios_calls.s | 337 ++++++++++++++++++++++++ src/bitmap.h | 209 +++++++++++++++ src/common.h | 696 +++++++++++++++++++++++++++++++++++++++++++++++++ src/irs.s | 89 +++++++ src/main.c | 155 +++++++++++ src/shorthand.h | 36 +++ src/small-font.c | 45 ++++ src/sprites.h | 79 ++++++ src/text.h | 232 +++++++++++++++++ src/uxn/devices/ppu.c | 180 +++++++++++++ src/uxn/devices/ppu.h | 36 +++ src/uxn/roms/console.c | 5 + src/uxn/roms/dvd.c | 22 ++ src/uxn/uxn.c | 196 ++++++++++++++ src/uxn/uxn.h | 55 ++++ 18 files changed, 2577 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/bd-font.c create mode 100644 src/bios_calls.s create mode 100644 src/bitmap.h create mode 100644 src/common.h create mode 100644 src/irs.s create mode 100644 src/main.c create mode 100644 src/shorthand.h create mode 100644 src/small-font.c create mode 100644 src/sprites.h create mode 100644 src/text.h create mode 100644 src/uxn/devices/ppu.c create mode 100644 src/uxn/devices/ppu.h create mode 100644 src/uxn/roms/console.c create mode 100644 src/uxn/roms/dvd.c create mode 100644 src/uxn/uxn.c create mode 100644 src/uxn/uxn.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d7a5ec --- /dev/null +++ b/Makefile @@ -0,0 +1,74 @@ +.POSIX: +.SUFFIXES: + +# Path to the development kit (devkitARM) and the GBA library. +DEVKITPRO := /opt/devkitpro +DEVKITARM := /opt/devkitpro/devkitARM +PATH := $(DEVKITARM)/bin:$(PATH) +LIBGBA_DIR := $(DEVKITPRO)/libgba +LIBGBA_SRC := /opt/devkitpro/libgba/include/ +LIBGBA := $(LIBGBA_DIR)/lib/libgba.a +LIBGBA += $(LIBGBA_DIR)/lib/libfat.a +LIBGBA += $(LIBGBA_DIR)/lib/libmm.a + +# Source code location and files to watch for changes. +SRC_DIR := src +SRC_MAIN := $(SRC_DIR)/main.c +ASM_FILES := $(wildcard $(SRC_DIR)/*.s) +WATCH_SRC := $(wildcard $(SRC_DIR)/*.c) +WATCH_SRC += $(wildcard $(SRC_DIR)/*.h) +WATCH_SRC += $(wildcard $(SRC_DIR)/*.s) + +# Output library names and executables. +TARGET := uxngba +BUILD_DIR := build +ELF := $(BUILD_DIR)/$(TARGET).elf +BIN := $(BUILD_DIR)/$(TARGET).gba + +# Compiler and linker configuration. +CC := arm-none-eabi-gcc +OBJCOPY := arm-none-eabi-objcopy +ARCH := -mthumb -mthumb-interwork +SPECS := -specs=gba.specs +CFLAGS := -Wall -Wextra -pedantic -fno-strict-aliasing -Wno-incompatible-pointer-types +CFLAGS += -mcpu=arm7tdmi -mtune=arm7tdmi $(ARCH) +CFLAGS += -I$(LIBGBA_SRC) +LDFLAGS := $(ARCH) $(SPECS) +LDLIBS := $(LIBGBA) +RELEASE_CFLAGS := -DNDEBUG -O2 -g +DEBUG_CFLAGS := -DDEBUG -O2 -g + +.PHONY: clean run + +# Setup debug/release builds. +# make clean && make DEBUG=0 +# make clean && make DEBUG=1 +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + CFLAGS += $(DEBUG_CFLAGS) +else + CFLAGS += $(RELEASE_CFLAGS) +endif + +main: $(BUILD_DIR) $(BIN) + +# Strip and fix header to create final .gba file. +$(BIN): $(ELF) + $(OBJCOPY) -v -O binary $(ELF) $(BIN) + gbafix $(BIN) + +# Link files. +$(ELF): $(SRC_MAIN) $(WATCH_SRC) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(ELF) $(SRC_MAIN) $(ASM_FILES) $(LDLIBS) + +# Create build directory if needed. +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Test the output .gba in an emulator. +run: main + mgba-qt $(BIN) + +# Remove build directory. +clean: + rm -r $(BUILD_DIR) diff --git a/src/bd-font.c b/src/bd-font.c new file mode 100644 index 0000000..e3fb6cc --- /dev/null +++ b/src/bd-font.c @@ -0,0 +1,130 @@ +u32 bd_font[] = { + 0x00000000, 0x00000000, 0x00002400, 0x423c0000, + 0x00002400, 0x3c420000, 0x0000363e, 0x3e1c0800, + 0x00081c3e, 0x3e1c0800, 0x001c1c3e, 0x363e081c, + 0x00081c3e, 0x3e3e081c, 0x00000018, 0x18000000, + 0x7e7e7e66, 0x667e7e7e, 0x00001824, 0x24180000, + 0x7e7e665a, 0x5a667e7e, 0x00081c3e, 0x081c221c, + 0x001c221c, 0x08083e08, 0x00183828, 0x08080c0c, + 0x003c2424, 0x24343606, 0x00082208, 0x1c082208, + 0x040c1c3c, 0x1c0c0400, 0x2030383c, 0x38302000, + 0x081c3e08, 0x083e1c08, 0x00141414, 0x14140014, + 0x003c2a2a, 0x2c282828, 0x0038043c, 0x423c201e, + 0x00000000, 0x7e000000, 0x081c3e08, 0x3e1c083e, + 0x081c3e08, 0x08080808, 0x08080808, 0x083e1c08, + 0x00001030, 0x7e301000, 0x0000080c, 0x7e0c0800, + 0x00000000, 0x0002023e, 0x00002466, 0xff662400, + 0x0008081c, 0x1c3e3e00, 0x003e3e1c, 0x1c080800, + 0x00000000, 0x00000000, 0x00080808, 0x08080008, + 0x14141400, 0x00000000, 0x0000143e, 0x143e1400, + 0x00081c02, 0x1c201e08, 0x00002616, 0x08343200, + 0x00081408, 0x34122c00, 0x08080800, 0x00000000, + 0x08040404, 0x04040408, 0x08101010, 0x10101008, + 0x00001408, 0x3e081400, 0x00000808, 0x3e080800, + 0x00000000, 0x000c0804, 0x00000000, 0x3e000000, + 0x00000000, 0x000c0c00, 0x00203018, 0x0c060200, + 0x00001c22, 0x2a221c00, 0x00000c0a, 0x08083e00, + 0x00001c20, 0x1c023e00, 0x00001c22, 0x18221c00, + 0x00001018, 0x143e1000, 0x00003e02, 0x1e201e00, + 0x00001c02, 0x1e221c00, 0x00003e20, 0x10080400, + 0x00001c22, 0x1c221c00, 0x00001c22, 0x3c201c00, + 0x00000c0c, 0x000c0c00, 0x00000c0c, 0x000c0804, + 0x00100804, 0x04081000, 0x0000003e, 0x003e0000, + 0x00040810, 0x10080400, 0x001c2220, 0x10080008, + 0x001c322a, 0x1a021c00, 0x001c2222, 0x3e222200, + 0x001e221e, 0x22221e00, 0x001c2202, 0x02221c00, + 0x000e1222, 0x22120e00, 0x003e021e, 0x02023e00, + 0x003e021e, 0x02020200, 0x001c2202, 0x32221c00, + 0x00222222, 0x3e222200, 0x003e0808, 0x08083e00, + 0x00202020, 0x22221c00, 0x0022120a, 0x0e122200, + 0x00020202, 0x02023e00, 0x0022362a, 0x22222200, + 0x0022262a, 0x32222200, 0x001c2222, 0x22221c00, + 0x001e2222, 0x1e020200, 0x001c2222, 0x2a122c00, + 0x001e2222, 0x1e122200, 0x001c021c, 0x20221c00, + 0x003e0808, 0x08080800, 0x00222222, 0x22221c00, + 0x00222222, 0x22140800, 0x0022222a, 0x2a2a1400, + 0x00221408, 0x08142200, 0x00222214, 0x08080800, + 0x003e1008, 0x04023e00, 0x1c040404, 0x0404041c, + 0x0002060c, 0x18302000, 0x1c101010, 0x1010101c, + 0x08142200, 0x00000000, 0x00000000, 0x00003e00, + 0x00040810, 0x00000000, 0x00001c20, 0x3c223c00, + 0x0002021e, 0x22221e00, 0x00001c22, 0x02221c00, + 0x0020203c, 0x22223c00, 0x00001c22, 0x1e023c00, + 0x00003c02, 0x021e0202, 0x00003c22, 0x223c201c, + 0x0002021e, 0x22222200, 0x0008000c, 0x08083e00, + 0x00200020, 0x2020221c, 0x0002120a, 0x060a3200, + 0x000c0808, 0x08083e00, 0x0000162a, 0x2a2a2a00, + 0x00000e32, 0x22222200, 0x00001c22, 0x22221c00, + 0x00001e22, 0x221e0202, 0x00003c22, 0x223c2070, + 0x00001a26, 0x02020200, 0x00001c02, 0x1c201e00, + 0x00043e04, 0x04041800, 0x00002222, 0x22221c00, + 0x00002222, 0x22140800, 0x00002222, 0x2a2a1400, + 0x00002214, 0x08142200, 0x00002222, 0x223c201c, + 0x00003e10, 0x08043e00, 0x18040402, 0x02040418, + 0x00080808, 0x08080808, 0x0c101020, 0x2010100c, + 0x00002c1a, 0x00000000, 0x00000814, 0x22223e00, + 0x001c2202, 0x221c080e, 0x00140022, 0x2222221c, + 0x1008001c, 0x221e023c, 0x0814001c, 0x203c223c, + 0x0014001c, 0x203c223c, 0x0408001c, 0x203c223c, + 0x0814081c, 0x203c223c, 0x00001c02, 0x221c080e, + 0x0814001c, 0x221e023c, 0x0014001c, 0x221e023c, + 0x0408001c, 0x221e023c, 0x0014000c, 0x0808083e, + 0x0014000c, 0x0808083e, 0x0408000c, 0x0808083e, + 0x0014001c, 0x223e2222, 0x0814081c, 0x223e2222, + 0x1008003e, 0x021e023e, 0x00001628, 0x1c0a3400, + 0x003c0a1a, 0x0e0a3a00, 0x0814001c, 0x2222221c, + 0x0014001c, 0x2222221c, 0x0408001c, 0x2222221c, + 0x08140022, 0x2222221c, 0x04080022, 0x2222221c, + 0x00140022, 0x223c201c, 0x14001c22, 0x2222221c, + 0x14002222, 0x2222221c, 0x00081c22, 0x02221c08, + 0x0018240e, 0x04043e00, 0x00221408, 0x1c081c08, + 0x0038041e, 0x041e0438, 0x0030081c, 0x08080806, + 0x1008001c, 0x203c223c, 0x1008000c, 0x0808083e, + 0x1008001c, 0x2222221c, 0x10080022, 0x2222221c, + 0x2c1a000e, 0x32222222, 0x2c1a0022, 0x262a3222, + 0x001c122c, 0x001e0000, 0x000c120c, 0x001e0000, + 0x08000804, 0x02221c00, 0x0000003e, 0x02020000, + 0x0000003e, 0x20200000, 0x06241468, 0x4422f000, + 0x06241468, 0x54f24000, 0x08000808, 0x08080800, + 0x00482412, 0x24480000, 0x00122448, 0x24120000, + 0x00880022, 0x00880022, 0x55885522, 0x55885522, + 0x55aa55aa, 0x55aa55aa, 0x08080808, 0x08080808, + 0x0808080f, 0x08080808, 0x08080f08, 0x0f080808, + 0x14141417, 0x14141414, 0x0000001f, 0x14141414, + 0x00000f08, 0x0f080808, 0x14141710, 0x17141414, + 0x14141414, 0x14141414, 0x00001f10, 0x17141414, + 0x14141710, 0x1f000000, 0x1414141f, 0x00000000, + 0x08080f08, 0x0f000000, 0x0000000f, 0x08080808, + 0x080808f8, 0x00000000, 0x080808ff, 0x00000000, + 0x000000ff, 0x08080808, 0x080808f8, 0x08080808, + 0x000000ff, 0x00000000, 0x080808ff, 0x08080808, + 0x0808f808, 0xf8080808, 0x141414f4, 0x14141414, + 0x1414f404, 0xfc000000, 0x0000fc04, 0xf4141414, + 0x1414f700, 0xff000000, 0x0000ff00, 0xf7141414, + 0x1414f404, 0xf4141414, 0x0000ff00, 0xff000000, + 0x1414f700, 0xf7141414, 0x0808ff00, 0xff000000, + 0x141414ff, 0x00000000, 0x0000ff00, 0xff080808, + 0x000000ff, 0x14141414, 0x141414fc, 0x00000000, + 0x0808f808, 0xf8000000, 0x0000f808, 0xf8080808, + 0x000000fc, 0x14141414, 0x141414ff, 0x14141414, + 0x0808ff08, 0xff080808, 0x0808080f, 0x00000000, + 0x000000f8, 0x08080808, 0xffffffff, 0xffffffff, + 0x00000000, 0xffffffff, 0x0f0f0f0f, 0x0f0f0f0f, + 0xf0f0f0f0, 0xf0f0f0f0, 0xffffffff, 0x00000000, + 0x00002c12, 0x12122c00, 0x000c120a, 0x12223a02, + 0x003e2202, 0x02020200, 0x00003e14, 0x14141400, + 0x3e220408, 0x04223e00, 0x00003c12, 0x12120c00, + 0x00002222, 0x221e0202, 0x002c1a08, 0x08281000, + 0x1c081c22, 0x221c081c, 0x1c22223e, 0x22221c00, + 0x1c222222, 0x22143600, 0x1e041824, 0x22221c00, + 0x0000142a, 0x2a140000, 0x0020142a, 0x2a140200, + 0x00003c02, 0x1c023c00, 0x001c2222, 0x22222202, + 0x00003e00, 0x3e003e00, 0x0000081c, 0x08001c00, + 0x04081008, 0x04001c00, 0x10080408, 0x10001c00, + 0x30480808, 0x08080808, 0x08080808, 0x08080906, + 0x00000800, 0x3e000800, 0x00004c32, 0x004c3200, + 0x00182418, 0x00000000, 0x00001c1c, 0x1c000000, + 0x00000018, 0x18000000, 0x00f01010, 0x10121418, + 0x0c342424, 0x00000000, 0x1810083c, 0x00000000, + 0x00003c3c, 0x3c3c0000, 0x00000000, 0x00000000, +}; diff --git a/src/bios_calls.s b/src/bios_calls.s new file mode 100644 index 0000000..740fa02 --- /dev/null +++ b/src/bios_calls.s @@ -0,0 +1,337 @@ +@ +@ Arithmetic functions. +@ + +@ Division. + .text + .code 16 + .align 2 + .global bios_div + .thumb_func +bios_div: + swi 0x06 + bx lr + +@ Square root. + .text + .code 16 + .align 2 + .global bios_sqrt + .thumb_func +bios_sqrt: + swi 0x08 + bx lr + +@ Arc-tangent. + .text + .code 16 + .align 2 + .global bios_atan + .thumb_func +bios_atan: + swi 0x09 + bx lr + +@ Arc-tangent2. + .text + .code 16 + .align 2 + .global bios_atan2 + .thumb_func +bios_atan2: + swi 0x0a + bx lr + +@ +@ Rotation/Scaling. +@ + +@ BG Affine Set. + .text + .code 16 + .align 2 + .global bios_bg_affine_set + .thumb_func +bios_bg_affine_set: + swi 0x0e + bx lr + +@ OBJ Affine Set. + .text + .code 16 + .align 2 + .global bios_obj_affine_set + .thumb_func +bios_obj_affine_set: + swi 0x0f + bx lr + +@ +@ (De)compression. +@ + +@ Bit unpacking. + .text + .code 16 + .align 2 + .global bios_bit_unpack + .thumb_func +bios_bit_unpack: + swi 0x10 + bx lr + +@ Huffman decompression. + .text + .code 16 + .align 2 + .global bios_huff_expand + .thumb_func +bios_huff_expand: + swi 0x13 + bx lr + +@ LZ77 decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_wram + .thumb_func +bios_lz77_expand_wram: + swi 0x11 + bx lr + +@ LZ77 decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_vram + .thumb_func +bios_lz77_expand_vram: + swi 0x12 + bx lr + +@ Run length encoding decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_wram + .thumb_func +bios_rle_expand_wram: + swi 0x14 + bx lr + +@ Run length encoding decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_vram + .thumb_func +bios_rle_expand_vram: + swi 0x15 + bx lr + +@ +@ Memory copy. +@ + +@ Memcopy/memfill in 32 byte units (Fast). + .text + .code 16 + .align 2 + .global bios_memcopy_32 + .thumb_func +bios_memcopy_32: + swi 0x0C + bx lr + +@ Memcopy/memfill in 4 / 2 byte units (Slow). + .text + .code 16 + .align 2 + .global bios_memcopy_8 + .thumb_func +bios_memcopy_8: + swi 0x0B + bx lr + +@ +@ Sound functions. +@ + +@ MIDI key to frequency. + .text + .code 16 + .align 2 + .global bios_midi2freq + .thumb_func +bios_midi2freq: + swi 0x1f + bx lr + +@ Sound bias. + .text + .code 16 + .align 2 + .global bios_sound_bias + .thumb_func +bios_sound_bias: + swi 0x19 + bx lr + +@ Sound channels clear. + .text + .code 16 + .align 2 + .global bios_sound_clear + .thumb_func +bios_sound_clear: + swi 0x1e + bx lr + +@ Sound driver initialization. + .text + .code 16 + .align 2 + .global bios_sound_init + .thumb_func +bios_sound_init: + swi 0x1a + bx lr + +@ Sound main function. To be called each 1/60 of a second after the sound VSync. + .text + .code 16 + .align 2 + .global bios_sound_main + .thumb_func +bios_sound_main: + swi 0x1c + bx lr + +@ Sound driver operation mode. + .text + .code 16 + .align 2 + .global bios_sound_mode + .thumb_func +bios_sound_mode: + swi 0x1b + bx lr + +@ Sound VSync. Called just after the VBlank interrupt each 1/60 of a second. + .text + .code 16 + .align 2 + .global bios_sound_vsync + .thumb_func +bios_sound_vsync: + swi 0x1d + bx lr + +@ Sound VSync off In case of issues manually call this to stop the sound. + .text + .code 16 + .align 2 + .global bios_sound_vsync_off + .thumb_func +bios_sound_vsync_off: + swi 0x28 + bx lr + +@ +@ Halt/Reset functions. +@ + +@ Halt until interrupt request. + .text + .code 16 + .align 2 + .global bios_halt + .thumb_func +bios_halt: + swi 0x02 + bx lr + +@ Halt until one of the specified interrupts occur. + .text + .code 16 + .align 2 + .global bios_interrupt_wait + .thumb_func +bios_interrupt_wait: + swi 0x04 + bx lr + +@ Halt until the VBlank interrupt occurs. + .text + .code 16 + .align 2 + .global bios_vblank_wait + .thumb_func +bios_vblank_wait: + swi 0x05 + bx lr + +@ Stop. Switches GBA to low power mode. + .text + .code 16 + .align 2 + .global bios_stop + .thumb_func +bios_stop: + swi 0x03 + bx lr + +@ Soft reset. + .text + .code 16 + .align 2 + .global bios_soft_reset + .thumb_func +bios_soft_reset: + swi 0x00 + bx lr + +@ Register RAM reset. + .text + .code 16 + .align 2 + .global bios_regram_reset + .thumb_func +bios_regram_reset: + swi 0x01 + bx lr + +@ Hard reset. + .text + .code 16 + .align 2 + .global bios_hard_reset + .thumb_func +bios_hard_reset: + swi 0x26 + bx lr + +@ +@ Misc functions. +@ + +@ BIOS checksum. + .text + .code 16 + .align 2 + .global bios_bios_checksum + .thumb_func +bios_bios_checksum: + swi 0x0d + bx lr + +@ MultiBoot. + .text + .code 16 + .align 2 + .global bios_multiboot + .thumb_func +bios_multiboot: + swi 0x25 + bx lr diff --git a/src/bitmap.h b/src/bitmap.h new file mode 100644 index 0000000..2e61f0e --- /dev/null +++ b/src/bitmap.h @@ -0,0 +1,209 @@ +#ifndef GBAEXP_BITMAP_H +#define GBAEXP_BITMAP_H + +#include "common.h" + +// Draws a line with the given color between (x0,y0) and (x1,y1) using the +// Bresenham's line drawing algorithm using exclusively integer arithmetic. +static void +draw_line(int x0, int y0, int x1, int y1, Color clr) { + // Pointer to the initial position of the screen buffer where we will start + // writing our data. + vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0); + + // Adjust the step direction and calculate deltas. + int x_step; + int y_step; + int dx; + int dy; + if (x0 > x1) { + x_step = -1; + dx = x0 - x1; + } else { + x_step = 1; + dx = x1 - x0; + } + if (y0 > y1) { + y_step = -SCREEN_WIDTH; + dy = y0 - y1; + } else { + y_step = +SCREEN_WIDTH; + dy = y1 - y0; + } + + if(dy == 0) { + // Horizontal line. + for(int i = 0; i <= dx; i++) { + destination[i * x_step] = clr; + } + } else if(dx == 0) { + // Vertical line. + for(int i = 0; i <= dy; i++) { + destination[i * y_step] = clr; + } + } else if (dx >= dy){ + // Positive slope. + int diff = 2 * dy - dx; + for (int i = 0; i <= dx; ++i) { + *destination = clr; + if (diff >= 0) { + destination += y_step; + diff -= 2 * dx; + } + destination += x_step; + diff += 2 * dy; + } + } else { + // Negative slope. + int diff = 2 * dx - dy; + for (int i = 0; i <= dy; ++i) { + *destination = clr; + if (diff >= 0) { + destination += x_step; + diff -= 2 * dy; + } + destination += y_step; + diff += 2 * dx; + } + } +} + +static inline void +draw_rect(int x0, int y0, int x1, int y1, Color clr) { + if (x0 > x1) { + int tmp = x0; + x0 = x1; + x1 = tmp; + } + if (y0 > y1) { + int tmp = y0; + y0 = y1; + y1 = tmp; + } + int dx = x1 - x0; + int dy = y1 - y0; + for (int i = 0; i <= dx; ++i) { + int x = x0 + i; + FRAMEBUFFER[y0][x] = clr; + FRAMEBUFFER[y1][x] = clr; + } + for (int j = 0; j <= dy; ++j) { + int y = y0 + j; + FRAMEBUFFER[y][x0] = clr; + FRAMEBUFFER[y][x1] = clr; + } +} + +static inline void +draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) { + if (x0 > x1) { + int tmp = x0; + x0 = x1; + x1 = tmp; + } + if (y0 > y1) { + int tmp = y0; + y0 = y1; + y1 = tmp; + } + int dx = x1 - x0; + int dy = y1 - y0; + for (int i = 0; i <= dx; ++i) { + for (int j = 0; j <= dy; ++j) { + int x = x0 + i; + int y = y0 + j; + FRAMEBUFFER[y][x] = clr; + } + } +} + +// In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the +// color directly, instead the color is stored in the palette memory at +// `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This +// plotter takes an index to a color stored in MEM_PAL[col_index]. Because the +// GBA needs to meet memory alignment requirements, we can't write a u8 into +// memory, instead we need to read a u16 word, mask and or the corresponding +// bits and wave the updated u16. +static inline void +put_pixel_m4(int x, int y, u8 clr_idx, vu16 *buffer) { + int buffer_index = (y * SCREEN_WIDTH + x) / 2; + vu16 *destination = &buffer[buffer_index]; + // Odd pixels will go to the top 8 bits of the destination. Even pixels to + // the lower 8 bits. + int odd = x & 0x1; + if(odd) { + *destination= (*destination & 0xFF) | (clr_idx << 8); + } else { + *destination= (*destination & ~0xFF) | clr_idx; + } +} + +static inline void +put_pixel_m3(int x, int y, u16 color, Scanline *buffer) { + buffer[y][x] = color; +} + +static inline void +clear_screen_m4() { + size_t size = SCREEN_WIDTH * SCREEN_HEIGHT / 4; + u32 *buf = backbuffer; + for (size_t i = 0; i < size; ++i) { + buf[i] = 0; + } +} + +static inline void +clear_screen_m3() { + size_t size = SCREEN_WIDTH * SCREEN_HEIGHT / 4; + u32 *buf = FRAMEBUFFER; + for (size_t i = 0; i < size; ++i) { + buf[i] = 0; + } +} + +static inline void +draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) { + int ix, iy; + for(iy = y0; iy < y1; iy++) { + for(ix = x0; ix < x1; ix++) { + put_pixel_m4(ix, iy, col_index, buffer); + } + } +} + +void +draw_logo(void) { + int side = 60; + int line = 35; + int height = side * 0.5; + int x = SCREEN_WIDTH / 2 - height / 2; + int y = SCREEN_HEIGHT / 2; + + // Draw red triangle. + draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED); + draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED); + draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED); + draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED); + + // Draw white triangle. + draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE); + draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE); + draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE); + + // Draw white line at triangle tip. + draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE); + draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE); + + // Double triangle line. + draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE); + draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE); + draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE); + + // Draw white lines. + draw_line(x - line, y, x, y, COLOR_WHITE); + draw_line(x + height, y, x + height + line, y, COLOR_WHITE); + draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE); + draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE); +} + +#endif // GBAEXP_BITMAP_H diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..aeea1a6 --- /dev/null +++ b/src/common.h @@ -0,0 +1,696 @@ +#ifndef GBAEXP_COMMON_H +#define GBAEXP_COMMON_H + +#include "shorthand.h" + +// +// Memory sections. +// + +// Defines for the different memory sections in the GBA. +#define MEM_SROM 0x00000000 +#define MEM_EW 0x02000000 +#define MEM_IW 0x03000000 +#define MEM_IO 0x04000000 +#define MEM_PAL 0x05000000 +#define MEM_VRAM 0x06000000 +#define MEM_OAM 0x07000000 +#define MEM_PAK 0x08000000 +#define MEM_CART 0x0E000000 + +// +// Display modes. +// + +// Display registers. +#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) +#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) +#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) + +// The first three bits in the DISP_CTRL are used to control the video mode. +#define DISP_MODE_0 0x0000 +#define DISP_MODE_1 0x0001 +#define DISP_MODE_2 0x0002 +#define DISP_MODE_3 0x0003 +#define DISP_MODE_4 0x0004 +#define DISP_MODE_5 0x0005 +#define DISP_GB (1 << 3) +#define DISP_PAGE (1 << 4) +#define DISP_OAM_HBLANK (1 << 5) +#define DISP_OBJ_1D (1 << 6) +#define DISP_BLANK (1 << 7) +#define DISP_BG_0 (1 << 8) +#define DISP_BG_1 (1 << 9) +#define DISP_BG_2 (1 << 10) +#define DISP_BG_3 (1 << 11) +#define DISP_OBJ (1 << 12) +#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D + +// These bits are used to control the DISP_STATUS register. +#define DISP_VBLANK_STATUS (1 << 0x0) +#define DISP_HBLANK_STATUS (1 << 0x1) +#define DISP_VCOUNT_STATUS (1 << 0x2) +#define DISP_VBLANK_IRQ (1 << 0x3) +#define DISP_HBLANK_IRQ (1 << 0x4) +#define DISP_VCOUNT_IRQ (1 << 0x5) +#define DISP_VCOUNT_TRIGGER(N) ((N) << 0x8) + +// Registers to control of BG layers. +#define BG_CTRL(N) *((vu16*)(0x04000008 + 0x0002 * (N))) + +// Bits to control the background. +#define BG_PRIORITY(N) ((N) & 0x3) +#define BG_CHARBLOCK(N) ((N) << 2) +#define BG_MOSAIC (1 << 6) +#define BG_HIGH_COLOR (1 << 7) +#define BG_SCREENBLOCK(N) ((N) << 8) +#define BG_AFFINE (1 << 0xD) +#define BG_SIZE(N) ((N) << 0xE) + +// BG registers for horizontal displacement. +#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) +#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) +#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) +#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) + +// BG registers for vertical displacement. +#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) +#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) +#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) +#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) + +// Screen settings. +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 160 + +// +// Colors. +// + +// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component +// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). +typedef u16 Color; + +// A palette is composed of 16 colors, with color at index 0 being transparent +// for sprites. +typedef Color Palette[16]; + +// Inline function to calculate the 15 bit color value. +static inline Color +rgb15(u32 red, u32 green, u32 blue ) { + return (blue << 10) | (green << 5) | red; +} + +// Some nice default colors. +#define COLOR_RED rgb15(31, 0, 12) +#define COLOR_BLUE rgb15(2, 15, 30) +#define COLOR_CYAN rgb15(0, 30, 30) +#define COLOR_GREY rgb15(12, 12, 12) +#define COLOR_BLACK rgb15(0, 0, 0) +#define COLOR_WHITE rgb15(28, 28, 28) + +// +// Tile memory access. +// + +// NOTE: Only defining 4bpp tiles for now. +typedef struct Tile { + u32 row[8]; +} Tile; + +// Screenblocks and charblocks (tile blocks). +typedef Tile TileBlock[512]; +#define TILE_MEM ((TileBlock*) MEM_VRAM) +typedef u16 ScreenBlock[1024]; +#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) + +// Screenblock entry bits. +#define SCREENBLOCK_ENTRY_H_FLIP (1 << 0xA) +#define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB) +#define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC) + +size_t +se_index(size_t tile_x, size_t tile_y, size_t map_width) { + size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5)); + return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32); +} + +// We can treat the screen as a HxW matrix. With the following macro we can +// write a pixel to the screen at the (x, y) position using: +// +// FRAMEBUFFER[y][x] = color; +// +typedef Color Scanline[SCREEN_WIDTH]; +#define FRAMEBUFFER ((Scanline*) MEM_VRAM) +#define SCREEN_BUFFER ((u16*) MEM_VRAM) +#define PAL_BUFFER_BG ((u16*) MEM_PAL) +#define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200)) +#define PAL_BANK_BG ((Palette*) MEM_PAL) +#define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200)) +static vu16 *backbuffer = ((vu16*)(MEM_VRAM + 0x0A000)); + +// +// Sprites. +// + +// Using macros instead of aligned structs for setting up OBJ attributes and +// affine parameters. +// TODO: Benchmark if this would be slower or the same that TONC's +// implementation. +// TODO: Cleanup OBJ/OAM memory copying and access. +#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) +#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) +#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) +#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) +#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) +#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) +#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) + +// OBJ_ATTR_0 parameters +#define OBJ_Y_COORD(N) ((N) & 0xFF) +#define OBJ_NORMAL (0x00 << 0x8) +#define OBJ_AFFINE (0x01 << 0x8) +#define OBJ_HIDDEN (0x02 << 0x8) +#define OBJ_AFFINE_2X (0x03 << 0x8) +#define OBJ_ALPHA_BLEND (0x01 << 0xA) +#define OBJ_WINDOW (0x02 << 0xA) +#define OBJ_SHAPE_SQUARE (0x00 << 0xE) +#define OBJ_SHAPE_WIDE (0x01 << 0xE) +#define OBJ_SHAPE_TALL (0x02 << 0xE) + +// OBJ_ATTR_1 parameters +#define OBJ_X_COORD(N) ((N) & 0x1FF) +#define OBJ_AFFINE_IDX(N) ((N) << 0x9) +#define OBJ_H_FLIP (0x01 << 0xC) +#define OBJ_V_FLIP (0x01 << 0xD) +#define OBJ_SIZE_SMALL (0x00 << 0xE) +#define OBJ_SIZE_MID (0x01 << 0xE) +#define OBJ_SIZE_BIG (0x02 << 0xE) +#define OBJ_SIZE_HUGE (0x03 << 0xE) + +// OBJ_ATTR_2 parameters +#define OBJ_TILE_INDEX(N) ((N) & 0x3FF) +#define OBJ_PRIORITY(N) ((N) << 0xA) +#define OBJ_PAL_BANK(N) ((N) << 0xC) + +// +// Mode 4 page flipping +// + +static inline void +flip_page(void) { + backbuffer = (u16*)((u32)backbuffer ^ 0x0A000); + DISP_CTRL ^= DISP_PAGE; +} + +#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) +#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) + +// +// Profiling. +// + +#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) +#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) +#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) +#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) +#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) +#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) +#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) +#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) + +// Timer control bits. +#define TIMER_CTRL_FREQ_0 0 +#define TIMER_CTRL_FREQ_1 1 +#define TIMER_CTRL_FREQ_2 2 +#define TIMER_CTRL_FREQ_3 3 +#define TIMER_CTRL_CASCADE (1 << 2) +#define TIMER_CTRL_IRQ (1 << 6) +#define TIMER_CTRL_ENABLE (1 << 7) +#define TIMER_CTRL_DISABLE (0 << 7) + +// We use timers 2 and 3 to count the number of cycles since the profile_start +// functions is called. Don't use if the code we are trying to profile make use +// of these timers. +static inline +void profile_start(void) { + TIMER_DATA_2 = 0; + TIMER_DATA_3 = 0; + TIMER_CTRL_2 = 0; + TIMER_CTRL_3 = 0; + TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; + TIMER_CTRL_2 = TIMER_CTRL_ENABLE; +} + +static inline +u32 profile_stop(void) { + TIMER_CTRL_2 = 0; + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +// +// Input handling. +// + +// Memory address for key input and control register +#define KEY_INPUTS *((vu16*) 0x04000130) +#define KEY_CTRL *((vu16*) 0x04000132) + +// Key control register bits. +#define KEY_IRQ_KEY(N) (N) +#define KEY_IRQ (1 << 0xE) +#define KEY_IRQ_IF_SET (1 << 0xF) + +// Alias for key pressing bits. +#define KEY_A (1 << 0) +#define KEY_B (1 << 1) +#define KEY_SELECT (1 << 2) +#define KEY_START (1 << 3) +#define KEY_RIGHT (1 << 4) +#define KEY_LEFT (1 << 5) +#define KEY_UP (1 << 6) +#define KEY_DOWN (1 << 7) +#define KEY_R (1 << 8) +#define KEY_L (1 << 9) + +#define KEY_MASK 0x03FF + +// Saving the previous and current key states as globals for now. +static u16 key_curr = 0; +static u16 key_prev = 0; + +static inline void +poll_keys(void) { + key_prev = key_curr; + key_curr = ~KEY_INPUTS & KEY_MASK; +} + +// Returns true if the given key has been pressed at time of calling and was not +// pressed since the previous call. For example, if a key is being held, this +// function will return `true` only on the frame where the key initially +// activated. +static inline u32 +key_pressed(u32 key) { + return (key_curr & key) & ~(key_prev & key); +} + +// Check if the given key is pressed and has been since at least one frame. +static inline u32 +key_hold(u32 key) { + return (key_curr & key) & key_prev & key; +} + +// Check if the given key/button is currently pressed. +#define KEY_PRESSED(key) (~(KEY_INPUTS) & key) + +// Back/unpack bits. +static inline u32 +unpack_1bb(u8 hex) { + const u32 conversion_u32[16] = { + 0x00000000, 0x00000001, 0x00000010, 0x00000011, + 0x00000100, 0x00000101, 0x00000110, 0x00000111, + 0x00001000, 0x00001001, 0x00001010, 0x00001011, + 0x00001100, 0x00001101, 0x00001110, 0x00001111, + }; + u8 low = hex & 0xF; + u8 high = (hex >> 4) & 0xF; + return (conversion_u32[high] << 16) | conversion_u32[low]; +} + +// Unpack N tiles packed at 1bpp. +static inline void +unpack_tiles(u32 *src, u32 *dst, size_t n_tiles) { + u32 *target_src = src + n_tiles * 2; + while (src != target_src) { + *dst++ = unpack_1bb((*src >> 24) & 0xFF); + *dst++ = unpack_1bb((*src >> 16) & 0xFF); + *dst++ = unpack_1bb((*src >> 8) & 0xFF); + *dst++ = unpack_1bb(*src & 0xFF); + src++; + } +} + +// +// Direct Memory Access (DMA) +// + + +// Source, destination, and control registers. +#define DMA_SRC(N) *((vu32*) 0x040000B0 + (N) * 12) +#define DMA_DST(N) *((vu32*) 0x040000B4 + (N) * 12) +#define DMA_CTRL(N) *((vu32*) 0x040000B8 + (N) * 12) + +// DMA control bits. +#define DMA_DST_INC (0 << 0x15) +#define DMA_DST_DEC (1 << 0x15) +#define DMA_DST_FIXED (2 << 0x15) +#define DMA_DST_RELOAD (3 << 0x15) +#define DMA_SRC_INC (0 << 0x17) +#define DMA_SRC_DEC (1 << 0x17) +#define DMA_SRC_FIXED (2 << 0x17) +#define DMA_REPEAT (1 << 0x19) +#define DMA_CHUNK_16 (0 << 0x1A) +#define DMA_CHUNK_32 (1 << 0x1A) +#define DMA_NOW (0 << 0x1C) +#define DMA_VBLANK (1 << 0x1C) +#define DMA_HBLANK (2 << 0x1C) +#define DMA_REFRESH (3 << 0x1C) +#define DMA_IRQ (1 << 0x1E) +#define DMA_ENABLE (1 << 0x1F) + +// Custom struct for cleaner DMA transfer functions. +typedef struct DmaStr { + const void *src; + void *dst; + u32 ctrl; +} DmaStr; + +#define DMA_TRANSFER ((volatile DmaStr*) 0x040000B0) + +// Transfer `count` number of chunks from src to dst using a DMA channel. Note +// that chunks are not bytes, but instead configured based on bits set by +// DMA_CTRL. +inline void +dma_transfer_copy(void *dst, const void *src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options; +} + +inline void +dma_transfer_fill(void *dst, volatile u32 src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = (const void *)&src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options | DMA_SRC_FIXED; +} + +// Copy N number of bytes using a DMA channel. +inline void +dma_copy(void *dst, const void *src, u32 size, int channel) { + dma_transfer_copy(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// Fill the dst location with the word set at src. +inline void +dma_fill(void *dst, const void *src, u32 size, int channel) { + dma_transfer_fill(dst, (volatile u32)src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// +// Interrupts. +// + +#define IRQ_ENABLE *((vu16*) 0x04000200) +#define IRQ_ACK *((vu16*) 0x04000202) +#define IRQ_CTRL *((vu16*) 0x04000208) +#define IRQ_ACK_BIOS *((vu16*) 0x03007FF8) + +typedef enum { + IRQ_VBLANK, + IRQ_HBLANK, + IRQ_VCOUNT, + IRQ_TIMER_0, + IRQ_TIMER_1, + IRQ_TIMER_2, + IRQ_TIMER_3, + IRQ_SERIAL, + IRQ_DMA_0, + IRQ_DMA_1, + IRQ_DMA_2, + IRQ_DMA_3, + IRQ_KEYPAD, + IRQ_GAMEPAK, +} IrqIndex; + +typedef void (*IrsFunc)(void); + +IrsFunc irs_table[] = { + [IRQ_VBLANK ] = NULL, + [IRQ_HBLANK ] = NULL, + [IRQ_VCOUNT ] = NULL, + [IRQ_TIMER_0] = NULL, + [IRQ_TIMER_1] = NULL, + [IRQ_TIMER_2] = NULL, + [IRQ_TIMER_3] = NULL, + [IRQ_SERIAL ] = NULL, + [IRQ_DMA_0 ] = NULL, + [IRQ_DMA_1 ] = NULL, + [IRQ_DMA_2 ] = NULL, + [IRQ_DMA_3 ] = NULL, + [IRQ_KEYPAD ] = NULL, + [IRQ_GAMEPAK] = NULL, +}; + +void +irq_enable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS |= DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS |= DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS |= DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 |= TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) |= DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) |= DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) |= DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) |= DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL |= KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE |= (1 << idx); +} + +void +irq_disable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS &= ~DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS &= ~DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS &= ~DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) &= ~DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) &= ~DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) &= ~DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) &= ~DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL &= ~KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE &= ~(1 << idx); +} + +void +irs_set(IrqIndex idx, IrsFunc func) { + // Store IRQ_CTRL status and disable interrupts for now. + u16 irq_ctrl = IRQ_CTRL; + IRQ_CTRL = 0; + + // Update the IRS table and enable/disable the given IRQ. + irs_table[idx] = func; + if (func == NULL) { + irq_disable(idx); + } else { + irq_enable(idx); + } + + // Restore previous irq_ctrl. + IRQ_CTRL = irq_ctrl; +} + +// External irs_main function, has to be written in ARM assembly. +void irs_main(void); + +// Initialize the function pointer for the main IRS routine written in ARM +// assembly and enable interrupts. +#define IRS_MAIN *(IrsFunc*)(0x03007FFC) +void +irq_init() { + IRS_MAIN = irs_main; + IRQ_CTRL = 1; +} + +// Stub function pointer needed for when we want to enable interrupts that don't +// require a custom function, such as for the BIOS VSync. +void +irs_stub() {} + +// +// BIOS function declarations. +// + +// These functions declarations can be used to call the BIOS functions from the +// asm code. +int bios_vblank_wait(); +int bios_div(int num, int denom); + +// +// Sound. +// + +// Sound registers. +#define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60)) +#define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62)) +#define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64)) +#define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68)) +#define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C)) +#define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70)) +#define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72)) +#define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74)) +#define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78)) +#define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C)) +#define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80)) +#define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82)) +#define SOUND_STATUS *((vu16*)(MEM_IO + 0x84)) +#define SOUND_BIAS *((vu16*)(MEM_IO + 0x88)) + +// Sound DMG master bits. +#define SOUND_VOLUME_LEFT(N) (N) +#define SOUND_VOLUME_RIGHT(N) ((N) << 4) +#define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8) +#define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9) +#define SOUND_ENABLE_WAVE_LEFT (1 << 0xA) +#define SOUND_ENABLE_NOISE_LEFT (1 << 0xB) +#define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC) +#define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD) +#define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE) +#define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF) + +typedef enum { + SOUND_SQUARE1 = (0x1 << 0), + SOUND_SQUARE2 = (0x1 << 1), + SOUND_WAVE = (0x1 << 2), + SOUND_NOISE = (0x1 << 3), +} SoundChannel; + +u16 +sound_volume(SoundChannel channels, u8 volume) { + volume = volume & 0x7; + channels = channels & 0xF; + return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC); +} + +// Sound Direct Sound master bits. +#define SOUND_DMG25 0x0 +#define SOUND_DMG50 0x1 +#define SOUND_DMG100 0x2 +#define SOUND_DSOUND_RATIO_A (1 << 0x2) +#define SOUND_DSOUND_RATIO_B (1 << 0x3) +#define SOUND_DSOUND_LEFT_A (1 << 0x8) +#define SOUND_DSOUND_RIGHT_A (1 << 0x9) +#define SOUND_DSOUND_TIMER_A (1 << 0xA) +#define SOUND_DSOUND_RESET_A (1 << 0xB) +#define SOUND_DSOUND_LEFT_B (1 << 0xC) +#define SOUND_DSOUND_RIGHT_B (1 << 0xD) +#define SOUND_DSOUND_TIMER_B (1 << 0xE) +#define SOUND_DSOUND_RESET_B (1 << 0xF) + +// Sound status bits. +#define SOUND_ENABLE (1 << 0x7) + +// DMG square control bits. +#define SOUND_SQUARE_LEN(N) (N) +#define SOUND_SQUARE_DUTY(N) ((N) << 0x6) +#define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8) +#define SOUND_SQUARE_ENV_DIR(N) ((N) << 0xB) +#define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC) + +// DMG square 1 sweep control bits. +#define SOUND_SWEEP_NUMBER(N) (N) +#define SOUND_SWEEP_DIR(N) ((N) << 0x3) +#define SOUND_SWEEP_TIME(N) ((N) << 0x4) + +// DMG frequency bits (Square/Wave). +#define SOUND_FREQ_TIMED (1 << 0xE) +#define SOUND_FREQ_RESET (1 << 0xF) + +// DMG wave ram. +#define SOUND_WAVE_RAM_0 *((vu32*)(MEM_IO + 0x90)) +#define SOUND_WAVE_RAM_1 *((vu32*)(MEM_IO + 0x94)) +#define SOUND_WAVE_RAM_2 *((vu32*)(MEM_IO + 0x98)) +#define SOUND_WAVE_RAM_3 *((vu32*)(MEM_IO + 0x9C)) + +// DMG wave control bits. +#define SOUND_WAVE_LENGTH(N) (N) +#define SOUND_WAVE_MUTE 0x0 +#define SOUND_WAVE_VOL_100 (0x1 << 0xD) +#define SOUND_WAVE_VOL_75 (0x4 << 0xD) +#define SOUND_WAVE_VOL_50 (0x2 << 0xD) +#define SOUND_WAVE_VOL_25 (0x3 << 0xD) + +// DMG wave mode bits. +#define SOUND_WAVE_BANK_MODE(N) ((N) << 0x5) +#define SOUND_WAVE_BANK_SELECT(N) ((N) << 0x6) +#define SOUND_WAVE_ENABLE (1 << 0x7) + +typedef u8 WaveBank[32]; + +// typedef u32 WaveBank[4]; +#define SOUND_WAVE_RAM ((WaveBank*)(MEM_IO + 0x90)) + +typedef enum { + NOTE_C_2 , NOTE_C_SHARP_2 , NOTE_D_2 , NOTE_D_SHARP_2 , + NOTE_E_2 , NOTE_F_2 , NOTE_F_SHARP_2 , NOTE_G_2 , + NOTE_G_SHARP_2 , NOTE_A_2 , NOTE_A_SHARP_2 , NOTE_B_2 , + NOTE_C_3 , NOTE_C_SHARP_3 , NOTE_D_3 , NOTE_D_SHARP_3 , + NOTE_E_3 , NOTE_F_3 , NOTE_F_SHARP_3 , NOTE_G_3 , + NOTE_G_SHARP_3 , NOTE_A_3 , NOTE_A_SHARP_3 , NOTE_B_3 , + NOTE_C_4 , NOTE_C_SHARP_4 , NOTE_D_4 , NOTE_D_SHARP_4 , + NOTE_E_4 , NOTE_F_4 , NOTE_F_SHARP_4 , NOTE_G_4 , + NOTE_G_SHARP_4 , NOTE_A_4 , NOTE_A_SHARP_4 , NOTE_B_4 , + NOTE_C_5 , NOTE_C_SHARP_5 , NOTE_D_5 , NOTE_D_SHARP_5 , + NOTE_E_5 , NOTE_F_5 , NOTE_F_SHARP_5 , NOTE_G_5 , + NOTE_G_SHARP_5 , NOTE_A_5 , NOTE_A_SHARP_5 , NOTE_B_5 , + NOTE_C_6 , NOTE_C_SHARP_6 , NOTE_D_6 , NOTE_D_SHARP_6 , + NOTE_E_6 , NOTE_F_6 , NOTE_F_SHARP_6 , NOTE_G_6 , + NOTE_G_SHARP_6 , NOTE_A_6 , NOTE_A_SHARP_6 , NOTE_B_6 , + NOTE_C_7 , NOTE_C_SHARP_7 , NOTE_D_7 , NOTE_D_SHARP_7 , + NOTE_E_7 , NOTE_F_7 , NOTE_F_SHARP_7 , NOTE_G_7 , + NOTE_G_SHARP_7 , NOTE_A_7 , NOTE_A_SHARP_7 , NOTE_B_7 , + NOTE_C_8 +} Note; + +const char * note_names[] = { + "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", + "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", + "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4", + "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", + "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", + "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", + "C8" +}; + +const u32 sound_rates[] = { + 44 , 156 , 262 , 363 , 457 , 547 , 631 , 710 , 785 , 856 , 923 , 986 , + 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517, + 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782, + 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915, + 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981, + 1985, 1988, 1992, 1995, 1998, 2001, 2003, 2006, 2008, 2010, 2012, 2014, + 2016, +}; + +// +// Misc. +// + +// Custom VSync option. This will waste a lot of battery power, since the +// machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts +// are enabled. +static inline void +wait_vsync(void) { + while(DISP_VCOUNT >= 160); + while(DISP_VCOUNT < 160); +} + +// General utility macros. +#define MIN(A, B) ((A) <= (B) ? (A) : (B)) +#define MAX(A, B) ((A) >= (B) ? (A) : (B)) +#define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X)) + +#endif // GBAEXP_COMMON_H diff --git a/src/irs.s b/src/irs.s new file mode 100644 index 0000000..5cccb6e --- /dev/null +++ b/src/irs.s @@ -0,0 +1,89 @@ + .file "irs.s" + .extern irs_table; + .section .iwram, "ax", %progbits + .arm + .align + .global irs_main + +irs_main: + @ Get the contents of IRQ_ENABLE, IRQ_ACK, and IRQ_CTRL + ldr ip, mem_irq_base_reg @ ip = (IRQ_ENABLE << 16) | IRQ_ACK + ldr r0, [ip] @ r0 = irq_enable + and r1, r0, r0, lsr #16 @ r1 = irq_enable & irq_ack + + @ Disable IRQ_CTRL for now. + mov r3, #0 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + + @ r0 = irs_table address pointer + @ r2 = tmp + @ r3 = 0 (for r3; r3 < 14; r3++) + ldr r0, = irs_table +irs_main_fp_search: + @ Check that the current index is an active IRQ. + mov r2, #1 + and r2, r1, r2, lsl r3 + cmp r2, #0 + beq irs_main_fp_search_not_enabled + + @ Extract the function pointer for this IRS if available. + ldr r2, [r0] + cmp r2, #0 + bne irs_main_handle_irs + +irs_main_fp_search_not_enabled: + add r0, r0, #4 + add r3, #1 + cmp r3, #14 + bne irs_main_fp_search + b irs_main_exit + +irs_main_handle_irs: + @ r2: Contains IRQ function pointer. + @ r3: Stores the IRQ index. + + @ Acknowledge that we are handling this interrupt writing to IRQ_ACK and + @ IRQ_ACK_BIOS. + mov r0, #1 + lsl r0, r0, r3 + strh r0, [ip, #2] + ldr r1, mem_irq_ack_bios @ r1 = IRQ_ACK_BIOS + str r0, [r1] + + @ Store the SPSR in one of the free registers and save it along with the + @ return pointer. + mrs r3, spsr + stmfd sp!, {r3, lr} + + @ Set CPU to system mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x1F + msr cpsr, r3 + + @ Call isr function pointer + stmfd sp!, {lr} + mov lr, pc + bx r2 + ldmfd sp!, {lr} + + @ Set CPU to irq mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x92 + msr cpsr, r3 + + @ Restore the SPSR and the registers. + ldmfd sp!, {r3, lr} + msr spsr, r3 + +irs_main_exit: + @ Restore IRQ_CTRL. + mov r3, #1 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + bx lr + +mem_irq_base_reg: + .word 0x04000200 @ IRQ_ENABLE +mem_irq_ack_bios: + .word 0x03007FF8 @ IRQ_ACK_BIOS diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..77cabcc --- /dev/null +++ b/src/main.c @@ -0,0 +1,155 @@ +#include + +#include "common.h" +#include "bitmap.h" +#include "text.h" +#include "small-font.c" + +#include "uxn/uxn.h" +#include "uxn/uxn.c" +#include "uxn/devices/ppu.h" +#include "uxn/devices/ppu.c" +// #include "uxn/roms/console.c" +#include "uxn/roms/dvd.c" +// #include "uxn/roms/proportional_fonts.c" +// #include "uxn/roms/automata.c" +// #include "uxn/roms/life.c" + +static Ppu ppu; +u8 reqdraw = 0; +static Device *devscreen; + +void +nil_talk(Device *d, Uint8 b0, Uint8 w) { + (void)d; + (void)b0; + (void)w; +} + +void +console_talk(Device *d, u8 b0, u8 w) { + if(!w) { + return; + } + switch(b0) { + case 0x8: txt_printf("%c", d->dat[0x8]); break; + case 0x9: txt_printf("0x%02x", d->dat[0x9]); break; + case 0xb: txt_printf("0x%04x", mempeek16(d->dat, 0xa)); break; + case 0xd: txt_printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; + } +} + +void +system_talk(Device *d, Uint8 b0, Uint8 w) +{ + if(!w) { + d->dat[0x2] = d->u->wst.ptr; + d->dat[0x3] = d->u->rst.ptr; + } else { + putcolors(&ppu, &d->dat[0x8]); + reqdraw = 1; + } + (void)b0; +} + +void +screen_talk(Device *d, Uint8 b0, Uint8 w) { + if(w && b0 == 0xe) { + Uint16 x = mempeek16(d->dat, 0x8); + Uint16 y = mempeek16(d->dat, 0xa); + Uint8 *addr = &d->mem[mempeek16(d->dat, 0xc)]; + Uint8 *layer = d->dat[0xe] >> 4 & 0x1 ? ppu.fg : ppu.bg; + Uint8 mode = d->dat[0xe] >> 5; + if(!mode) { + putpixel(&ppu, layer, x, y, d->dat[0xe] & 0x3); + } else if(mode-- & 0x1) { + puticn(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); + } else { + putchr(&ppu, layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); + } + reqdraw = 1; + } +} + +void +redraw(Uint16 *dst, Uxn *u) { + // TODO: The screen will flicker but using Mode4 for double buffering would + // be way too slow. + drawppu(&ppu); + clear_screen_m3(); + // Copy ppu data to framebuffer. + for (size_t j = 0; j < ppu.height; ++j) { + for (size_t i = 0; i < ppu.width; ++i) { + FRAMEBUFFER[j][i] = ppu.output[i + j * ppu.width]; + } + } + reqdraw = 0; +} + +void +init_uxn(Uxn *u) { + // Initialize PPU. + initppu(&ppu, 30, 20, 0); + + // Copy rom to VM. + memcpy(u->ram.dat + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); + + // Prepare devices. + portuxn(u, 0x0, "system", system_talk); + portuxn(u, 0x1, "console", console_talk); + devscreen = portuxn(u, 0x2, "screen", screen_talk); + portuxn(u, 0x3, "---", nil_talk); + portuxn(u, 0x4, "---", nil_talk); + portuxn(u, 0x5, "---", nil_talk); + portuxn(u, 0x6, "---", nil_talk); + portuxn(u, 0x7, "---", nil_talk); + portuxn(u, 0x8, "---", nil_talk); + portuxn(u, 0x9, "---", nil_talk); + portuxn(u, 0xa, "---", nil_talk); + portuxn(u, 0xb, "---", nil_talk); + portuxn(u, 0xc, "---", nil_talk); + portuxn(u, 0xd, "---", nil_talk); + portuxn(u, 0xe, "---", nil_talk); + portuxn(u, 0xf, "---", nil_talk); + mempoke16(devscreen->dat, 2, ppu.hor * 8); + mempoke16(devscreen->dat, 4, ppu.ver * 8); +} + +int main(void) { + // Initialize display mode and bg palette. + DISP_CTRL = DISP_MODE_3 | DISP_BG_2; + + // Initialize text engine. + txt_init_bitmap( + TXT_MODE_MODE3, + (Font){ + .data = small_font, + .char_width = 4, + .char_height = 8, + .color = COLOR_BLUE, + .char_map = small_font_map, + }); + + // Register interrupts. + irq_init(); + irs_set(IRQ_VBLANK, irs_stub); + + // Initialize VM. + Uxn u = {0}; + init_uxn(&u); + evaluxn(&u, 0x0100); + + // Main loop. + int frame_counter = 0; + while(true) { + bios_vblank_wait(); + poll_keys(); + evaluxn(&u, mempeek16(devscreen->dat, 0)); + if(reqdraw) { + redraw(ppu.output, &u); + } + frame_counter++; + }; + + return 0; +} diff --git a/src/shorthand.h b/src/shorthand.h new file mode 100644 index 0000000..42eb935 --- /dev/null +++ b/src/shorthand.h @@ -0,0 +1,36 @@ +#ifndef UTILS_SHORTHAND_H +#define UTILS_SHORTHAND_H + +#include +#include +#include +#include + +// +// This simple header just typedefs the basic C define types to a shorter name, +// loads the quality of life bool macro for _Bool and defines shorthand macros +// for byte sizes. + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; +typedef volatile u8 vu8; +typedef volatile u16 vu16; +typedef volatile u32 vu32; +typedef volatile u64 vu64; +typedef volatile s8 vs8; +typedef volatile s16 vs16; +typedef volatile s32 vs32; +typedef volatile s64 vs64; + +#define KB(N) ((u64)(N) * 1024) +#define MB(N) ((u64)KB(N) * 1024) +#define GB(N) ((u64)MB(N) * 1024) +#define TB(N) ((u64)GB(N) * 1024) + +#endif // UTILS_SHORTHAND_H diff --git a/src/small-font.c b/src/small-font.c new file mode 100644 index 0000000..2f232dd --- /dev/null +++ b/src/small-font.c @@ -0,0 +1,45 @@ +u32 small_font[] = { + 0x00000000, 0x00000000, 0x00020202, 0x00020000, + 0x000a0a00, 0x00000000, 0x000a1f0a, 0x1f0a0000, + 0x02070107, 0x04070200, 0x00010402, 0x01040000, + 0x0007050f, 0x050d0000, 0x00020200, 0x00000000, + 0x00020101, 0x01020000, 0x00020404, 0x04020000, + 0x00050207, 0x02050000, 0x00000207, 0x02000000, + 0x00000000, 0x00020100, 0x00000007, 0x00000000, + 0x00000000, 0x00020000, 0x00040602, 0x03010000, + 0x00070505, 0x05070000, 0x00020302, 0x02070000, + 0x00070407, 0x01070000, 0x00070406, 0x04070000, + 0x00040605, 0x07040000, 0x00070107, 0x04070000, + 0x00070107, 0x05070000, 0x00070402, 0x01010000, + 0x00070507, 0x05070000, 0x00070507, 0x04040000, + 0x00000200, 0x00020000, 0x00000200, 0x00020100, + 0x00040201, 0x02040000, 0x00000700, 0x07000000, + 0x00010204, 0x02010000, 0x00070402, 0x00020000, + 0x00070505, 0x01070000, 0x00070505, 0x07050000, + 0x00070503, 0x05070000, 0x00070101, 0x01070000, + 0x00030505, 0x05030000, 0x00070103, 0x01070000, + 0x00070103, 0x01010000, 0x00070105, 0x05070000, + 0x00050507, 0x05050000, 0x00070202, 0x02070000, + 0x00040404, 0x05070000, 0x00050503, 0x05050000, + 0x00010101, 0x01070000, 0x00050705, 0x05050000, + 0x00030505, 0x05050000, 0x00030505, 0x05070000, + 0x00070507, 0x01010000, 0x00070505, 0x07040000, + 0x00070503, 0x05050000, 0x00060107, 0x04030000, + 0x00070202, 0x02020000, 0x00050505, 0x05070000, + 0x00050505, 0x05060000, 0x00050505, 0x07050000, + 0x00050502, 0x05050000, 0x00050507, 0x02020000, + 0x00070402, 0x01070000, 0x00070101, 0x01070000, + 0x00010302, 0x06040000, 0x00070404, 0x04070000, + 0x00020500, 0x00000000, 0x00000000, 0x00070000, +}; + +u8 small_font_map[256] = { + 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , + 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0 , 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 0 , 0 , 0 , 0 , 0 , +}; diff --git a/src/sprites.h b/src/sprites.h new file mode 100644 index 0000000..a64b784 --- /dev/null +++ b/src/sprites.h @@ -0,0 +1,79 @@ +#ifndef GBAEXP_SPRITES_H +#define GBAEXP_SPRITES_H + +#include "common.h" + +typedef struct Sprite { + // A unique sprite identifier. + size_t id; + // The number of tiles for a single sprite frame. + size_t n_tiles; + // The starting tile of this sprite. + size_t tile_start; + // The associated palette bank for this sprite. + size_t pal_bank; +} Sprite; + + +#define NUM_SPRITES 128 +Sprite sprites[NUM_SPRITES]; + +// Keeping track of unique sprites and current sprite memory pointer using +// global singletons. +size_t sprite_counter = 0; +size_t sprite_tile_counter = 0; +u32 *sprite_memory = NULL; + +// Loads the sprite data into video memory and initialize the Sprite structure. +size_t +load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { + memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32)); + sprite_memory += 8 * n_tiles * n_frames; + Sprite sprite = { + .id = sprite_counter, + .n_tiles = n_tiles, + .tile_start = sprite_tile_counter, + }; + sprite_tile_counter += n_tiles * n_frames; + sprites[sprite_counter] = sprite; + return sprite_counter++; +} + +size_t +load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { + unpack_tiles(sprite_data, sprite_memory, n_tiles * n_frames); + sprite_memory += 8 * n_tiles * n_frames; + Sprite sprite = { + .id = sprite_counter, + .n_tiles = n_tiles, + .tile_start = sprite_tile_counter, + }; + sprite_tile_counter += n_tiles * n_frames; + sprites[sprite_counter] = sprite; + return sprite_counter++; +} + +void +init_sprites(size_t starting_tile) { + // Initialize all attributes by disabling rendering. If we don't do this, + // glitches may appear. + for (size_t i = 0; i < 128; ++i) { + OBJ_ATTR_0(i) = (1 << 9); + } + + sprite_counter = 0; + // Prepare global sprite_memory address. + sprite_memory = &TILE_MEM[4][starting_tile]; + sprite_tile_counter = starting_tile; +} + +void +init_sprite_pal(size_t starting_index, Color col) { + // Add colors to the sprite color palette. Tiles with color number 0 are + // treated as transparent. + for (size_t i = 0; i < 16; ++i) { + PAL_BUFFER_SPRITES[i + starting_index] = col; + } +} + +#endif // GBAEXP_SPRITES_H diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..bfac4e4 --- /dev/null +++ b/src/text.h @@ -0,0 +1,232 @@ +#ifndef GBAEXP_TILES_H +#define GBAEXP_TILES_H + +#include +#include + +#include "common.h" +#include "bitmap.h" + +typedef enum { + TXT_MODE_TILED_BG, + TXT_MODE_MODE3, +} TextMode; + +typedef struct Font { + // A pointer to an area of memory containing font data. + // TODO: Should we unpack each char everytime or unpack everything into RAM? + // Maybe this should be optional? + u16 *data; + // The char_map stores the index to the character position within the font + // array depending on the ascii number we want to render. This allows + // the usage reduced font sets, for example just uppercase letters and + // numbers. + u8 *char_map; + // Width and height of each font character. Only monospaced fonts are + // currently supported. + u8 char_width; + u8 char_height; + // The color of this font. + Color color; +} Font; + +typedef struct TextEngine { + // Currently working on tiled backgrounds only. The X and Y positions + // correspond to the tile X and Y starting from the top left of the screen. + // For a 240x160 screen, we have 30x20 tiles available. + size_t cursor_x; + size_t cursor_y; + // Pointer to the memory being used for writing to the screen. + u16 *memory; + // TODO: Support other modes and monospaced fonts should be simple but we + // need to keep track of a couple of other variables, which may not be + // available for all modes. For example, tile modes can't have a font width + // smaller than the tile size, although bigger fonts (For example fonts + // composed of multiple tiles) should still be possible. This may not be + // a good time investment, but can be done in the future if needed. + // The mode controls how the text is rendered and how the memory is managed. + TextMode mode; + // The font used to render the text. + Font font; +} TextEngine; + +static TextEngine text_engine = {0}; + +static u8 default_char_map[256] = {0}; + +void +txt_putc_tile(char c) { + if (c == '\0') { + return; + } + if (c == '\n') { + text_engine.cursor_x = 0; + text_engine.cursor_y++; + } else { + text_engine.memory[text_engine.cursor_x + 32 * text_engine.cursor_y] = c; + text_engine.cursor_x++; + if (text_engine.cursor_x >= 30) { + text_engine.cursor_x = 0; + text_engine.cursor_y++; + } + } + if (text_engine.cursor_y >= 20) { + text_engine.cursor_y = 0; + } +} + +void +txt_putc_m3(char c) { + if (c == '\0') { + return; + } + if (c == '\n') { + text_engine.cursor_x = 0; + text_engine.cursor_y += text_engine.font.char_height; + } else { + u8 idx = text_engine.font.char_map[(int)c] * 2; + u32 *packed_char = text_engine.font.data; + packed_char += idx; + Tile tile = {0}; + unpack_tiles(packed_char, &tile, 1); + int x = text_engine.cursor_x; + int y = text_engine.cursor_y; + for (size_t i = 0; i < text_engine.font.char_height; ++i) { + for (size_t j = 0; j < text_engine.font.char_width; ++j) { + if ((tile.row[i] >> 4 * j) & 0x1) { + // put_pixel_m4(x + j, y + i, 1, backbuffer); + // TODO: Clean this up please. + put_pixel_m3(x + j, + y + i, + text_engine.font.color, + FRAMEBUFFER); + } + } + } + text_engine.cursor_x += text_engine.font.char_width; + if (text_engine.cursor_x >= SCREEN_WIDTH) { + text_engine.cursor_x = 0; + text_engine.cursor_y += text_engine.font.char_height; + } + } + if (text_engine.cursor_y >= SCREEN_HEIGHT) { + text_engine.cursor_y = 0; + } +} + +void +txt_putc(char c) { + switch (text_engine.mode) { + case TXT_MODE_TILED_BG: { + txt_putc_tile(c); + } break; + case TXT_MODE_MODE3: { + txt_putc_m3(c); + } break; + } +} + +static inline void +txt_puts(char *msg) { + while (*msg) { + txt_putc(*msg++); + } +} + +void +txt_init_tile(size_t bg, Font font, size_t cb_idx) { + // The screenblock for the tile map should start after the tile memory + // (MEM_VRAM + 0x2000 for 256 characters). Since each screenblock is + // composed of 1024 screenblock entries of u16 (2 bytes), we need an + // screenblock index offset of 8192 / (1024 * 2) = 4 screen blocks. + size_t sb_idx = cb_idx * 8 + 4; + + // Set the background parameters for the text layer. + BG_CTRL(bg) = BG_CHARBLOCK(cb_idx) | BG_SCREENBLOCK(sb_idx) | BG_PRIORITY(3); + + // Load font data in video memory. Each character is unpacked into a tile of + // 8 32bit values (4bpp), meaning that for the full ASCII set of 256 + // characters, we need 8192 bytes of VRAM (8 * 4 * 256). + text_engine.font = font; + unpack_tiles(font.data, &TILE_MEM[cb_idx], 256); + + // Initialize default values. + if (font.color == 0) { + font.color = COLOR_WHITE; + } + + // Load palette color. + PAL_BUFFER_BG[1] = font.color; + + // Update text_engine variables. + text_engine.memory = SCREENBLOCK_MEM[sb_idx]; +} + +void +txt_init_bitmap(TextMode mode, Font font) { + // NOTE: Only mode 3 is currently supported + assert(mode == TXT_MODE_MODE3); + + // If font_map is NULL, initialize the standard 0-255 character map. + if (font.char_map == NULL) { + for (size_t i = 0; i < 256; ++i) { + default_char_map[i] = i; + } + font.char_map = &default_char_map; + } + + // Initialize default values if set to zero. + if (font.char_width == 0) { + font.char_width = 8; + } + if (font.char_height == 0) { + font.char_height = 8; + } + if (font.color == 0) { + font.color = COLOR_WHITE; + } + + // Prepare text engine. + text_engine.font = font; + text_engine.mode = mode; +} + +// Print text to the screen with formatting. +void +txt_printf(char *msg, ...) { + va_list arg_list; + va_start(arg_list, msg); + char buf[512] = {0}; + vsprintf(buf, msg, arg_list); + txt_puts(buf); +} + +// TODO: Update for working on bitmap modes. +void +txt_clear_line(void) { + for (size_t i = 0; i < 30; ++i) { + text_engine.memory[i + 32 * text_engine.cursor_y] = ' '; + } + text_engine.cursor_x = 0; +} + +// TODO: Update for working on bitmap modes. +void +txt_clear_screen(void) { + for (size_t j = 0; j < 20; ++j) { + for (size_t i = 0; i < 30; ++i) { + text_engine.memory[i + 32 * j] = ' '; + } + } + text_engine.cursor_x = 0; + text_engine.cursor_y = 0; +} + +void +txt_position(size_t tile_x, size_t tile_y) { + text_engine.cursor_x = tile_x; + text_engine.cursor_y = tile_y; +} + +#endif // GBAEXP_TILES_H + diff --git a/src/uxn/devices/ppu.c b/src/uxn/devices/ppu.c new file mode 100644 index 0000000..b977f97 --- /dev/null +++ b/src/uxn/devices/ppu.c @@ -0,0 +1,180 @@ +#include "ppu.h" + +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 Andrew Alderwick + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +static Uint8 font[][8] = { + {0x00, 0x7c, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c}, + {0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, + {0x00, 0x7c, 0x82, 0x02, 0x7c, 0x80, 0x80, 0xfe}, + {0x00, 0x7c, 0x82, 0x02, 0x1c, 0x02, 0x82, 0x7c}, + {0x00, 0x0c, 0x14, 0x24, 0x44, 0x84, 0xfe, 0x04}, + {0x00, 0xfe, 0x80, 0x80, 0x7c, 0x02, 0x82, 0x7c}, + {0x00, 0x7c, 0x82, 0x80, 0xfc, 0x82, 0x82, 0x7c}, + {0x00, 0x7c, 0x82, 0x02, 0x1e, 0x02, 0x02, 0x02}, + {0x00, 0x7c, 0x82, 0x82, 0x7c, 0x82, 0x82, 0x7c}, + {0x00, 0x7c, 0x82, 0x82, 0x7e, 0x02, 0x82, 0x7c}, + {0x00, 0x7c, 0x82, 0x02, 0x7e, 0x82, 0x82, 0x7e}, + {0x00, 0xfc, 0x82, 0x82, 0xfc, 0x82, 0x82, 0xfc}, + {0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0x82, 0x7c}, + {0x00, 0xfc, 0x82, 0x82, 0x82, 0x82, 0x82, 0xfc}, + {0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x82, 0x7c}, + {0x00, 0x7c, 0x82, 0x80, 0xf0, 0x80, 0x80, 0x80}}; + +Uint8 +readpixel(Uint8 *sprite, Uint8 h, Uint8 v) +{ + Uint8 ch1 = ((sprite[v] >> h) & 0x1); + Uint8 ch2 = (((sprite[v + 8] >> h) & 0x1) << 1); + return ch1 + ch2; +} + +void +clear(Ppu *p) +{ + int i, sz = p->height * p->width, rows = sz / 4; + for(i = 0; i < sz; ++i) + p->output[i] = p->colors[0]; + for(i = 0; i < rows; i++) { + p->fg[i] = 0; + p->bg[i] = 0; + } +} + +void +putcolors(Ppu *p, Uint8 *addr) +{ + int i; + for(i = 0; i < 4; ++i) { + Uint8 + r = (*(addr + i / 2) >> (!(i % 2) << 2)) & 0x0f, + g = (*(addr + 2 + i / 2) >> (!(i % 2) << 2)) & 0x0f, + b = (*(addr + 4 + i / 2) >> (!(i % 2) << 2)) & 0x0f; + p->colors[i] = rgb15(r,g,b); + } + // p->colors[0] = COLOR_BLUE; + // p->colors[1] = COLOR_WHITE; + // p->colors[2] = COLOR_RED; + // p->colors[3] = COLOR_CYAN; +} + +void +putpixel(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 color) +{ + Uint16 row = (y % 8) + ((x / 8 + y / 8 * p->hor) * 16), col = 7 - (x % 8); + if(x >= p->hor * 8 || y >= p->ver * 8 || row > (p->hor * p->ver * 16) - 8) + return; + if(color == 0 || color == 2) + layer[row] &= ~(1UL << col); + else + layer[row] |= 1UL << col; + if(color == 0 || color == 1) + layer[row + 8] &= ~(1UL << col); + else + layer[row + 8] |= 1UL << col; +} + +void +puticn(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy) +{ + Uint16 v, h; + for(v = 0; v < 8; v++) + for(h = 0; h < 8; h++) { + Uint8 ch1 = ((sprite[v] >> (7 - h)) & 0x1); + if(ch1 == 1 || (color != 0x05 && color != 0x0a && color != 0x0f)) + putpixel(p, + layer, + x + (flipx ? 7 - h : h), + y + (flipy ? 7 - v : v), + ch1 ? color % 4 : color / 4); + } +} + +void +putchr(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy) +{ + Uint16 v, h; + for(v = 0; v < 8; v++) + for(h = 0; h < 8; h++) { + Uint8 ch1 = ((sprite[v] >> (7 - h)) & 0x1) * color; + Uint8 ch2 = ((sprite[v + 8] >> (7 - h)) & 0x1) * color; + putpixel(p, + layer, + x + (flipx ? 7 - h : h), + y + (flipy ? 7 - v : v), + (((ch1 + ch2 * 2) + color / 4) & 0x3)); + } +} + +/* output */ + +void +drawpixel(Ppu *p, Uint16 x, Uint16 y, Uint8 color) +{ + if(x >= p->pad && x <= p->width - p->pad - 1 && y >= p->pad && y <= p->height - p->pad - 1) + p->output[y * p->width + x] = p->colors[color]; +} + +void +drawdebugger(Ppu *p, Uint8 *stack, Uint8 ptr) +{ + Uint8 i, x, y, b; + for(i = 0; i < 0x20; ++i) { /* memory */ + x = ((i % 8) * 3 + 1) * 8, y = (i / 8 + 1) * 8, b = stack[i]; + puticn(p, p->bg, x, y, font[(b >> 4) & 0xf], 1 + (ptr == i) * 0x7, 0, 0); + puticn(p, p->bg, x + 8, y, font[b & 0xf], 1 + (ptr == i) * 0x7, 0, 0); + } + for(x = 0; x < 0x20; ++x) { + drawpixel(p, x, p->height / 2, 2); + drawpixel(p, p->width - x, p->height / 2, 2); + drawpixel(p, p->width / 2, p->height - x, 2); + drawpixel(p, p->width / 2, x, 2); + drawpixel(p, p->width / 2 - 16 + x, p->height / 2, 2); + drawpixel(p, p->width / 2, p->height / 2 - 16 + x, 2); + } +} + +void +drawppu(Ppu *p) +{ + Uint16 x, y; + for(y = 0; y < p->ver; ++y) + for(x = 0; x < p->hor; ++x) { + Uint8 v, h; + Uint16 key = (y * p->hor + x) * 16; + for(v = 0; v < 8; v++) + for(h = 0; h < 8; h++) { + Uint8 color = readpixel(&p->fg[key], h, v); + if(color == 0) + color = readpixel(&p->bg[key], h, v); + drawpixel(p, x * 8 + p->pad + 7 - h, y * 8 + p->pad + v, color); + } + } +} + +int +initppu(Ppu *p, Uint8 hor, Uint8 ver, Uint8 pad) +{ + p->hor = hor; + p->ver = ver; + p->pad = pad; + p->width = (8 * p->hor + p->pad * 2); + p->height = (8 * p->ver + p->pad * 2); + if(!(p->output = malloc(p->width * p->height * sizeof(Uint16)))) + return 0; + if(!(p->bg = malloc(p->width * p->height * sizeof(Uint8) / 4))) + return 0; + if(!(p->fg = malloc(p->width * p->height * sizeof(Uint8) / 4))) + return 0; + clear(p); + return 1; +} diff --git a/src/uxn/devices/ppu.h b/src/uxn/devices/ppu.h new file mode 100644 index 0000000..187d364 --- /dev/null +++ b/src/uxn/devices/ppu.h @@ -0,0 +1,36 @@ +#ifndef UXNGBA_PPU_H +#define UXNGBA_PPU_H + +#include +#include + +/* +Copyright (c) 2021 Devine Lu Linvega +Copyright (c) 2021 Andrew Alderwick + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +typedef unsigned char Uint8; +typedef unsigned short Uint16; +typedef unsigned int Uint32; + +typedef struct Ppu { + Uint8 *bg, *fg; + Uint16 hor, ver, pad, width, height; + Uint16 *output, colors[4]; +} Ppu; + +int initppu(Ppu *p, Uint8 hor, Uint8 ver, Uint8 pad); +void putcolors(Ppu *p, Uint8 *addr); +void putpixel(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 color); +void puticn(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy); +void putchr(Ppu *p, Uint8 *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy); +void drawppu(Ppu *p); +void drawdebugger(Ppu *p, Uint8 *stack, Uint8 ptr); +#endif // UXNGBA_PPU_H diff --git a/src/uxn/roms/console.c b/src/uxn/roms/console.c new file mode 100644 index 0000000..13d669a --- /dev/null +++ b/src/uxn/roms/console.c @@ -0,0 +1,5 @@ +u16 uxn_rom[] = { + 0x0121, 0x9411, 0x1801, 0x2117, 0x0100, 0x9438, 0xf401, 0x230d, + 0x4800, 0x6c65, 0x6f6c, 0x5720, 0x726f, 0x646c, 0x6620, 0x6f72, + 0x206d, 0x5855, 0x214e, 0x000d +}; diff --git a/src/uxn/roms/dvd.c b/src/uxn/roms/dvd.c new file mode 100644 index 0000000..2cccc84 --- /dev/null +++ b/src/uxn/roms/dvd.c @@ -0,0 +1,22 @@ +u16 uxn_rom[] = { + 0x4c21, 0x01fd, 0x3708, 0x4c21, 0x01f3, 0x370a, 0xdc21, 0x01f2, + 0x370c, 0x0121, 0x0133, 0x3720, 0x2201, 0x2136, 0x0200, 0x013b, + 0x3100, 0x2401, 0x2136, 0x0200, 0x013b, 0x3102, 0x2101, 0x0121, + 0x2eb2, 0x0100, 0x2120, 0xb201, 0x012e, 0x3000, 0x2201, 0x2136, + 0x2000, 0x2839, 0x0001, 0x2130, 0x0000, 0x2128, 0x0000, 0x0128, + 0x0d09, 0x0401, 0x0110, 0x0800, 0x0401, 0x0111, 0x3002, 0x2401, + 0x2136, 0x1000, 0x2839, 0x0201, 0x2130, 0x0000, 0x2128, 0x0000, + 0x0128, 0x0d09, 0x0501, 0x0110, 0x0800, 0x0501, 0x0111, 0x3000, + 0x0021, 0x0101, 0x0100, 0x1004, 0x0001, 0x2108, 0xfeff, 0x383a, + 0x0138, 0x3100, 0x0201, 0x2130, 0x0100, 0x0001, 0x0501, 0x0110, + 0x0800, 0xff21, 0x3afe, 0x3838, 0x0201, 0x0131, 0x2121, 0xb201, + 0x002e, 0x210f, 0xfd01, 0x2c01, 0x0137, 0x3002, 0x0201, 0x2130, + 0x1000, 0x2638, 0x2a01, 0x0137, 0x3000, 0x0001, 0x2130, 0x2000, + 0x2638, 0x2801, 0xcf37, 0x2e01, 0x0117, 0x362c, 0x0021, 0x3808, + 0x2c01, 0x2537, 0x0021, 0x3808, 0xab25, 0xe401, 0x230d, 0x2523, + 0x0021, 0x3808, 0xab25, 0xca01, 0x230d, 0x4323, 0x006c, 0x3f1f, + 0x3838, 0x7838, 0x007f, 0xfefe, 0x777e, 0xe377, 0x00c3, 0x1f0f, + 0x7b3b, 0xe777, 0x00c7, 0xfefc, 0x878f, 0x0e07, 0x7ffc, 0x0000, + 0xff0f, 0x077f, 0x0300, 0x0001, 0xf0ff, 0xfff8, 0x8700, 0x0000, + 0x7fff, 0xff7f, 0xf000, 0x0000, 0xfce0, 0x80fc, 0x0a00, +}; diff --git a/src/uxn/uxn.c b/src/uxn/uxn.c new file mode 100644 index 0000000..796a980 --- /dev/null +++ b/src/uxn/uxn.c @@ -0,0 +1,196 @@ +#include +#include "uxn.h" + +/* +Copyright (u) 2021 Devine Lu Linvega + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#pragma mark - Operations + +/* clang-format off */ +void push8(Stack *s, Uint8 a) { if (s->ptr == 0xff) { s->error = 2; return; } s->dat[s->ptr++] = a; } +Uint8 pop8_keep(Stack *s) { if (s->kptr == 0) { s->error = 1; return 0; } return s->dat[--s->kptr]; } +Uint8 pop8_nokeep(Stack *s) { if (s->ptr == 0) { s->error = 1; return 0; } return s->dat[--s->ptr]; } +static Uint8 (*pop8)(Stack *s); +void mempoke8(Uint8 *m, Uint16 a, Uint8 b) { m[a] = b; } +Uint8 mempeek8(Uint8 *m, Uint16 a) { return m[a]; } +void devpoke8(Device *d, Uint8 a, Uint8 b) { d->dat[a & 0xf] = b; d->talk(d, a & 0x0f, 1); } +Uint8 devpeek8(Device *d, Uint8 a) { d->talk(d, a & 0x0f, 0); return d->dat[a & 0xf]; } +void push16(Stack *s, Uint16 a) { push8(s, a >> 8); push8(s, a); } +Uint16 pop16(Stack *s) { return pop8(s) + (pop8(s) << 8); } +void mempoke16(Uint8 *m, Uint16 a, Uint16 b) { mempoke8(m, a, b >> 8); mempoke8(m, a + 1, b); } +Uint16 mempeek16(Uint8 *m, Uint16 a) { return (mempeek8(m, a) << 8) + mempeek8(m, a + 1); } +void devpoke16(Device *d, Uint8 a, Uint16 b) { devpoke8(d, a, b >> 8); devpoke8(d, a + 1, b); } +Uint16 devpeek16(Device *d, Uint16 a) { return (devpeek8(d, a) << 8) + devpeek8(d, a + 1); } +/* Stack */ +void op_brk(Uxn *u) { u->ram.ptr = 0; } +void op_nop(Uxn *u) { (void)u; } +void op_lit(Uxn *u) { push8(u->src, mempeek8(u->ram.dat, u->ram.ptr++)); } +void op_pop(Uxn *u) { pop8(u->src); } +void op_dup(Uxn *u) { Uint8 a = pop8(u->src); push8(u->src, a); push8(u->src, a); } +void op_swp(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, a); push8(u->src, b); } +void op_ovr(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b); push8(u->src, a); push8(u->src, b); } +void op_rot(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src), c = pop8(u->src); push8(u->src, b); push8(u->src, a); push8(u->src, c); } +/* Logic */ +void op_equ(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b == a); } +void op_neq(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b != a); } +void op_gth(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b > a); } +void op_lth(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b < a); } +void op_jmp(Uxn *u) { Uint8 a = pop8(u->src); u->ram.ptr += (Sint8)a; } +void op_jnz(Uxn *u) { Uint8 a = pop8(u->src); if (pop8(u->src)) u->ram.ptr += (Sint8)a; } +void op_jsr(Uxn *u) { Uint8 a = pop8(u->src); push16(u->dst, u->ram.ptr); u->ram.ptr += (Sint8)a; } +void op_sth(Uxn *u) { Uint8 a = pop8(u->src); push8(u->dst, a); } +/* Memory */ +void op_pek(Uxn *u) { Uint8 a = pop8(u->src); push8(u->src, mempeek8(u->ram.dat, a)); } +void op_pok(Uxn *u) { Uint8 a = pop8(u->src); Uint8 b = pop8(u->src); mempoke8(u->ram.dat, a, b); } +void op_ldr(Uxn *u) { Uint8 a = pop8(u->src); push8(u->src, mempeek8(u->ram.dat, u->ram.ptr + (Sint8)a)); } +void op_str(Uxn *u) { Uint8 a = pop8(u->src); Uint8 b = pop8(u->src); mempoke8(u->ram.dat, u->ram.ptr + (Sint8)a, b); } +void op_lda(Uxn *u) { Uint16 a = pop16(u->src); push8(u->src, mempeek8(u->ram.dat, a)); } +void op_sta(Uxn *u) { Uint16 a = pop16(u->src); Uint8 b = pop8(u->src); mempoke8(u->ram.dat, a, b); } +void op_dei(Uxn *u) { Uint8 a = pop8(u->src); push8(u->src, devpeek8(&u->dev[a >> 4], a)); } +void op_deo(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); devpoke8(&u->dev[a >> 4], a, b); } +/* Arithmetic */ +void op_add(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b + a); } +void op_sub(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b - a); } +void op_mul(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b * a); } +void op_div(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b / a); } +void op_and(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b & a); } +void op_ora(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b | a); } +void op_eor(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b ^ a); } +void op_sft(Uxn *u) { Uint8 a = pop8(u->src), b = pop8(u->src); push8(u->src, b >> (a & 0x07) << ((a & 0x70) >> 4)); } +/* Stack */ +void op_lit16(Uxn *u) { push16(u->src, mempeek16(u->ram.dat, u->ram.ptr++)); u->ram.ptr++; } +void op_pop16(Uxn *u) { pop16(u->src); } +void op_dup16(Uxn *u) { Uint16 a = pop16(u->src); push16(u->src, a); push16(u->src, a); } +void op_swp16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, a); push16(u->src, b); } +void op_ovr16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b); push16(u->src, a); push16(u->src, b); } +void op_rot16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src), c = pop16(u->src); push16(u->src, b); push16(u->src, a); push16(u->src, c); } +/* Logic(16-bits) */ +void op_equ16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push8(u->src, b == a); } +void op_neq16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push8(u->src, b != a); } +void op_gth16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push8(u->src, b > a); } +void op_lth16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push8(u->src, b < a); } +void op_jmp16(Uxn *u) { u->ram.ptr = pop16(u->src); } +void op_jnz16(Uxn *u) { Uint16 a = pop16(u->src); if (pop8(u->src)) u->ram.ptr = a; } +void op_jsr16(Uxn *u) { push16(u->dst, u->ram.ptr); u->ram.ptr = pop16(u->src); } +void op_sth16(Uxn *u) { Uint16 a = pop16(u->src); push16(u->dst, a); } +/* Memory(16-bits) */ +void op_pek16(Uxn *u) { Uint8 a = pop8(u->src); push16(u->src, mempeek16(u->ram.dat, a)); } +void op_pok16(Uxn *u) { Uint8 a = pop8(u->src); Uint16 b = pop16(u->src); mempoke16(u->ram.dat, a, b); } +void op_ldr16(Uxn *u) { Uint8 a = pop8(u->src); push16(u->src, mempeek16(u->ram.dat, u->ram.ptr + (Sint8)a)); } +void op_str16(Uxn *u) { Uint8 a = pop8(u->src); Uint16 b = pop16(u->src); mempoke16(u->ram.dat, u->ram.ptr + (Sint8)a, b); } +void op_lda16(Uxn *u) { Uint16 a = pop16(u->src); push16(u->src, mempeek16(u->ram.dat, a)); } +void op_sta16(Uxn *u) { Uint16 a = pop16(u->src); Uint16 b = pop16(u->src); mempoke16(u->ram.dat, a, b); } +void op_dei16(Uxn *u) { Uint8 a = pop8(u->src); push16(u->src, devpeek16(&u->dev[a >> 4], a)); } +void op_deo16(Uxn *u) { Uint8 a = pop8(u->src); Uint16 b = pop16(u->src); devpoke16(&u->dev[a >> 4], a, b); } +/* Arithmetic(16-bits) */ +void op_add16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b + a); } +void op_sub16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b - a); } +void op_mul16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b * a); } +void op_div16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b / a); } +void op_and16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b & a); } +void op_ora16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b | a); } +void op_eor16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b ^ a); } +void op_sft16(Uxn *u) { Uint16 a = pop16(u->src), b = pop16(u->src); push16(u->src, b >> (a & 0x000f) << ((a & 0x00f0) >> 4)); } + +void (*ops[])(Uxn *u) = { + op_brk, op_lit, op_nop, op_pop, op_dup, op_swp, op_ovr, op_rot, + op_equ, op_neq, op_gth, op_lth, op_jmp, op_jnz, op_jsr, op_sth, + op_pek, op_pok, op_ldr, op_str, op_lda, op_sta, op_dei, op_deo, + op_add, op_sub, op_mul, op_div, op_and, op_ora, op_eor, op_sft, + /* 16-bit */ + op_brk, op_lit16, op_nop, op_pop16, op_dup16, op_swp16, op_ovr16, op_rot16, + op_equ16, op_neq16, op_gth16, op_lth16, op_jmp16, op_jnz16, op_jsr16, op_sth16, + op_pek16, op_pok16, op_ldr16, op_str16, op_lda16, op_sta16, op_dei16, op_deo16, + op_add16, op_sub16, op_mul16, op_div16, op_and16, op_ora16, op_eor16, op_sft16 +}; + +/* clang-format on */ + +#pragma mark - Core + +int +haltuxn(Uxn *u, char *name, int id) +{ + txt_printf("Halted: %s#%04x, at 0x%04x\n", name, id, u->ram.ptr); + u->ram.ptr = 0; + return 0; +} + +void +opcuxn(Uxn *u, Uint8 instr) +{ + Uint8 op = instr & 0x3f, freturn = instr & 0x40, fkeep = instr & 0x80; + u->src = freturn ? &u->rst : &u->wst; + u->dst = freturn ? &u->wst : &u->rst; + if(fkeep) { + pop8 = pop8_keep; + u->src->kptr = u->src->ptr; + } else { + pop8 = pop8_nokeep; + } + (*ops[op])(u); +} + +int +stepuxn(Uxn *u, Uint8 instr) +{ + opcuxn(u, instr); + if(u->wst.error) + return haltuxn(u, u->wst.error == 1 ? "Working-stack underflow" : "Working-stack overflow", instr); + if(u->rst.error) + return haltuxn(u, u->rst.error == 1 ? "Return-stack underflow" : "Return-stack overflow", instr); + return 1; +} + +int +evaluxn(Uxn *u, Uint16 vec) +{ + u->ram.ptr = vec; + u->wst.error = 0; + u->rst.error = 0; + while(u->ram.ptr) + if(!stepuxn(u, u->ram.dat[u->ram.ptr++])) + return 0; + return 1; +} + +int +bootuxn(Uxn *u) +{ + size_t i; + char *cptr = (char *)u; + for(i = 0; i < sizeof(*u); i++) + cptr[i] = 0; + return 1; +} + +int +loaduxn(Uxn *u, char *filepath) +{ + FILE *f; + if(!(f = fopen(filepath, "rb"))) + return haltuxn(u, "Missing input rom.", 0); + fread(u->ram.dat + PAGE_PROGRAM, sizeof(u->ram.dat) - PAGE_PROGRAM, 1, f); + txt_printf("Uxn loaded[%s].\n", filepath); + return 1; +} + +Device * +portuxn(Uxn *u, Uint8 id, char *name, void (*talkfn)(Device *d, Uint8 b0, Uint8 w)) +{ + Device *d = &u->dev[id]; + d->addr = id * 0x10; + d->u = u; + d->mem = u->ram.dat; + d->talk = talkfn; + txt_printf("Device added #%02x: %s, at 0x%04x \n", id, name, d->addr); + return d; +} diff --git a/src/uxn/uxn.h b/src/uxn/uxn.h new file mode 100644 index 0000000..b2c90ac --- /dev/null +++ b/src/uxn/uxn.h @@ -0,0 +1,55 @@ +#ifndef UXNGBA_UXN_H +#define UXNGBA_UXN_H + +#include + +/* +Copyright (c) 2021 Devine Lu Linvega + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +typedef unsigned char Uint8; +typedef signed char Sint8; +typedef unsigned short Uint16; +typedef signed short Sint16; + +#define PAGE_PROGRAM 0x0100 + +typedef struct { + Uint8 ptr, kptr, error; + Uint8 dat[256]; +} Stack; + +typedef struct { + Uint16 ptr; + Uint8 dat[KB(16)]; +} Memory; + +typedef struct Device { + struct Uxn *u; + Uint8 addr, dat[16], *mem; + void (*talk)(struct Device *d, Uint8, Uint8); +} Device; + +typedef struct Uxn { + Stack wst, rst, *src, *dst; + Memory ram; + Device dev[16]; +} Uxn; + +struct Uxn; + +void mempoke16(Uint8 *m, Uint16 a, Uint16 b); +Uint16 mempeek16(Uint8 *m, Uint16 a); + +int loaduxn(Uxn *c, char *filepath); +int bootuxn(Uxn *c); +int evaluxn(Uxn *u, Uint16 vec); +Device *portuxn(Uxn *u, Uint8 id, char *name, void (*talkfn)(Device *, Uint8, Uint8)); +#endif // UXNGBA_UXN_H -- cgit v1.2.1