Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/Mikejmoffitt/mdk

Barebones megadrive development setup.
https://github.com/Mikejmoffitt/mdk

Last synced: 2 months ago
JSON representation

Barebones megadrive development setup.

Awesome Lists containing this project

README

        

MDK: Mega Drive Software Development Kit
=======================================

Origin
------
This project was born out of my wanting to write my own C support code for Mega Drive when I was working on a particular game.
I had developed it in another SDK, but between a mismatch in code style, old code not aging well, and some functionally completely breaking when I updated to a newer version, I wanted to feel a little more in control of my project.
So, I started fresh, and tried to compartmentalize all of my code that interacted with the MD hardware, keeping it divorced from the game logic where sensible.
This project continues to receive updates as I add features or change it during the development of that game.

IT IS NOT YET STABLE! The API may change at any time, until that game is done.

Goals
-----
This project aims to supply a set of minimal support code for use in making software for Sega Mega Drive, Genesis, System C, and System C2 platforms using the C programming language.
Current solutions exist on both ends of the complexity spectrum, with straight 68000 assembly programming on one and SGDK on the other.
This project aims to deliver something a little more in the middle in terms of weight and simplicity.

This project supports Linux, Windows, and MacOS-based development. For Windows, I strongly suggest using Windows Subsytem for Linux.

Rather than provide a library to link against, this project is intended to act as a skeleton that is the basis of another project.
MD support functions may be compiled and used. The suggested use case is to make an `mdk` submodule for a project.

Sample code and Makefiles exist in the `samples/` directory.
A makefile is provided that will recursively look for source files in the `src/` directory. It links using the provided linker script, which puts ROM from $000000 to $3FFFFF and RAM at $FF0000-$FFFFFF.

Setup
=====

Environment Prerequisites
-------------------------
The Sega Mega Drive / Genesis is powered by the Motorola 68000 processor. In this era, it is expected that development is gone through cross-assembly and cross-compilation from a more powerful host computer. To that end, we need to install a toolchain.

Building the Cross Compiler
---------------------------
A Makefile has already been made which will clone the GCC 12 source, configure it appropriately for M68000, and kick off a build. Run the following commands:

```
$ git clone [email protected]:andwn/m68k-gcc-toolchain
$ cd m68k-gcc-toolchain
$ make
$ sudo make install
```

This will install the toolchain into /opt/toolchains/m68k-elf/.

Afterwards, you may remove the build artifacts and GCC sources cloned earlier.

```
$ rm -rf ./m68k-gcc-toolchain
```

Starting a Project
------------------

If you are starting fresh, I recommend copying the contents of blank-project into your root and using that:

```
$ cp -r mdk/blank-project my-project
```

Then, add MDK as a submodule:

```
$ cd my-project
$ git submodule add [email protected]:mikejmoffitt/mdk mdk
$ git add mdk && git commit -m "Added MDK submodule."
```

The default Makefile searches in the `src` directory recursively for any .c files or .s files, and compiles or assembles them accordingly.
In addition, files placed in `res` are included in binary form. Their usage is detailed further below.
Edit the Makefile to change the name from blank-project to that if your project.

At this stage, you should be able to build your project, though it will not do anything interesting.

```
$ make
```

It's not required to do so, but you may want to edit mdk/header.inc to include your project metadata as well.

You can periodically update MDK with the following command:

```
$ git submodule update --remote --merge
$ git add mdk && git commit -m "Updated MDK submodule."
```

Extra Dependencies
------------------

If you have edited your Makefile to include additional generated files (script parsing, data generation, etc), you should declare any external build dependencies with `EXTERNAL_DEPS`, and make the build system aware of externally generated artifacts with `EXTERNAL_ARTIFACTS` (so they can be cleaned when the `clean` target is run).

Utils
-----

MDK relies on a few host-side utilities, so you will want to include `utils/` in your project.

SDK Usage
=========

Paths and Sources
-----------------
MDK presents two directories in your source path: `md`, which contains the core Mega Drive-related functionality, and `util`, which has useful auxiliary functions that aren't considered core functions.

