Building a ROM
So as I said in my last post, there is no IDE or standard dev kit to use when building a 32X Game. Fortunately some of the heroes of the homebrew community have pieced together the parts to get a simple game functioning.Where to begin.
Building a Toolchain for 32X
Let's assume you have a basic familiarity with the C language and compilers and you want to write your game in C as opposed to SH2 assembly language. In order to build a 32X ROM you will need to compile your code for the architecture of the 32X which happens to be the 32-bit Hitachi SH2 processor (as well as a secondary tool chain for the 16-bit M68k of the Genesis). Your standard C compiler or IDE is probably using a toolchain which is designed to target your 32-bit or 64-bit architecture of the PC, based on x86. This will of course produce a binary set of instructions that would not be readable by our friend the 32X. So before going further you must find (or build) a 32X compatible toolchain.Courtesy SpritesMind.net forum poster Chilly WillyIf you do not want to build the toolchain yourself with the above steps, you may be able to find posters who have posted their copy of the resulting binaries which you could then take and use (for Windows OS for example). Furthermore most of the steps above are for a Linux environment, to build for Windows you'll first need software to emulate a Linux environment such as MinGW. I did not have too much luck with Cygwin personally, but it should also work in theory.
Building a Genesis/32X toolchain
0 - System Prerequisites (based on testing on various Ubuntu Linux distros as of 1/18/2015):
- Package gcc-4.6 (I have run into issues using default gcc on Mac OSX, as well as higher versions of gcc on linux e.g. 4.8.2). I have had success with gcc-4.6.4
- Package texinfo (e.g. sudo apt-get install texinfo)
- Package p7zip-full for extracting .7z using Archive Manager (e.g. sudo apt-get install p7zip-full)
1 - Go here and download the following:
gcc-4.6.2.tar.bz2
Decompress it to wherever you keep your projects; you should end up with a folder called gcc-4.6.2.
2.1 - Go here and download mpfr-2.4.2.tar.bz2.
2.2 - Go here and download mpc-0.9.tar.gz.
2.3 - Go here and download gmp-5.0.4.tar.bz2.
Decompress them all in the same folder. You should have three folders called mpfr-2.4.2, mpc-0.9, and gmp-5.0.4. Rename them to get rid of the version numbers, leaving you with mpfr, mpc, and gmp. Copy them into the gcc-4.6.2 folder.
3 - Go here and download binutils-2.22.tar.bz2.
Decompress it in the same folder as the gcc folder so that you have two folders - gcc-4.6.2 and binutils-2.22.
4 - Go here and download newlib-1.20.0.tar.gz.
Decompress it in the same folder as gcc and binutils, leaving you with the folders - gcc-4.6.2, binutils-2.22, and newlib-1.20.0.
5 - Get this archive and decompress it to the same place as the previous directories. You should have two more directories, bin and ldscripts, in addition to the file, makefile-sega.
6 - If you wish to leave the makefile with the default path of /opt/toolchains/sega, make sure you have permission to write to /opt or the toolchain will fail to install to the path. Since there's nothing critical in /opt, it's easiest just to do "sudo chmod 777 -R /opt" which allows anyone to do anything they want in /opt.
7 - Run "make -f makefile-sega" - depending on the speed of your computer, in an hour or two you should have two toolchains in /opt/toolchains/sega: m68k-elf and sh-elf. Copy the ldscripts and bin directories to /opt/toolchains/sega.
You now have the latest gcc, binutils, and newlib for both the 68000 and the SH2. Both have compilers for C, C++, Objective-C, and Objective-C++. The bin directory has a few common tools one might use for compiling Z80 code for the MD. Copy whatever other tools you use into it, like sixpack or bin2c.
Note: The size of the built toolchain can be reduced by stripping the debug symbols from the executables in the bin directories, and by deleting the libraries meant for CPUs other than the 68000 and SH2. For example, you don't need the libraries for the 68020 or 68040 or SH3 or SH4, etc.
Let us assume you have your toolchain built and in place. On my system I decided to put it here C:\bin\_gcc\gen
My gen\ directory contained:
- bin
- ldscripts
- m68k-elf (toolchain for Sega Genesis M68k CPU)
- sh-elf (toolchain for 32X's SH2 CPUs)
Hello World ROM
32x Hello World |
You can download the source code and the ROM binary that this produces from my GitHub repo
https://github.com/ammianus/32xHomebrew/tree/master/32xhelloworld
Using the toolchain, I have created a simple HelloWorld 32X application. Besides displaying the text "Hello World" it also will print some text which corresponds to controls pressed (A, B, C, Up, Down, Left, Right, Start)
If you download the 32xhelloworld.zip I linked to, extract it to some folder. Note that I am on a Windows 7 Home Premium environment. To compile it you will need to run the Make command from the toolchain we've installed. First edit the 'Makefile' in a text editor of your choice ( I use the extremely productive Notepad++ ). Make sure to change the path of variable:
GENDEV=C:/bin/_gcc/gen
To whatever the path of your toolchain from above steps.
To run use a command a prompt (Run As Administrator on Windows 7). Change directory (cd) to the directory of the extracted 32xhelloworld directory. Then run the 'make' command.
C:\Users\ammianus\Documents\roms\homebrew\32xhelloworld>make
C:/bin/_gcc/gen/m68k-elf/bin/m68k-elf-as -m68000 --register-prefix-optional -o m
68k_crt0.o m68k_crt0.s
C:/bin/_gcc/gen/m68k-elf/bin/m68k-elf-ld -T C:/bin/_gcc/gen/ldscripts/md.ld --of
ormat binary -o m68k_crt0.bin m68k_crt0.o
C:/bin/_gcc/gen/m68k-elf/bin/m68k-elf-as -m68000 --register-prefix-optional -o m
68k_crt1.o m68k_crt1.s
C:/bin/_gcc/gen/m68k-elf/bin/m68k-elf-ld -T C:/bin/_gcc/gen/ldscripts/md.ld --of
ormat binary -o m68k_crt1.bin m68k_crt1.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-as --small -o sh2_crt0.o sh2_crt0.s
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer main.c -o main.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer slave.c -o slave.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O1 -Wall -c -fomit-frame-pointer
hw_32x.c -o hw_32x.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer font.c -o font.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-as --small -o lzss_decode.o lzss_decode.s
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer 32x_images.c -o 32x_images.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer graphics.c -o graphics.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -m2 -mb -O3 -Wall -Wformat -c -fomit-frame
-pointer shared_objects.c -o shared_objects.o
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-gcc -T mars-helloworld.ld -Wl,-Map=output.map
-nostdlib --warn-section-align --sort-common sh2_crt0.o main.o slave.o hw_32x.o
font.o lzss_decode.o 32x_images.o graphics.o shared_objects.o -LC:/bin/_gcc/gen
/sh-elf/sh-elf/lib -LC:/bin/_gcc/gen/sh-elf/lib/gcc/sh-elf/4.5.2 -lc -lgcc -lgcc
-Os-4-200 -lnosys -lm -o helloworld.elf
C:/bin/_gcc/gen/sh-elf/bin/sh-elf-objcopy -O binary helloworld.elf temp.bin
dd if=temp.bin of=helloworld.32x bs=64K conv=sync
0+1 records in
1+0 records out
65536 bytes (66 kB) copied, 0.00307811 s, 21.3 MB/s
This results in a bunch of new binary files generated in the same directory. One should be called helloworld.32x, this is the ROM file which you may load with your favorite Genesis/32X emulator (Fusion 3.64, Gens, etc...)
You can try it out and see what happens if you press your controller keys for up, up, down, down, left, right, left, right, b, a, b, a, start (for example).
A 32X Hello World: How It's Made
I apologize in advance for those who would expect at this point a tutorial where I tell you how to write every line of code from scratch in an easy to follow, incremental process. If my time and resources were infinite I would do that. But creating a working game in 32X is no easy process. I have been mostly copying code from other tutorials (especially from the hero, Chilly Willy of the SpritesMind forums) and modifying for what I wanted to do.
I have tried to strip out most of my code that isn't required for this tutorial, but it is still a complex animal after all.
Let's look at what the various files are in the 32helloworld.zip and what they do:
- 32x.h - General 32x / MD constants and function signatures for assembly file sh2_crt0.s
- 32x_images.h/.c - functions for drawing image (sprites) to different memory locations of 32x, e.g. the framebuffer
- font.c - Open Source default font characters for printing text to screen
- game_constants.h - my own set of game specific constants, some not used for helloworld
- graphics.h/.c - my own set of graphics functions for drawing circle, calculating sin values
- hw_32x.h/.c - Chilly Willy's set of 32X functions for various operations and for controlling the MegaDrive (MD)
- lzss_decode.h/.s - LZSS compression decoder for 32X which was used for decoding images compressed with lzss by the "sixpack" tool tile conversion and compression utility
- m68k_crt0.s - Chilly Willy's first part of the ROM header for MegaDrive and initial exception vectors. MegaDrive 68k assembly!
- m68k_crt1.s - Chilly Willy's third part of rom header, standard 32X header code. MegaDrive 68k assembly!
- main.c - the main code for the Master CPU, all logic for Hello World and handling input
- Makefile - the make file for this project
- mars-helloworld.ld - linker script for this project which puts the generated binaries into the right places in the ROM which sega/32x will be able to process.
- sh2_crt0.s - Chilly Willy's Rom header and SH2 init/exception code - must be first in object list
- shared_objects.h/.c - My own shared global variables
- slave.c - the main code for the Slave CPU, mostly just idle looping in Hello World.
- types.h - typedefs for various commonly used types e.g. typdef unsigned char uint8 //unsigned 8-bit integer
main.c source (thanks to Chilly Willy for some of the code):
/*
* Copyright 2014 ammianus
*/
#include <stdlib.h>
#include <math.h>
#include "types.h"
#include "32x.h"
#include "hw_32x.h"
#include "32x_images.h"
#include "game_constants.h"
#include "graphics.h"
#include "shared_objects.h"
#define DEBUG 1
#define MAP_WIDTH 320
#define IMAGE_START_ADDRESS 0
#define IMAGE_END_ADDRESS 1
#define NINJA_STANDING_INDEX 0
//global variable
char keyPressedText[100];
//const int world_width = WORLD_WIDTH;
//int paused = UNPAUSED;
//stores the previous buttons pressed for handle_input
unsigned short prev_buttons = 0;
/*
* Converts an integer to the relevant ascii char where 0 = ascii 65 ('A')
*/
char ascii(int letterIndex){
int asciiOffset = 65;
char newChar;
newChar = (char)(asciiOffset + letterIndex);
return newChar;
}
/*
* Call 32x hardware initialization routine
*/
void mars_init(void)
{
//using 256 color mode using palette
Hw32xInit(MARS_VDP_MODE_256, 0);
}
/*
* Check the current SEGA Controllers for inputs, update player, direction
* , speed, and action accordingly.
*/
void handle_input()
{
unsigned short new_buttons, curr_buttons;
//unsigned short buttons = 0;
//The type is either 0xF if no controller is present, 1 if a six button pad is present, or 0 if a three button pad is present. The buttons are SET to 1 if the corresponding button is pressed, and consist of:
//(0 0 0 1 M X Y Z S A C B R L D U) or (0 0 0 0 0 0 0 0 S A C B R L D U)
// MARS_SYS_COMM10 holds the current button values: - - - - M X Y Z S A C B R L D U
curr_buttons = MARS_SYS_COMM8;
if ((curr_buttons & SEGA_CTRL_TYPE) == SEGA_CTRL_NONE)
curr_buttons = MARS_SYS_COMM10; // if no pad 1, try using pad 2
// set if button changed
new_buttons = (curr_buttons & 0x0FFF) ^ prev_buttons;
prev_buttons = curr_buttons & 0x0FFF;
while (MARS_SYS_COMM6 == SLAVE_LOCK) ; // wait until slave isn't blocking
MARS_SYS_COMM6 = MASTER_LOCK; //tell slave to wait
//pause when start is first pressed only
if (curr_buttons & SEGA_CTRL_START )
{
sprintf(keyPressedText,"Key Pressed: Start");
}
else if (curr_buttons & SEGA_CTRL_UP )
{
sprintf(keyPressedText,"Key Pressed: Up");
}
else if (curr_buttons & SEGA_CTRL_DOWN )
{
sprintf(keyPressedText,"Key Pressed: Down");
}
else if (curr_buttons & SEGA_CTRL_LEFT )
{
sprintf(keyPressedText,"Key Pressed: Left");
}
else if (curr_buttons & SEGA_CTRL_RIGHT )
{
sprintf(keyPressedText,"Key Pressed: Right");
}
else if (curr_buttons & SEGA_CTRL_A)
{
sprintf(keyPressedText,"Key Pressed: A");
}
else if (curr_buttons & SEGA_CTRL_B)
{
sprintf(keyPressedText,"Key Pressed: B");
}
else if (curr_buttons & SEGA_CTRL_C)
{
sprintf(keyPressedText,"Key Pressed: C");
}
MARS_SYS_COMM6 = MASTER_STATUS_OK; //tell slave to resume
}
/*
* Starts application
*/
int main(void)
{
//
// Declarations
//
int more = 1;
int frameDelay = 5;
MARS_SYS_COMM6 = 0; //init COMM6 for slave
//
// Init Graphics
//
mars_init();
//init screen
Hw32xScreenClear();
HwMdClearScreen();
Hw32xSetBGColor(0,0,0,0);
sprintf(keyPressedText,"Key Pressed: ...");
currentFB = MARS_VDP_FBCTL & MARS_VDP_FS;
MARS_SYS_COMM6 = MASTER_STATUS_OK; // tells slave to start
//game loop
while ( more ) {
handle_input();
HwMdClearScreen();
// wait on flip to finish
while ((MARS_VDP_FBCTL & MARS_VDP_FS) != currentFB) {}
//print paused to screen
HwMdPuts("Hello World", 0x2000, 16, 14);
HwMdPuts(keyPressedText, 0x2000, 10, 16);
//
// draw to FB
//
//redraw background 10*BG_TILE_SIZE
while (MARS_SYS_COMM6 == SLAVE_LOCK) ; // wait until slave isn't blocking
MARS_SYS_COMM6 = 4; //tell slave to wait
MARS_SYS_COMM6 = 1; //tell slave to resume
//draw the 32X framebuffer line table with offset of 4
drawLineTable(4);
//flip the FB, without waiting on flip
currentFB ^= 1;
MARS_VDP_FBCTL = currentFB;
//do game loo
//artificially introduce delay
Hw32xDelay(frameDelay);
}
HwMdClearScreen ();
return 0;
} // end of main function
In the main function int main(void), there is a while loop which is where the main action takes place. First when the program initializes it will set various attributes of the 32X and the synchronization between the master CPU running the main.c and slave CPU. The place where any real logic is happening in this game loop are where handle_input(); function is called for each loop iteration. The handle_input() function reads the current control inputs and determines if a button has been pressed, then sets a char[] with some text to say which has been pressed. The place where this gets printed to the output is with the lines:
HwMdPuts("Hello World", 0x2000, 16, 14);
HwMdPuts(keyPressedText, 0x2000, 10, 16);
The HwMdPuts() function is a function from hw_32x.h. As the name suggests, this actually is sending a command to the MegaDrive (Md aka Genesis) to print some text to the sprite layer of the Genesis. This is displayed on top of the 32X display output, which was only a black background.
This is certainly not the only way to achieve the same result, if you wished you would be able to create functions to print directly the the 32X display, but it is slightly more involved and get's into the 32X graphics systems. This just shows how you can get a ROM to be build and run in an emulator, as well as offload some processing work to the M68k cpu in the Genesis.
More on how to display graphics in my next installment!
Further reading:
Further references to other simple projects to get you started, courtesy of SpritesMind forums:32X ROM Template [ASM]
Toolchain with links to several demos program projects