diff options
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | Makefile | 25 | ||||
| -rw-r--r-- | _rom.ld | 15 | ||||
| -rw-r--r-- | _sram.ld | 26 | ||||
| -rw-r--r-- | m68k.ld | 4 | ||||
| -rw-r--r-- | main.c | 332 | ||||
| -rw-r--r-- | startup.c | 122 | 
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) @@ -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 = .; +} @@ -0,0 +1,4 @@ +MEMORY { +    ROM(rx) : ORIGIN = 0x00000000, LENGTH = 4M +    SRAM(rwx) : ORIGIN = 0x00ff0000, LENGTH = 64K +} @@ -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) {} | 