Different components are in `md`, and their header files will provide useful structures, enums, and documentation.
However, `md/megadrive.h` exists to declare a function which initializes all of these components, and if included you may avoid referencing each component separately.

Interaction
-----------
The C runtime startup code clears RAM, sets up initial data for static variables, sets up the stack, and jumps to `main()`.
That's all that is done out of the C programmer's sight. However, I'd recommend making the very first line in `main()` a call to `megadrive_init()`:

```
megadrive_init();
```

This sets the Mega Drive to sane defaults, and initializes various helper systems for the peripherals.
Following that, an infinite loop like this is sufficient:

```
while(1)
{
// Your logic goes here!
megadrive_finish();
}
```

Samples
-------

Check out the samples/ directory for some small example projects that show usage of the SDK.

Platforms and Unused Code
-------------------------

MDK presently supports the following platforms:
* Mega Drive / Sega Genesis
* Sega System C2

The default platform is Mega Drive, and the symbol MDK_TARGET_MD is defined.
You may specify another platform by setting the following in the Makefile:

```
TARGET_PLATFORM := MDK_TARGET_C2
```

Multiple platforms means that some SDK code may not be relevant to your platform.
If the code is not relevant, it is not called, or the branch to determine whether to call it is extremly cheap (i.e. a single `tst.w`)
As for code size, GCC does a good job deleting unused functions and variables, so code size will not suffer.
As for memory usage, unnecessary variables are gated by #ifdef guards, and will not waste RAM.

Components
==========
For each component, I recommend reading the notes in the header
files. I will summarize the components here.

Platform
--------

`megadrive_finish()` should be called at the end of one frame of execution\*, and will do the following things:
* Terminate the sprite list
* Queue any CRAM (palette) memory transfers
* Wait for vertical blank to begin
* Poll controllers and store button state
* Process pending DMA transfers (to VRAM, CRAM, or VSRAM)

You can see the body of `megadrive_finish()` in md/megadrive.h. As with almost everything in this project, you aren't required to use it. Feel free to copy the parts you like, or call them elsewhere, if you really want to.

\* Small note on execution timing and vblank: On a system with fixed specifications like the Mega Drive, it is not only normal but also recommended that one "tick" of game engine execution occur synchronously with the refresh of the screen.
As a result, it's typical that one frame of logic runs, and then waits for the end of the frame. I am not doing anything from stopping you from running your game based on timer reads from the OPN or something, if you really want to.

Interrupt Handlers
------------------
`irq.s` is where the interrupt handlers live. The vblank ISR does
three things: signals the start of vblank for a synchronization
function, polls and caches controller data, and processes the DMA
queue with any remaining time.

The hblank and controller th pin ISRs have been left empty. If you
wish to do anything with them, you will want to place a handler in
`irq.s`, or at least call out to a C function of your choosing from
here.

VDP Control
-----------
`vdp.h` contains VDP control functions. Most registers do not need
to be manipulated directly, as separate manipulation functions exist
with some level of friendliness on top. Registers written are
cached, so that they may be "read back" even though the VDP does not
actually support this feature. This is useful for restoring
temporarily changed register values.

DMA control
-----------
`dma.h` contains DMA related functions. On the Mega Drive, VRAM,
CRAM, and VSRAM are all written to indirectly through the VDP, as
they are not memory mapped. For transferring large chunks of data,
DMA is the only efficient way to do this. However, timing a DMA can
be complicated, as it not only ties up the CPU bus, but faces VRAM
contention during the active display region.

DMA transfers are registered with calls to the DMA queue functions, and that list is processed during vertical blank (from `md_dma_process()`).
A single high-priority slot exists for the sake of the sprite table, which will be checked and transferred before any others.

If you really want a DMA to occur immediately, you may call `md_dma_process()` right after queueing a transfer.

