summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-05-03 01:23:56 +0300
committerOxore <oxore@protonmail.com>2023-05-03 01:29:14 +0300
commitf2016e84699c4f8a83e759fecac9e5dacd490a40 (patch)
treeb4a94dae0a8a192fd2d780cde5df5fe1280c7474
Initial commit
-rw-r--r--.gitignore5
-rw-r--r--Makefile25
-rw-r--r--_rom.ld15
-rw-r--r--_sram.ld26
-rw-r--r--m68k.ld4
-rw-r--r--main.c332
-rw-r--r--startup.c122
7 files changed, 529 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..67bcd8c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.elf
+*.bin
+compile_commands.json
+disasm.dir/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c66436d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,25 @@
+WARNFLAGS = -Wall -Wextra -pedantic -Wshadow -Wlogical-op
+OPTFLAGS = -Os
+ARCHFLAGS = -m68000
+_CFLAGS = $(CFLAGS) $(WARNFLAGS) $(ARCHFLAGS) $(OPTFLAGS) -g3 -ffunction-sections -fdata-sections
+LDSCRIPTS = m68k.ld _sram.ld _rom.ld
+_LDFLAGS = $(LDFLAGS) $(OPTFLAGS) $(addprefix -T,$(LDSCRIPTS)) --gc-sections
+
+OBJECTS=startup.o main.o
+
+.PHONY: all
+all: hellorom.bin
+
+%.bin: %.elf Makefile
+ m68k-none-elf-objcopy -O binary $< $@
+
+hellorom.elf: $(OBJECTS) $(LDSCRIPTS) Makefile
+ m68k-none-elf-ld -o $@ $(OBJECTS) $(_LDFLAGS)
+
+OBJECTS: Makefile
+
+%.o: %.c Makefile
+ m68k-none-elf-gcc -m68000 -c $< -o $@ $(_CFLAGS)
+
+clean:
+ rm -rfv hellorom.elf hellorom.bin $(OBJECTS)
diff --git a/_rom.ld b/_rom.ld
new file mode 100644
index 0000000..55106be
--- /dev/null
+++ b/_rom.ld
@@ -0,0 +1,15 @@
+SECTIONS {
+ . = ORIGIN(ROM);
+ .text : {
+ KEEP(*(.stack))
+ KEEP(*(.vectors))
+ KEEP(*(.smd_header))
+ KEEP(*(.text))
+ . = ALIGN(4);
+ *(.text*)
+ . = ALIGN(4);
+ KEEP(*(.rodata))
+ *(.rodata*)
+ . = ALIGN(4);
+ } >ROM
+}
diff --git a/_sram.ld b/_sram.ld
new file mode 100644
index 0000000..e9fcc24
--- /dev/null
+++ b/_sram.ld
@@ -0,0 +1,26 @@
+SECTIONS {
+ __stacktop = ORIGIN(SRAM) + 0xe000;
+ __data_load = LOADADDR(.data);
+ . = ORIGIN(SRAM);
+
+ .data ALIGN(4) : {
+ __data_start = .;
+ *(.data)
+ *(.data*)
+ . = ALIGN(4);
+ __data_end = .;
+ } >SRAM AT >ROM
+
+ .bss ALIGN(4) (NOLOAD) : {
+ __bss_start = .;
+ *(.bss)
+ *(.bss*)
+ . = ALIGN(4);
+ __bss_end = .;
+ *(.noinit)
+ *(.noinit*)
+ } >SRAM
+
+ . = ALIGN(4);
+ __heap_start = .;
+}
diff --git a/m68k.ld b/m68k.ld
new file mode 100644
index 0000000..dcc5257
--- /dev/null
+++ b/m68k.ld
@@ -0,0 +1,4 @@
+MEMORY {
+ ROM(rx) : ORIGIN = 0x00000000, LENGTH = 4M
+ SRAM(rwx) : ORIGIN = 0x00ff0000, LENGTH = 64K
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..5852765
--- /dev/null
+++ b/main.c
@@ -0,0 +1,332 @@
+// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/master/1_hello_world/hello.asm
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define VDP_DATA ((volatile uint16_t*)0x00C00000L)
+#define VDP_CONTROL ((volatile uint16_t*)0x00C00004L)
+#define VDP_CMD_VRAM_WRITE ((uint32_t)0x40000000L)
+#define VDP_CMD_CRAM_WRITE ((uint32_t)0xC0000000L)
+#define VRAM_ADDR_TILES (0x0000)
+#define VRAM_ADDR_PLANE_A (0xC000)
+#define VRAM_ADDR_PLANE_B (0xE000)
+#define VDP_SCREEN_WIDTH (0x0140)
+#define VDP_SCREEN_HEIGHT (0x00F0)
+#define VDP_PLANE_WIDTH (0x40)
+#define VDP_PLANE_HEIGHT (0x20)
+#define HARDWARE_VER_ADDRESS ((uint8_t*)0x00A10001L)
+#define TMSS_ADDRESS ((uint32_t*)0x00A14000L)
+#define TMSS_SIGNATURE (('S' << 24) | ('E' << 16) | ('G' << 8) | 'A')
+#define SIZE_WORD (2)
+#define SIZE_LONG (4)
+#define SIZE_PALETTE_B (0x20)
+#define SIZE_PALETTE_W (SIZE_PALETTE_B/SIZE_WORD)
+#define SIZE_PALETTE_L (SIZE_PALETTE_B/SIZE_LONG)
+#define SIZE_TILE_B (0x20)
+#define SIZE_TILE_W (SIZE_TILE_B/SIZE_WORD)
+#define SIZE_TILE_L (SIZE_TILE_B/SIZE_LONG)
+#define TEXT_POS_X (0x08)
+#define TEXT_POS_Y (0x04)
+
+// ==============================================================
+// TILE IDs
+// ==============================================================
+// The indices of each tile above. Once the tiles have been
+// written to VRAM, the VDP refers to each tile by its index.
+// ==============================================================
+#define TILE_ID_SPACE (0x0)
+#define TILE_ID_H (0x1)
+#define TILE_ID_E (0x2)
+#define TILE_ID_L (0x3)
+#define TILE_ID_O (0x4)
+#define TILE_ID_W (0x5)
+#define TILE_ID_R (0x6)
+#define TILE_ID_D (0x7)
+#define TILE_COUNT (0x8) // Last entry is just the count
+
+static uint8_t vdp_registers[] = {
+ 0x14, // 0x00: H interrupt on, palettes on
+ 0x74, // 0x01: V interrupt on, display on, DMA on, Genesis mode on
+ 0x30, // 0x02: Pattern table for Scroll Plane A at VRAM 0xC000 (bits 3-5 = bits 13-15)
+ 0x00, // 0x03: Pattern table for Window Plane at VRAM 0x0000 (disabled) (bits 1-5 = bits 11-15)
+ 0x07, // 0x04: Pattern table for Scroll Plane B at VRAM 0xE000 (bits 0-2 = bits 11-15)
+ 0x78, // 0x05: Sprite table at VRAM 0xF000 (bits 0-6 = bits 9-15)
+ 0x00, // 0x06: Unused
+ 0x00, // 0x07: Background colour: bits 0-3 = colour, bits 4-5 = palette
+ 0x00, // 0x08: Unused
+ 0x00, // 0x09: Unused
+ 0x08, // 0x0A: Frequency of Horiz. interrupt in Rasters (number of lines travelled by the beam)
+ 0x00, // 0x0B: External interrupts off, V scroll fullscreen, H scroll fullscreen
+ 0x81, // 0x0C: Shadows and highlights off, interlace off, H40 mode (320 x 224 screen res)
+ 0x3F, // 0x0D: Horiz. scroll table at VRAM 0xFC00 (bits 0-5)
+ 0x00, // 0x0E: Unused
+ 0x02, // 0x0F: Autoincrement 2 bytes
+ 0x01, // 0x10: Scroll plane size: 64x32 tiles
+ 0x00, // 0x11: Window Plane X pos 0 left (pos in bits 0-4, left/right in bit 7)
+ 0x00, // 0x12: Window Plane Y pos 0 up (pos in bits 0-4, up/down in bit 7)
+ 0xFF, // 0x13: DMA length lo byte
+ 0xFF, // 0x14: DMA length hi byte
+ 0x00, // 0x15: DMA source address lo byte
+ 0x00, // 0x16: DMA source address mid byte
+ 0x80, // 0x17: DMA source address hi byte, memory-to-VRAM mode (bits 6-7)
+};
+
+static uint16_t palette[SIZE_PALETTE_W] = {
+ 0x0000, // Colour 0 = Transparent
+ 0x0000, // Colour 1 = Black
+ 0x0EEE, // Colour 2 = White
+ 0x000E, // Colour 3 = Red
+ 0x00E0, // Colour 4 = Blue
+ 0x0E00, // Colour 5 = Green
+ 0x0E0E, // Colour 6 = Pink
+ 0x0000, // Leave the rest black...
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+};
+
+static uint32_t characters[TILE_COUNT * SIZE_TILE_L] = {
+ // Space
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ // H
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x00000000L,
+ // E
+ 0x22222220L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x00000000L,
+ // L
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x00000000L,
+ // O
+ 0x22222220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222220L,
+ 0x00000000L,
+ // W
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22020220L,
+ 0x22020220L,
+ 0x22020220L,
+ 0x22222220L,
+ 0x00000000L,
+ // R
+ 0x22222200L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222200L,
+ 0x22022000L,
+ 0x22002200L,
+ 0x22000220L,
+ 0x00000000L,
+ // D
+ 0x22222200L,
+ 0x22002220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22002220L,
+ 0x22222200L,
+ 0x00000000L,
+};
+
+// Set the VRAM (video RAM) address to write to next
+static inline void SetVRAMWrite(uint16_t addr)
+{
+ *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_VRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
+}
+
+// Set the CRAM (colour RAM) address to write to next
+static inline void SetCRAMWrite(uint16_t addr)
+{
+ *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_CRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
+}
+
+static void VDP_WriteTMSS(void)
+{
+ // The TMSS (Trademark Security System) locks up the VDP if we don't
+ // write the string 'SEGA' to a special address. This was to discourage
+ // unlicensed developers, since doing this displays the "LICENSED BY SEGA
+ // ENTERPRISES LTD" message to screen (on Mega Drive models 1 and higher).
+ //
+ // First, we need to check if we're running on a model 1+, then write
+ // 'SEGA' to hardware address 0xA14000.
+ const uint8_t ver = (*HARDWARE_VER_ADDRESS) & 0x0f;
+ if (ver != 0) {
+ *TMSS_ADDRESS = TMSS_SIGNATURE;
+ }
+
+ // Check VDP
+ // Read VDP status register (hangs if no access)
+ *VDP_CONTROL;
+}
+
+static void VDP_LoadRegisters(void)
+{
+ // To initialise the VDP, we write all of its initial register values from
+ // the table at the top of the file, using a loop.
+ //
+ // To write a register, we write a word to the control port.
+ // The top bit must be set to 1 (so 0x8000), bits 8-12 specify the register
+ // number to write to, and the bottom byte is the value to set.
+ //
+ // In binary:
+ // 100X XXXX YYYY YYYY
+ // X = register number
+ // Y = value to write
+
+ // Set VDP registers
+ for (size_t i = 0; i < sizeof(vdp_registers) / sizeof(*vdp_registers); i++) {
+ const uint16_t cmd = 0x8000; // 'Set register 0' command
+ const uint16_t reg_num = i << 8;
+ *VDP_CONTROL = cmd | reg_num | vdp_registers[i];
+ }
+}
+
+void __start(void)
+{
+ // ==============================================================
+ // Initialise the Mega Drive
+ // ==============================================================
+
+ // Write the TMSS signature (if a model 1+ Mega Drive)
+ VDP_WriteTMSS();
+
+ // Load the initial VDP registers
+ VDP_LoadRegisters();
+
+ //==============================================================
+ // Clear VRAM (video memory)
+ //==============================================================
+
+ // Setup the VDP to write to VRAM address 0x0000 (start of VRAM)
+ SetVRAMWrite(0x0000);
+
+ // Write 0's across all of VRAM
+ const size_t count = (0x00010000 / SIZE_WORD); // Loop counter = 64kb, in words
+ for (size_t i = 0; i < count; i++) {
+ *VDP_DATA = 0x0; // Write a 0x0000 (word size) to VRAM
+ }
+
+ //==============================================================
+ // Initialise status register and set interrupt level.
+ // This begins firing vertical and horizontal interrupts.
+ //==============================================================
+ asm inline volatile (" move.w #0x2300, %sr");
+
+ //==============================================================
+ // Write the palette to CRAM (colour memory)
+ //==============================================================
+
+ // Setup the VDP to write to CRAM address 0x0000 (first palette)
+ SetCRAMWrite(0x0000);
+
+ // Write the palette to CRAM
+ for (size_t i = 0; i < SIZE_PALETTE_W; i++) {
+ *VDP_DATA = palette[i]; // Write palette entry
+ }
+
+ //==============================================================
+ // Write the font tiles to VRAM
+ //==============================================================
+
+ // Setup the VDP to write to VRAM address 0x0000 (the address of the first graphics tile, index 0)
+ SetVRAMWrite(VRAM_ADDR_TILES);
+
+ // Write the font glyph tiles to VRAM
+ for (size_t i = 0; i < TILE_COUNT * SIZE_TILE_L; i++) {
+ *(volatile uint32_t*)VDP_DATA = characters[i]; // Write palette entry
+ }
+
+ //==============================================================
+ // Write the tile IDs of "HELLO WORLD" to Plane A's cell grid
+ //==============================================================
+
+ // Each scroll plane is made up of a 64x32 tile grid (this size is specified in VDP register 0x10),
+ // with each cell specifying the index of each tile to draw, the palette to draw it with, and
+ // some flags (for priority and flipping).
+ //
+ // Each plane cell is 1 word in size (2 bytes), in the binary format
+ // ABBC DEEE EEEE EEEE, where:
+ //
+ // A = Draw priority (1 bit)
+ // B = Palette index (2 bits, specifies palette 0, 1, 2, or 3)
+ // C = Flip tile horizontally (1 bit)
+ // D = Flip tile vertically (1 bit)
+ // E = Tile index to draw (11 bits, specifies tile index from 0 to 2047)
+ //
+ // Since we're using priority 0, palette 0, and no flipping, we
+ // only need to write the tile ID and leave everything else zero.
+
+ // Setup the VDP to write the tile ID at text_pos_x,text_pos_y in plane A's cell grid.
+ // Plane A's cell grid starts at address 0xC000, which is specified in VDP register 0x2.
+ //
+ // Since each cell is 1 word in size, to compute a cell address within plane A:
+ // ((y_pos * plane_width) + x_pos) * size_word
+ SetVRAMWrite(VRAM_ADDR_PLANE_A + (((TEXT_POS_Y * VDP_PLANE_WIDTH) + TEXT_POS_X) * SIZE_WORD));
+
+ // then move the tile ID for "H" to VRAM
+ *VDP_DATA = TILE_ID_H;
+
+ // Repeat for the remaining characters in the string.
+ // We don't need to adjust the VRAM address each time, since the auto-increment
+ // register (VDP register 0xF) is set to 2, so the destination address
+ // will automatically increment by one word (conveniently the size of a cell)
+ // after each write.
+ *VDP_DATA = TILE_ID_E;
+ *VDP_DATA = TILE_ID_L;
+ *VDP_DATA = TILE_ID_L;
+ *VDP_DATA = TILE_ID_O;
+ *VDP_DATA = TILE_ID_SPACE;
+ *VDP_DATA = TILE_ID_W;
+ *VDP_DATA = TILE_ID_O;
+ *VDP_DATA = TILE_ID_R;
+ *VDP_DATA = TILE_ID_L;
+ *VDP_DATA = TILE_ID_D;
+
+ // Finished!
+
+ //==============================================================
+ // Loop forever
+ //==============================================================
+ // This loops forever, effectively ending our code. The VDP will
+ // still continue to run (and fire vertical/horizontal interrupts)
+ // of its own accord, so it will continue to render our Hello World
+ // even though the CPU is stuck in this loop.
+ while (1);
+}
diff --git a/startup.c b/startup.c
new file mode 100644
index 0000000..3b3c017
--- /dev/null
+++ b/startup.c
@@ -0,0 +1,122 @@
+typedef void (*ptr_func_t)();
+
+struct smd_header {
+ char console_name[16];
+ char copyright[16];
+ char domestic_name[48];
+ char international_name[48];
+ char version[14];
+ unsigned short checksum;
+ char io_support[16];
+ unsigned long rom_start;
+ unsigned long rom_end;
+ unsigned long ram_start;
+ unsigned long ram_end;
+ unsigned long sram_enabled;
+ unsigned long unused1;
+ unsigned long sram_start;
+ unsigned long sram_end;
+ unsigned long unused2;
+ unsigned long unused3;
+ char notes[40];
+ char country_codes[16];
+};
+
+extern void __start(void);
+static void HSYNC_handler(void);
+static void VSYNC_handler(void);
+
+extern unsigned __stacktop;
+
+__attribute__((section(".stack"), used)) unsigned *__stack_init = &__stacktop;
+
+__attribute__((section(".vectors"), used)) ptr_func_t __isr_vectors[] = {
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ HSYNC_handler,
+ __start,
+ VSYNC_handler,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+ __start,
+};
+
+__attribute__((section(".smd_header"), used)) struct smd_header __smd_header = {
+ .console_name = "SEGA MEGA DRIVE ",
+ .copyright = "BIGEVILCORP. ",
+ .domestic_name = "HELLO WORLD ",
+ .international_name = "HELLO WORLD ",
+ .version = "GM XXXXXXXX-XX",
+ .checksum = 0x0000,
+ .io_support = "J ",
+ .rom_start = 0,
+ .rom_end = 0xfffff,
+ .ram_start = 0xff0000,
+ .ram_end = 0xffffff,
+ .sram_enabled = 0x00000000,
+ .unused1 = 0x00000000,
+ .sram_start = 0x00000000,
+ .sram_end = 0x00000000,
+ .unused2 = 0x00000000,
+ .unused3 = 0x00000000,
+ .notes = " ",
+ .country_codes = " E ",
+};
+
+void HSYNC_handler(void) {}
+void VSYNC_handler(void) {}