Palettes
--------
`pal.h` has functions for uploading an entire palette, or setting individual colors.
It operates on a 64-entry palette cache in work RAM, and keeps track of whether a palette "line" (16-color grouping native to the video system) has changed.
Accordingly, it will queue a transfer with DMA to be done during vertical blank.

IO
--
`io.h` is used to get controller data. It supports 3 and 6 button controllers, and SMS controllers with a caveat.

A six-button pad is detected with a simple herusitic, and a special bit is set in the returned data (MD_PAD_6B).
You may test this bit to determine if a six-button pad is in use.

If no pad is plugged in, `MD_PAD_UNPLUGGED` is set in the controller data.
If an SMS controller is in use, this bit may get set as a side effect.
It is safe to ignore this bit.

Sprites
-------
`spr.h` houses sprite placement functions. The sprite table is
exposed under a global symbol `g_sprite_table`, but sprite placement
is easy using the `md_spr_put()` inline function. Sprites are placed into
a buffer in main memory with this function. Once a frame's
calculation is finished, `md_spr_finish()` will terminate the sprite list
properly, and schedule a DMA transfer of the sprite table to VRAM.

`md_spr_mask_line_full` will insert a "magic sprite" that masks off any other sprites appearing on the specified scanline.

`md_spr_finish()` is called by `megadrive_finish()`, so if you are using that, you do not need to worry about it.

System
------
`sys.h` is for controlling various top level system functions.
Interrupts may be disabled and enabled, as well as Z80 bus
manipulation and initialization. The status register can be checked
too, to retrieve the system region, revision, refresh rate, etc.

Interrupts and Exceptions
-------------------------
`irq.h` presents `md_irq_register()` by which a callback can be associated with interrupts and excepptions.

```
void irq_callback(void);
{ ... }

md_irq_register(MD_IRQ_VBLANK, irq_callback);
```

They are called safely from an interrupt context, with register clobber protection.

The vertical blank ISR clears a flag related to frame timing, and then calls the callback if it is there. Registering a callback will not interfere with normal operation.

Sound
-----------
`opn.h` and `psg.h` contain functions for talking to the FM and PSG
sound hardware, respectively. You may use these to create your own
sound driver, but that task is typically relegated to the Z80. You
might find the PSG channel pitch to be a better debugging tool than
it appears at first glance.

Including Binary Data
=====================

A tool called `bin2s` is included with this project, and is a build
target for the host machine. It takes several binary files and
generates an assembly language (.s) file containing their data, with
symbols named after their name and location.

If a file structure is created like this:

```
res/
foo.bin
bar/baz.bin
```

the following `uint8_t` arrays will be defined:

```
extern const uint8_t res_foo_bin[];
extern const uint8_t res_bar_baz_bin[];
```

The following sizes will also be defined:

```
extern const uint32_t res_foo_bin_size;
extern const uint32_t res_bar_baz_bin_size;
```

If you wish to generate files using other tools that will then be placed in `res/`, you can declare them as external dependencies.

Hardware Testing
================

Bryan Topp's Megaloader is included, allowing for the `flash` make
target to push the game binary over to a Mega Everdrive connected via
USB. This is for testing your program on hardware.

TODO
====

This project is far from complete. In no particular order, there are
some tasks I'd like to get taken care of:

* **Z80 development**
I would like for Z80 sound driver development to be a first-class
endeavor. Providing a few sample drivers for different needs would
not be a bad idea (include Echo, XGM, my own engine, and a simple
PCM playback reference driver perhaps).
* **DMA slicing**
If a VRAM transfer is projected to overrun the remaining bandwidth,
it should be split into one or more smaller transfers, deferred to
the next vblank.
* **Tilemap functions**
This is not a core function, but I would like to include a
reference implementation of a simple scrolling tilemap in
`src/util/plane.c`. Right now, `plane.h` only offers a function
to clear planes using a DMA Fill.
* **Bank switching support**
Allow placing object files in specific sections that get mapped
to specific 512 KiB banks for use with a Sega mapper or 2 MiB
banks for use with a simpler discrete mapper.