Chilly Willy
Established Member
Lesson 1 - Make your own makefiles
Since a makefile is an important part of any gcc project, we should probably take a look at an example and see how everything works together so you can make your own. One thing I should stress right off the bat - KEEP IT SIMPLE, STUPID!! The KISS method is especially important for makefiles. Makefiles can be truly, horrifically, needlessly complicated. Look at this example.
Do you have the vaguest idea of what it's doing? Neither does ANYBODY else.
Now let's look at the Wolf32X makefile.
Let's look at it bit by bit.
We start by setting some variables for the programs we will use to build our game. Any time you see $( ), whatever is inside is a variable, be it one defined in the makefile, or a system environment variable passed in from the shell. To call the SH2 compiler, we just use $(SHCC) and make will replace that with $(GENDEV)/sh2/bin/gcc.
After that, most folks set variables for commonly used options for the programs.
In the above, FLAGS is used by gcc when compiling. OBJS is a list of object files we need to build the game. Using $(OBJS) is much easier than using the list over and over. The list can have multiple items per line. If you need more line, the "\" is used to tell make the next line continues the current line. VERY IMPORTANT! The indent used at the start of a line in a makefile MUST be a TAB!! Any other form of indentation (spaces) will cause the makefile to fail. That's why you should post makefiles in code tags for people unless you are trying to do a tutorial like this one; nearly all boards replace the tabs with spaces, which causes the makefile to fail if you try to use it directly.
Now let's get to the heart of the makefile - the rules.
Each rule has this format:
The target is what make is currently trying to build; the dependencies tell make the list of things that need to be built before the target can be built. If the dependencies don't exist, make goes through the list looking for a rule to make the dependencies. Once the dependencies exist, make then uses the commands to build the target.
The first target is all - when you just type "make", this will be the first target. Make sees that in order to make "all", it first needs to make m68k_Crt0.bin, m68k_crt1.bin, and wolf32x.bin. So it now looks for rules to make each of those.
There is the first. It says to make m68k_crt0.bin, it first needs src/m68k_crt0.s, which is the source file which always exists. So it now uses the commands to make the file. The first command calls the MD assembler to make the object file, and the second command calls the MD linker to convert the object file to binary. We now have the m68k_crt0.bin file.
The m68k_crt1.bin is done in a similar manner, so let's look at the third.
This tells make that in order to build wolf32x.bin, we first need $(OBJS). So the entire list of object files needs to exist before we can build wolf32x.bin. So make now looks for a rule for each object in the list of objects. However, we don't have any rules for them! That's where generic rules come in; while we don't have a specific rule for src/wl_play.o, we DO have a rule for %.o.
These rules use pattern matching to allow use to make one rule for a bunch of files. Any time you can do multiple files with a generic rule, do so. We COULD make a rule for every single object in OBJS, but that would be silly! KISS!!!
The first generic rule above says that for any file ending in .o that is not handled by a specific rule, we need a dependency of the same file with an ending of .c. Is there a src/wl_play.c file? You bet your sweet bippy! So, we have met the rule, so we do the commands to build the object file. In this case, we call the SH2 compiler with this line.
We saw the FLAGS earlier. What we are concerned with here are $< and $@. These are special symbols that stand for the dependency and the target file names, respectively. So $< is src/wl_play.c, and $@ is src/wl_play.o in our example. You can see how the pattern matching in generic rules work.
The second generic rule above similarly calls the SH2 assembler for assembly files as opposed to the compiler for c files. Between these two, we build all the object files in the list.
So now we have all the object files needed to build wolf32x.bin. Look at the command.
That calls the SH2 linker, which makes the binary file from the list of object files as well as a few SH2 libraries that come with the compiler.
So there you have it! You have made the game using a few variables and rules. But what about that last line?
That tells make that when you run "make clean" as the command in the shell, that there are no dependencies for the target "clean", so just run the command, which removes (deletes) the list of files. The wildcards mean all files in src ending in .o or .out or .bin will be deleted, as well as all files in the current directory that end in .bin. This allows us to delete all the various intermediate files, leaving just the source files.
Lesson 2 - Using assembly in your project
Let me start by saying inline assembly is a joke - don't bother. If you're going to use some assembly in your project, do it right and make a separate assembly file. It's actually easier than trying to remember all those ridiculous formats inline assembly require. It's also much more powerful.
Let's start with 68000 assembly; you may use it for Genesis or 32X projects. The standard compiler in the GNU Compiler Collection is AS; it is a part of binutils. You use "standard" Motorola syntax; the main differences from more common assemblers is the directives. Let's look at a few.
This sets the current section to the code section. With the linker script I supply with my Genesis toolchain, this puts any code or data following this directive into the rom.
This sets the current section to the data section. This puts any code or data following the directive into ram... with the proper startup code. In actuality, the code and data after this directive is still in the rom, but it has all references set so that when the startup code copies this code and data into ram, it will be referenced properly. If you look at the crt0.s file in my example, you'll see the loop that copies the code and data in the data section to ram.
Let's briefly look at a few handy directives you'll commonly use.
These directives make tables of bytes, words, and longs, respectively. You can have one value (as in the line with .long), or multiple values (as in the other two). Values are stored in big-endian format since that is the format the 68000 uses. It does NOT handle alignment of the data, so be careful with words and longs, which must be on at least a word boundary to avoid an exception. Which brings us to our next directive.
This directive increments the current address to the next boundary specified. For the 68000 version of AS, the value of the align directive is in bytes. 2 means align to two bytes, 4 means align to 4 bytes, etc. When in doubt, use an align directive. Be liberal - it doesn't hurt and is needed before words, longs, and instructions to avoid an exception. Only byte data can be on a byte boundary. Most hacks or homebrew that work in emulators, but fail on real hardware, usually have data or code on a byte boundary somewhere because they forgot to use the align directive.
Here are two more handy directives.
These turn the string into a byte table of the ASCII values of the string. The difference is the asciz directive always adds a byte of 0x00 to the end of the string. That would be necessary for standard C strings, which MUST be null terminated.
Now let's look at labels. AS for the 68000 allows you to use labels exactly the same as in C. There are no special characters needed. The labels are case sensitive, so watch the capitalization! Any label you use in assembly that isn't defined in the file is assumed to be defined in another file, so there is no extern directive like you see in C. To define a label as being seen externally requires the global directive.
The global directive tells AS that the specified label may be seen by other files. Not using the global directive makes any other defined labels local, so there is no static directive like you see in C. Labels may refer to code or data. It's up to the programmer to use what the labels refer to properly. There is no type safety - you can easily use a C array of longs as bytes in an assembly file. You could use C code as data, or vice versa.
So now how do we use code in assembly that properly interacts with C? This is what is referred to as the Application Binary Interface, or ABI. The 68000 ABI says that when you call an assembly language function, you must save registers D2 to D7, and A2 to A6. D0, D1, A0, and A1 may be modified freely without saving or restoring them. If you can, only use those registers in your assembly for better speed. When you have to use more registers, you must push them on the stack before you do so, and pop them off the stack before returning. When the code is entered, the stack points to a long that is the return address. The long at the stack pointer + 4 is the first argument passed to the function, if any. The long at the stack pointer + 8 is the second argument passed to the function, if any. Additional arguments, if any, are accessed on the stack in a similar manner, being at + 12, + 16, + 20, etc. First, in this case, means the left-most argument, with other arguments going right accessed at higher addresses. All arguments are pushed as long values regardless of size! If you pass a byte from C to assembly, it's pushed on the stack as a long. The return value, if any, should be left in D0.
Let's look at a simple example.
The "|" character indicates the start of a comment. You can also use /* */ for comments. Notice how we put an align directive before the function. Do this if your code ever follows byte data to avoid exceptions. Notice that since we only use D0 and D1, we don't need to save or restore any registers. Notice that the argument (short new_sr) is accessed at the stack pointer + 4. Notice how we put the old version of the status register in D0 as the return value.
Now let's look at a more complex example.
Notice how we use D2, so we must save it at the start, and restore it before we return. Because we pushed D2 onto the stack, any arguments are now one long further back on the stack; that is why the argument is now accessed at the stack pointer + 8 instead of + 4. Notice that we use both kinds of comments this function. Be sure to comment your code well enough that you can figure out what you are doing when you look at your code years later. Again, the return value is left in D0.
We are not going to cover more in this post as this should be enough to get you going. If you wish to know more of the directives, especially macros, consult the binutils documentation for AS.
Now let's look at AS for the SH2. It's very nearly the same as AS for the 68000 as all platforms use most of the same directives. So we mainly need to concern ourselves with the differences. The data directives are the same, but there is a difference as far as alignment goes - words need to be on word boundaries, like the 68000, but longs need to be on long boundaries; the 68000 can take longs on word or long boundaries - the SH2 cannot. Which brings the first difference in the directives - the value used with the align directive is the power of two used to set the current address. For example, the following are the same.
68000
SH2
SH2 code must be word aligned, but is slightly faster if aligned to a 16 byte boundary (.align 4).
Our next difference is in the labels - the SH2 assembler needs a "_" (underscore) character in front of labels if they are defined in C code, or if you wish to access the label from C. For example.
This function would be called from C like this.
Some more differences immediately are apparent. You use "!" for a comment instead of "|"; you can still use /* */ for comments. The next difference is in how arguments are passed in; the first argument is passed in R4, the second in R5, the third in R6, and the fourth in R7. Any other arguments are pushed on the stack. Use no more than four arguments for best speed. On the SH2, you must save R8 to R14, and the PR register. For example.
A practical example of an assembly routine is my code to stretch the frame buffer contents - it takes a half a line of data and doubles it to fill an entire line.
As far as the sections go, with my 32X toolchain, the text section still goes to the rom, while data goes to the SDRAM.
Okay, that's enough for now. I encourage people to look at the assembly files in the Tic Tac Toe examples, as well as the Yeti3D example.
Since a makefile is an important part of any gcc project, we should probably take a look at an example and see how everything works together so you can make your own. One thing I should stress right off the bat - KEEP IT SIMPLE, STUPID!! The KISS method is especially important for makefiles. Makefiles can be truly, horrifically, needlessly complicated. Look at this example.
Code:
# Makefile.in generated by automake 1.10.2 from Makefile.am.
# Makefile. Generated from Makefile.in by configure.
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
# Copyright (c) 2007 Hans Ulrich Niedermann <hun@n-dimensional.de>
#
# This Makefile fragment is free software; the author(s) give(s)
# unlimited permission to copy, distribute and modify it.
pkgdatadir = $(datadir)/gens
pkglibdir = $(libdir)/gens
pkgincludedir = $(includedir)/gens
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = i686-pc-linux-gnu
host_triplet = i686-pc-linux-gnu
target_triplet = i686-pc-linux-gnu
DIST_COMMON = $(am__configure_deps) $(srcdir)/Makefile.am \
$(srcdir)/Makefile.in $(srcdir)/config.h.in \
$(srcdir)/git_version.am $(top_srcdir)/configure compile \
config.guess config.sub depcomp install-sh ltmain.sh missing \
mkinstalldirs
subdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/gtk-2.0.m4 \
$(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
$(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
$(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
$(top_srcdir)/m4/sdl.m4 $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
configure.lineno config.status.lineno
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
CONFIG_HEADER = config.h
CONFIG_CLEAN_FILES =
SOURCES =
DIST_SOURCES =
RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
html-recursive info-recursive install-data-recursive \
install-dvi-recursive install-exec-recursive \
install-html-recursive install-info-recursive \
install-pdf-recursive install-ps-recursive install-recursive \
installcheck-recursive installdirs-recursive pdf-recursive \
ps-recursive uninstall-recursive
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
distclean-recursive maintainer-clean-recursive
ETAGS = etags
CTAGS = ctags
DIST_SUBDIRS = $(SUBDIRS)
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir)
am__remove_distdir = \
{ test ! -d $(distdir) \
|| { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \
&& rm -fr $(distdir); }; }
DIST_ARCHIVES = $(distdir).tar.gz
GZIP_ENV = --best
distuninstallcheck_listfiles = find . -type f -print
distcleancheck_listfiles = find . -type f -print
ACLOCAL = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run aclocal-1.10
ALLOCA =
AMTAR = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run tar
AM_CFLAGS = -Wall -Wextra -O0 -ggdb
AM_CPPFLAGS =
AM_CXXFLAGS = -Wall -Wextra -O0 -ggdb -fvisibility-inlines-hidden
AM_LDFLAGS = -Wl,--as-needed -Wl,--hash-style=both
AR = ar
AUTOCONF = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run autoconf
AUTOHEADER = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run autoheader
AUTOMAKE = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run automake-1.10
AWK = gawk
CC = gcc -std=gnu99
CCAS = gcc
CCASDEPMODE = depmode=gcc3
CCASFLAGS = -g -O2
CCDEPMODE = depmode=gcc3
CFLAGS = -g -O2
CPP = gcc -E
CPPFLAGS =
CXX = g++
CXXCPP = g++ -E
CXXDEPMODE = depmode=gcc3
CXXFLAGS = -g -O2
CYGPATH_W = echo
DEFS = -DHAVE_CONFIG_H
DEPDIR = .deps
DSYMUTIL =
DUMPBIN =
ECHO_C =
ECHO_N = -n
ECHO_T =
EGREP = /bin/grep -E
EXEEXT =
FGREP = /bin/grep -F
GETTIMEFLAG =
GL_LIBS = -lGL
GREP = /bin/grep
GTK_CFLAGS = -pthread -I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/pango-1.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 -DGTK_DISABLE_DEPRECATED -DDISABLE_DEPRECATED -DGSEAL_ENABLE
GTK_LIBS = -pthread -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lm -lcairo -lpng12 -lpango-1.0 -lfreetype -lfontconfig -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lrt -lglib-2.0
ICONV_LIBS =
INSTALL = /usr/bin/install -c
INSTALL_DATA = ${INSTALL} -m 644
INSTALL_PROGRAM = ${INSTALL}
INSTALL_SCRIPT = ${INSTALL}
INSTALL_STRIP_PROGRAM = $(install_sh) -c -s
LD = /usr/bin/ld
LDFLAGS =
LIBOBJS =
LIBS =
LIBTOOL = $(SHELL) $(top_builddir)/libtool
LIPO =
LN_S = ln -s
LTLIBOBJS =
MAKEINFO = ${SHELL} /home/jlfenton/Projects/linux/gens-gs-r7/missing --run makeinfo
MKDIR_P = /bin/mkdir -p
NASM = /usr/bin/nasm -O99 -f elf -D __GCC2 -g -F dwarf -I$(top_srcdir)/src/ -w-orphan-labels
NM = /usr/bin/nm -B
NMEDIT =
OBJDUMP = objdump
OBJEXT = o
OTOOL =
OTOOL64 =
PACKAGE = gens
PACKAGE_BUGREPORT = [email]gerbilsoft@verizon.net[/email]
PACKAGE_NAME = gens
PACKAGE_STRING = gens 2.16.7
PACKAGE_TARNAME = gens
PACKAGE_VERSION = 2.16.7
PATH_SEPARATOR = :
PKG_CONFIG = /usr/bin/pkg-config
PNG_LIBS = -lpng
RANLIB = ranlib
RC =
RT_LIBS = -lrt
SDL_CFLAGS = -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT
SDL_CONFIG = /usr/bin/sdl-config
SDL_LIBS = -L/usr/lib -lSDL
SED = /bin/sed
SET_MAKE =
SHELL = /bin/bash
STRIP = strip
VERSION = 2.16.7
X11_CFLAGS =
X11_LIBS = -lX11
XMKMF =
YASM =
abs_builddir = /home/jlfenton/Projects/linux/gens-gs-r7
abs_srcdir = /home/jlfenton/Projects/linux/gens-gs-r7
abs_top_builddir = /home/jlfenton/Projects/linux/gens-gs-r7
abs_top_srcdir = /home/jlfenton/Projects/linux/gens-gs-r7
ac_ct_CC = gcc
ac_ct_CXX = g++
ac_ct_DUMPBIN =
am__include = include
am__leading_dot = .
am__quote =
am__tar = ${AMTAR} chof - "$$tardir"
am__untar = ${AMTAR} xf -
bindir = ${exec_prefix}/bin
build = i686-pc-linux-gnu
build_alias =
build_cpu = i686
build_os = linux-gnu
build_vendor = pc
builddir = .
datadir = ${datarootdir}
datarootdir = ${prefix}/share
docdir = ${datarootdir}/doc/${PACKAGE_TARNAME}
dvidir = ${docdir}
exec_prefix = ${prefix}
host = i686-pc-linux-gnu
host_alias =
host_cpu = i686
host_os = linux-gnu
host_vendor = pc
htmldir = ${docdir}
includedir = ${prefix}/include
infodir = ${datarootdir}/info
install_sh = $(SHELL) /home/jlfenton/Projects/linux/gens-gs-r7/install-sh
libdir = ${exec_prefix}/lib
libexecdir = ${exec_prefix}/libexec
localedir = ${datarootdir}/locale
localstatedir = ${prefix}/var
lt_ECHO = echo
mandir = ${datarootdir}/man
mkdir_p = /bin/mkdir -p
oldincludedir = /usr/include
pdfdir = ${docdir}
prefix = /usr/local
program_transform_name = s,x,x,
psdir = ${docdir}
sbindir = ${exec_prefix}/sbin
sharedstatedir = ${prefix}/com
srcdir = .
sysconfdir = ${prefix}/etc
target = i686-pc-linux-gnu
target_alias =
target_cpu = i686
target_os = linux-gnu
target_vendor = pc
top_build_prefix =
top_builddir = .
top_srcdir = .
AUTOMAKE_OPTIONS = foreign
SUBDIRS = doc images xdg src
ACLOCAL_AMFLAGS = -I m4 --install
# The stamp file which is never created ensures that git_version.h is updated
# before every build. Having git_version.h in foo_SOURCES ensures a recompile
# of foo-bar.c if it is newer than the foo-bar.o file. Using noinst_foo_SOURCES
# instead of foo_SOURCES prevents shipping git_version.h in dist tarballs,
# which may cause false GIT_FOO readings.
BUILT_SOURCES = git_version.stamp
CLEANFILES = git_version.h
GIT_VERSION_CMD = $(SHELL) $(top_srcdir)/git_version.sh
all: $(BUILT_SOURCES) config.h
$(MAKE) $(AM_MAKEFLAGS) all-recursive
.SUFFIXES:
am--refresh:
@:
$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(srcdir)/git_version.am $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
echo ' cd $(srcdir) && $(AUTOMAKE) --foreign '; \
cd $(srcdir) && $(AUTOMAKE) --foreign \
&& exit 0; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
cd $(top_srcdir) && \
$(AUTOMAKE) --foreign Makefile
.PRECIOUS: Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
echo ' $(SHELL) ./config.status'; \
$(SHELL) ./config.status;; \
*) \
echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
esac;
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
$(SHELL) ./config.status --recheck
$(top_srcdir)/configure: $(am__configure_deps)
cd $(srcdir) && $(AUTOCONF)
$(ACLOCAL_M4): $(am__aclocal_m4_deps)
cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
config.h: stamp-h1
@if test ! -f $@; then \
rm -f stamp-h1; \
$(MAKE) $(AM_MAKEFLAGS) stamp-h1; \
else :; fi
stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
@rm -f stamp-h1
cd $(top_builddir) && $(SHELL) ./config.status config.h
$(srcdir)/config.h.in: $(am__configure_deps)
cd $(top_srcdir) && $(AUTOHEADER)
rm -f stamp-h1
touch $@
distclean-hdr:
-rm -f config.h stamp-h1
mostlyclean-libtool:
-rm -f *.lo
clean-libtool:
-rm -rf .libs _libs
distclean-libtool:
-rm -f libtool config.lt
# This directory's subdirectories are mostly independent; you can cd
# into them and run `make' without going through this Makefile.
# To change the values of `make' variables: instead of editing Makefiles,
# (1) if the variable is set in `config.status', edit `config.status'
# (which will cause the Makefiles to be regenerated when you run `make');
# (2) otherwise, pass the desired values on the `make' command line.
$(RECURSIVE_TARGETS):
@failcom='exit 1'; \
for f in x $$MAKEFLAGS; do \
case $$f in \
*=* | --[!k]*);; \
*k*) failcom='fail=yes';; \
esac; \
done; \
dot_seen=no; \
target=`echo $@ | sed s/-recursive//`; \
list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Making $$target in $$subdir"; \
if test "$$subdir" = "."; then \
dot_seen=yes; \
local_target="$$target-am"; \
else \
local_target="$$target"; \
fi; \
(cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|| eval $$failcom; \
done; \
if test "$$dot_seen" = "no"; then \
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
fi; test -z "$$fail"
$(RECURSIVE_CLEAN_TARGETS):
@failcom='exit 1'; \
for f in x $$MAKEFLAGS; do \
case $$f in \
*=* | --[!k]*);; \
*k*) failcom='fail=yes';; \
esac; \
done; \
dot_seen=no; \
case "$@" in \
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
*) list='$(SUBDIRS)' ;; \
esac; \
rev=''; for subdir in $$list; do \
if test "$$subdir" = "."; then :; else \
rev="$$subdir $$rev"; \
fi; \
done; \
rev="$$rev ."; \
target=`echo $@ | sed s/-recursive//`; \
for subdir in $$rev; do \
echo "Making $$target in $$subdir"; \
if test "$$subdir" = "."; then \
local_target="$$target-am"; \
else \
local_target="$$target"; \
fi; \
(cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|| eval $$failcom; \
done && test -z "$$fail"
tags-recursive:
list='$(SUBDIRS)'; for subdir in $$list; do \
test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
done
ctags-recursive:
list='$(SUBDIRS)'; for subdir in $$list; do \
test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
done
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
mkid -fID $$unique
tags: TAGS
TAGS: tags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
tags=; \
here=`pwd`; \
if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
include_option=--etags-include; \
empty_fix=.; \
else \
include_option=--include; \
empty_fix=; \
fi; \
list='$(SUBDIRS)'; for subdir in $$list; do \
if test "$$subdir" = .; then :; else \
test ! -f $$subdir/TAGS || \
tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \
fi; \
done; \
list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
test -n "$$unique" || unique=$$empty_fix; \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
$$tags $$unique; \
fi
ctags: CTAGS
CTAGS: ctags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
tags=; \
list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
test -z "$(CTAGS_ARGS)$$tags$$unique" \
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
$$tags $$unique
GTAGS:
here=`$(am__cd) $(top_builddir) && pwd` \
&& cd $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) $$here
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(DISTFILES)
$(am__remove_distdir)
test -d $(distdir) || mkdir $(distdir)
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
fi; \
cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
else \
test -f $(distdir)/$$file \
|| cp -p $$d/$$file $(distdir)/$$file \
|| exit 1; \
fi; \
done
list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
if test "$$subdir" = .; then :; else \
test -d "$(distdir)/$$subdir" \
|| $(MKDIR_P) "$(distdir)/$$subdir" \
|| exit 1; \
distdir=`$(am__cd) $(distdir) && pwd`; \
top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
(cd $$subdir && \
$(MAKE) $(AM_MAKEFLAGS) \
top_distdir="$$top_distdir" \
distdir="$$distdir/$$subdir" \
am__remove_distdir=: \
am__skip_length_check=: \
distdir) \
|| exit 1; \
fi; \
done
$(MAKE) $(AM_MAKEFLAGS) \
top_distdir="$(top_distdir)" distdir="$(distdir)" \
dist-hook
-find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
! -type d ! -perm -400 -exec chmod a+r {} \; -o \
! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
|| chmod -R a+r $(distdir)
dist-gzip: distdir
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
$(am__remove_distdir)
dist-bzip2: distdir
tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
$(am__remove_distdir)
dist-lzma: distdir
tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
$(am__remove_distdir)
dist-tarZ: distdir
tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
$(am__remove_distdir)
dist-shar: distdir
shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
$(am__remove_distdir)
dist-zip: distdir
-rm -f $(distdir).zip
zip -rq $(distdir).zip $(distdir)
$(am__remove_distdir)
dist dist-all: distdir
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
$(am__remove_distdir)
# This target untars the dist file and tries a VPATH configuration. Then
# it guarantees that the distribution is self-contained by making another
# tarfile.
distcheck: dist
case '$(DIST_ARCHIVES)' in \
*.tar.gz*) \
GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\
*.tar.bz2*) \
bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\
*.tar.lzma*) \
unlzma -c $(distdir).tar.lzma | $(am__untar) ;;\
*.tar.Z*) \
uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
*.shar.gz*) \
GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\
*.zip*) \
unzip $(distdir).zip ;;\
esac
chmod -R a-w $(distdir); chmod a+w $(distdir)
mkdir $(distdir)/_build
mkdir $(distdir)/_inst
chmod a-w $(distdir)
dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
&& dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
&& cd $(distdir)/_build \
&& ../configure --srcdir=.. --prefix="$$dc_install_base" \
$(DISTCHECK_CONFIGURE_FLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) dvi \
&& $(MAKE) $(AM_MAKEFLAGS) check \
&& $(MAKE) $(AM_MAKEFLAGS) install \
&& $(MAKE) $(AM_MAKEFLAGS) installcheck \
&& $(MAKE) $(AM_MAKEFLAGS) uninstall \
&& $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
distuninstallcheck \
&& chmod -R a-w "$$dc_install_base" \
&& ({ \
(cd ../.. && umask 077 && mkdir "$$dc_destdir") \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
} || { rm -rf "$$dc_destdir"; exit 1; }) \
&& rm -rf "$$dc_destdir" \
&& $(MAKE) $(AM_MAKEFLAGS) dist \
&& rm -rf $(DIST_ARCHIVES) \
&& $(MAKE) $(AM_MAKEFLAGS) distcleancheck
$(am__remove_distdir)
@(echo "$(distdir) archives ready for distribution: "; \
list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
distuninstallcheck:
@cd $(distuninstallcheck_dir) \
&& test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
|| { echo "ERROR: files left after uninstall:" ; \
if test -n "$(DESTDIR)"; then \
echo " (check DESTDIR support)"; \
fi ; \
$(distuninstallcheck_listfiles) ; \
exit 1; } >&2
distcleancheck: distclean
@if test '$(srcdir)' = . ; then \
echo "ERROR: distcleancheck can only run from a VPATH build" ; \
exit 1 ; \
fi
@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
|| { echo "ERROR: files left in build directory after distclean:" ; \
$(distcleancheck_listfiles) ; \
exit 1; } >&2
check-am: all-am
check: $(BUILT_SOURCES)
$(MAKE) $(AM_MAKEFLAGS) check-recursive
all-am: Makefile config.h
installdirs: installdirs-recursive
installdirs-am:
install: $(BUILT_SOURCES)
$(MAKE) $(AM_MAKEFLAGS) install-recursive
install-exec: install-exec-recursive
install-data: install-data-recursive
uninstall: uninstall-recursive
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-recursive
install-strip:
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
`test -z '$(STRIP)' || \
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
mostlyclean-generic:
clean-generic:
-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
distclean-generic:
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
maintainer-clean-generic:
@echo "This command is intended for maintainers to use"
@echo "it deletes files that may require special tools to rebuild."
-test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
clean: clean-recursive
clean-am: clean-generic clean-libtool mostlyclean-am
distclean: distclean-recursive
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
-rm -f Makefile
distclean-am: clean-am distclean-generic distclean-hdr \
distclean-libtool distclean-tags
dvi: dvi-recursive
dvi-am:
html: html-recursive
info: info-recursive
info-am:
install-data-am:
install-dvi: install-dvi-recursive
install-exec-am:
install-html: install-html-recursive
install-info: install-info-recursive
install-man:
install-pdf: install-pdf-recursive
install-ps: install-ps-recursive
installcheck-am:
maintainer-clean: maintainer-clean-recursive
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
-rm -rf $(top_srcdir)/autom4te.cache
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-recursive
mostlyclean-am: mostlyclean-generic mostlyclean-libtool
pdf: pdf-recursive
pdf-am:
ps: ps-recursive
ps-am:
uninstall-am:
.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
install-strip
.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
all all-am am--refresh check check-am clean clean-generic \
clean-libtool ctags ctags-recursive dist dist-all dist-bzip2 \
dist-gzip dist-hook dist-lzma dist-shar dist-tarZ dist-zip \
distcheck distclean distclean-generic distclean-hdr \
distclean-libtool distclean-tags distcleancheck distdir \
distuninstallcheck dvi dvi-am html html-am info info-am \
install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \
install-pdf install-pdf-am install-ps install-ps-am \
install-strip installcheck installcheck-am installdirs \
installdirs-am maintainer-clean maintainer-clean-generic \
mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
ps ps-am tags tags-recursive uninstall uninstall-am
git_version.stamp:
@if test -f "$(srcdir)/git_version.h"; then \
if test -f "git_version.h"; then :; \
else \
cp "$(srcdir)/git_version.h" "git_version.h"; \
fi; \
fi
$(GIT_VERSION_CMD) -k -s $(top_srcdir) -o git_version.h
@if test -s "$(srcdir)/git_version.h"; then \
if cmp "$(srcdir)/git_version.h" "git_version.h"; then :; \
else \
echo "Error: $(srcdir)/git_version.h and git_version.h differ."; \
echo " You probably want to remove the former."; \
exit 1; \
fi; \
fi
dist-hook: git_version.stamp
if test -f "git_version.h"; then \
$(SED) -e 's|^#undef GIT_IS_DIST.*|#define GIT_IS_DIST 1|' \
"git_version.h" > "$(distdir)/git_version.h"; \
fi
love:
@echo "What is love?"
@echo "Baby don't hurt me."
@echo "Don't hurt me."
@echo "No more."
me:
@if [ "`id -u`" != "0" ]; then echo "What? Make it yourself!" ; else echo "Okay." ; fi
a:
@echo >/dev/null
sandwich:
@echo >/dev/null
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
Do you have the vaguest idea of what it's doing? Neither does ANYBODY else.
Now let's look at the Wolf32X makefile.
Code:
MDLD = $(GENDEV)/m68k/bin/ld
MDAS = $(GENDEV)/m68k/bin/as
SHLD = $(GENDEV)/sh2/bin/ld
SHCC = $(GENDEV)/sh2/bin/gcc
SHAS = $(GENDEV)/sh2/bin/as
RM = rm -f
FLAGS = -m2 -mb -O3 -Wall -g -fomit-frame-pointer -DHAVE_FFBLK -DDOSISM -DWMODE=0 -I./src -I$(GENDEV)/sh2/include
OBJS = \
src/sh2_crt0.o \
src/id_ca.o \
src/id_us.o \
src/id_vh.o \
src/misc.o \
src/objs.o \
src/vi_comm.o \
src/vi_32x.o \
src/wl_act1.o \
src/wl_act2.o \
src/wl_act3.o \
src/wl_agent.o \
src/wl_debug.o \
src/sd_comm.o \
src/sd_32x.o \
src/adlibtables_wsw.o \
src/wl_draw.o \
src/wl_game.o \
src/wl_inter.o \
src/wl_main.o \
src/wl_menu.o \
src/wl_play.o \
src/wl_state.o \
src/wl_text.o \
src/w3dsw_data.o \
src/automap.o \
src/debug_32x.o \
src/debug_font.o
all: m68k_crt0.bin m68k_crt1.bin wolf32x.bin
wolf32x.bin: $(OBJS)
$(SHLD) -T $(GENDEV)/sh2/lib/32x.ld -relax -small -e _start --oformat binary -o wolf32x.bin $(OBJS) $(GENDEV)/sh2/lib/libm.a $(GENDEV)/sh2/lib/libc.a $(GENDEV)/sh2/lib/libgcc.a $(GENDEV)/sh2/lib/libgcc-Os-4-200.a
m68k_crt0.bin: src/m68k_crt0_wsw.s
$(MDAS) -m68000 --register-prefix-optional -o src/m68k_crt0_wsw.o src/m68k_crt0_wsw.s
$(MDLD) -T $(GENDEV)/m68k/lib/md.ld --oformat binary -o src/m68k_crt0.bin src/m68k_crt0_wsw.o
m68k_crt1.bin: src/m68k_crt1.s
$(MDAS) -m68000 --register-prefix-optional -o src/m68k_crt1.o src/m68k_crt1.s
$(MDLD) -T $(GENDEV)/m68k/lib/md.ld --oformat binary -o src/m68k_crt1.bin src/m68k_crt1.o
%.o: %.c
$(SHCC) $(FLAGS) -c $< -o $@
%.o: %.s
$(SHAS) --small -o $@ $<
clean:
$(RM) -f src/*.o src/*.out src/*.bin *.bin
Let's look at it bit by bit.
Code:
MDLD = $(GENDEV)/m68k/bin/ld
MDAS = $(GENDEV)/m68k/bin/as
SHLD = $(GENDEV)/sh2/bin/ld
SHCC = $(GENDEV)/sh2/bin/gcc
SHAS = $(GENDEV)/sh2/bin/as
RM = rm -f
We start by setting some variables for the programs we will use to build our game. Any time you see $( ), whatever is inside is a variable, be it one defined in the makefile, or a system environment variable passed in from the shell. To call the SH2 compiler, we just use $(SHCC) and make will replace that with $(GENDEV)/sh2/bin/gcc.
After that, most folks set variables for commonly used options for the programs.
Code:
FLAGS = -m2 -mb -O3 -Wall -g -fomit-frame-pointer -DHAVE_FFBLK -DDOSISM -DWMODE=0 -I./src -I$(GENDEV)/sh2/include
OBJS = \
src/sh2_crt0.o \
src/id_ca.o \
src/id_us.o \
src/id_vh.o \
src/misc.o \
src/objs.o \
src/vi_comm.o \
src/vi_32x.o \
src/wl_act1.o \
src/wl_act2.o \
src/wl_act3.o \
src/wl_agent.o \
src/wl_debug.o \
src/sd_comm.o \
src/sd_32x.o \
src/adlibtables_wsw.o \
src/wl_draw.o \
src/wl_game.o \
src/wl_inter.o \
src/wl_main.o \
src/wl_menu.o \
src/wl_play.o \
src/wl_state.o \
src/wl_text.o \
src/w3dsw_data.o \
src/automap.o \
src/debug_32x.o \
src/debug_font.o
In the above, FLAGS is used by gcc when compiling. OBJS is a list of object files we need to build the game. Using $(OBJS) is much easier than using the list over and over. The list can have multiple items per line. If you need more line, the "\" is used to tell make the next line continues the current line. VERY IMPORTANT! The indent used at the start of a line in a makefile MUST be a TAB!! Any other form of indentation (spaces) will cause the makefile to fail. That's why you should post makefiles in code tags for people unless you are trying to do a tutorial like this one; nearly all boards replace the tabs with spaces, which causes the makefile to fail if you try to use it directly.
Now let's get to the heart of the makefile - the rules.
Code:
all: m68k_crt0.bin m68k_crt1.bin wolf32x.bin
wolf32x.bin: $(OBJS)
$(SHLD) -T $(GENDEV)/sh2/lib/32x.ld -relax -small -e _start --oformat binary -o wolf32x.bin $(OBJS) $(GENDEV)/sh2/lib/libm.a $(GENDEV)/sh2/lib/libc.a $(GENDEV)/sh2/lib/libgcc.a $(GENDEV)/sh2/lib/libgcc-Os-4-200.a
m68k_crt0.bin: src/m68k_crt0_wsw.s
$(MDAS) -m68000 --register-prefix-optional -o src/m68k_crt0_wsw.o src/m68k_crt0_wsw.s
$(MDLD) -T $(GENDEV)/m68k/lib/md.ld --oformat binary -o src/m68k_crt0.bin src/m68k_crt0_wsw.o
m68k_crt1.bin: src/m68k_crt1.s
$(MDAS) -m68000 --register-prefix-optional -o src/m68k_crt1.o src/m68k_crt1.s
$(MDLD) -T $(GENDEV)/m68k/lib/md.ld --oformat binary -o src/m68k_crt1.bin src/m68k_crt1.o
%.o: %.c
$(SHCC) $(FLAGS) -c $< -o $@
%.o: %.s
$(SHAS) --small -o $@ $<
clean:
$(RM) -f src/*.o src/*.out src/*.bin *.bin
Each rule has this format:
Code:
target : dependencies
<tab>command1
<tab>command2
etc
The target is what make is currently trying to build; the dependencies tell make the list of things that need to be built before the target can be built. If the dependencies don't exist, make goes through the list looking for a rule to make the dependencies. Once the dependencies exist, make then uses the commands to build the target.
Code:
all: m68k_crt0.bin m68k_crt1.bin wolf32x.bin
The first target is all - when you just type "make", this will be the first target. Make sees that in order to make "all", it first needs to make m68k_Crt0.bin, m68k_crt1.bin, and wolf32x.bin. So it now looks for rules to make each of those.
Code:
m68k_crt0.bin: src/m68k_crt0_wsw.s
$(MDAS) -m68000 --register-prefix-optional -o src/m68k_crt0_wsw.o src/m68k_crt0_wsw.s
$(MDLD) -T $(GENDEV)/m68k/lib/md.ld --oformat binary -o src/m68k_crt0.bin src/m68k_crt0_wsw.o
There is the first. It says to make m68k_crt0.bin, it first needs src/m68k_crt0.s, which is the source file which always exists. So it now uses the commands to make the file. The first command calls the MD assembler to make the object file, and the second command calls the MD linker to convert the object file to binary. We now have the m68k_crt0.bin file.
The m68k_crt1.bin is done in a similar manner, so let's look at the third.
Code:
wolf32x.bin: $(OBJS)
$(SHLD) -T $(GENDEV)/sh2/lib/32x.ld -relax -small -e _start --oformat binary -o wolf32x.bin $(OBJS) $(GENDEV)/sh2/lib/libm.a $(GENDEV)/sh2/lib/libc.a $(GENDEV)/sh2/lib/libgcc.a $(GENDEV)/sh2/lib/libgcc-Os-4-200.a
This tells make that in order to build wolf32x.bin, we first need $(OBJS). So the entire list of object files needs to exist before we can build wolf32x.bin. So make now looks for a rule for each object in the list of objects. However, we don't have any rules for them! That's where generic rules come in; while we don't have a specific rule for src/wl_play.o, we DO have a rule for %.o.
Code:
%.o: %.c
$(SHCC) $(FLAGS) -c $< -o $@
%.o: %.s
$(SHAS) --small -o $@ $<
These rules use pattern matching to allow use to make one rule for a bunch of files. Any time you can do multiple files with a generic rule, do so. We COULD make a rule for every single object in OBJS, but that would be silly! KISS!!!
The first generic rule above says that for any file ending in .o that is not handled by a specific rule, we need a dependency of the same file with an ending of .c. Is there a src/wl_play.c file? You bet your sweet bippy! So, we have met the rule, so we do the commands to build the object file. In this case, we call the SH2 compiler with this line.
Code:
$(SHCC) $(FLAGS) -c $< -o $@
We saw the FLAGS earlier. What we are concerned with here are $< and $@. These are special symbols that stand for the dependency and the target file names, respectively. So $< is src/wl_play.c, and $@ is src/wl_play.o in our example. You can see how the pattern matching in generic rules work.
The second generic rule above similarly calls the SH2 assembler for assembly files as opposed to the compiler for c files. Between these two, we build all the object files in the list.
So now we have all the object files needed to build wolf32x.bin. Look at the command.
Code:
$(SHLD) -T $(GENDEV)/sh2/lib/32x.ld -relax -small -e _start --oformat binary -o wolf32x.bin $(OBJS) $(GENDEV)/sh2/lib/libm.a $(GENDEV)/sh2/lib/libc.a $(GENDEV)/sh2/lib/libgcc.a $(GENDEV)/sh2/lib/libgcc-Os-4-200.a
That calls the SH2 linker, which makes the binary file from the list of object files as well as a few SH2 libraries that come with the compiler.
So there you have it! You have made the game using a few variables and rules. But what about that last line?
Code:
clean:
$(RM) -f src/*.o src/*.out src/*.bin *.bin
That tells make that when you run "make clean" as the command in the shell, that there are no dependencies for the target "clean", so just run the command, which removes (deletes) the list of files. The wildcards mean all files in src ending in .o or .out or .bin will be deleted, as well as all files in the current directory that end in .bin. This allows us to delete all the various intermediate files, leaving just the source files.
Lesson 2 - Using assembly in your project
Let me start by saying inline assembly is a joke - don't bother. If you're going to use some assembly in your project, do it right and make a separate assembly file. It's actually easier than trying to remember all those ridiculous formats inline assembly require. It's also much more powerful.
Let's start with 68000 assembly; you may use it for Genesis or 32X projects. The standard compiler in the GNU Compiler Collection is AS; it is a part of binutils. You use "standard" Motorola syntax; the main differences from more common assemblers is the directives. Let's look at a few.
Code:
.text
This sets the current section to the code section. With the linker script I supply with my Genesis toolchain, this puts any code or data following this directive into the rom.
Code:
.data
This sets the current section to the data section. This puts any code or data following the directive into ram... with the proper startup code. In actuality, the code and data after this directive is still in the rom, but it has all references set so that when the startup code copies this code and data into ram, it will be referenced properly. If you look at the crt0.s file in my example, you'll see the loop that copies the code and data in the data section to ram.
Let's briefly look at a few handy directives you'll commonly use.
Code:
.byte 0x00,0x01,0x02,0x03
.word 0x0001,0x0203
.long 0x00010203
These directives make tables of bytes, words, and longs, respectively. You can have one value (as in the line with .long), or multiple values (as in the other two). Values are stored in big-endian format since that is the format the 68000 uses. It does NOT handle alignment of the data, so be careful with words and longs, which must be on at least a word boundary to avoid an exception. Which brings us to our next directive.
Code:
.align 2
This directive increments the current address to the next boundary specified. For the 68000 version of AS, the value of the align directive is in bytes. 2 means align to two bytes, 4 means align to 4 bytes, etc. When in doubt, use an align directive. Be liberal - it doesn't hurt and is needed before words, longs, and instructions to avoid an exception. Only byte data can be on a byte boundary. Most hacks or homebrew that work in emulators, but fail on real hardware, usually have data or code on a byte boundary somewhere because they forgot to use the align directive.
Here are two more handy directives.
Code:
.ascii "SEGA MD Example "
.asciz "SEGA MD Example "
These turn the string into a byte table of the ASCII values of the string. The difference is the asciz directive always adds a byte of 0x00 to the end of the string. That would be necessary for standard C strings, which MUST be null terminated.
Now let's look at labels. AS for the 68000 allows you to use labels exactly the same as in C. There are no special characters needed. The labels are case sensitive, so watch the capitalization! Any label you use in assembly that isn't defined in the file is assumed to be defined in another file, so there is no extern directive like you see in C. To define a label as being seen externally requires the global directive.
Code:
.global gTicks
gTicks:
.long 0
The global directive tells AS that the specified label may be seen by other files. Not using the global directive makes any other defined labels local, so there is no static directive like you see in C. Labels may refer to code or data. It's up to the programmer to use what the labels refer to properly. There is no type safety - you can easily use a C array of longs as bytes in an assembly file. You could use C code as data, or vice versa.
So now how do we use code in assembly that properly interacts with C? This is what is referred to as the Application Binary Interface, or ABI. The 68000 ABI says that when you call an assembly language function, you must save registers D2 to D7, and A2 to A6. D0, D1, A0, and A1 may be modified freely without saving or restoring them. If you can, only use those registers in your assembly for better speed. When you have to use more registers, you must push them on the stack before you do so, and pop them off the stack before returning. When the code is entered, the stack points to a long that is the return address. The long at the stack pointer + 4 is the first argument passed to the function, if any. The long at the stack pointer + 8 is the second argument passed to the function, if any. Additional arguments, if any, are accessed on the stack in a similar manner, being at + 12, + 16, + 20, etc. First, in this case, means the left-most argument, with other arguments going right accessed at higher addresses. All arguments are pushed as long values regardless of size! If you pass a byte from C to assembly, it's pushed on the stack as a long. The return value, if any, should be left in D0.
Let's look at a simple example.
Code:
.align 2
| short set_sr(short new_sr);
| set SR, return previous SR
| entry: arg = SR value
| exit: d0 = previous SR value
.global set_sr
set_sr:
moveq #0,d0
move.w sr,d0
move.l 4(sp),d1
move.w d1,sr
rts
The "|" character indicates the start of a comment. You can also use /* */ for comments. Notice how we put an align directive before the function. Do this if your code ever follows byte data to avoid exceptions. Notice that since we only use D0 and D1, we don't need to save or restore any registers. Notice that the argument (short new_sr) is accessed at the stack pointer + 4. Notice how we put the old version of the status register in D0 as the return value.
Now let's look at a more complex example.
Code:
| short get_pad(short pad);
| return buttons for selected pad
| entry: arg = pad index (0 or 1)
| exit: d0 = pad value (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)
.global get_pad
get_pad:
move.l d2,-(sp)
move.l 8(sp),d0 /* first arg is pad number */
cmpi.w #1,d0
bhi no_pad
add.w d0,d0
addi.l #0xA10003,d0 /* pad control register */
movea.l d0,a0
bsr.b get_input /* - 0 s a 0 0 d u - 1 c b r l d u */
move.w d0,d1
andi.w #0x0C00,d0
bne.b no_pad
bsr.b get_input /* - 0 s a 0 0 d u - 1 c b r l d u */
bsr.b get_input /* - 0 s a 0 0 0 0 - 1 c b m x y z */
move.w d0,d2
bsr.b get_input /* - 0 s a 1 1 1 1 - 1 c b r l d u */
andi.w #0x0F00,d0 /* 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 */
cmpi.w #0x0F00,d0
beq.b common /* six button pad */
move.w #0x010F,d2 /* three button pad */
common:
lsl.b #4,d2 /* - 0 s a 0 0 0 0 m x y z 0 0 0 0 */
lsl.w #4,d2 /* 0 0 0 0 m x y z 0 0 0 0 0 0 0 0 */
andi.w #0x303F,d1 /* 0 0 s a 0 0 0 0 0 0 c b r l d u */
move.b d1,d2 /* 0 0 0 0 m x y z 0 0 c b r l d u */
lsr.w #6,d1 /* 0 0 0 0 0 0 0 0 s a 0 0 0 0 0 0 */
or.w d1,d2 /* 0 0 0 0 m x y z s a c b r l d u */
eori.w #0x1FFF,d2 /* 0 0 0 1 M X Y Z S A C B R L D U */
move.w d2,d0
move.l (sp)+,d2
rts
| 3-button/6-button pad not found
no_pad:
move.w #0xF000,d0 /* SEGA_CTRL_NONE */
move.l (sp)+,d2
rts
| read single phase from controller
get_input:
move.b #0x00,(a0)
nop
nop
move.b (a0),d0
move.b #0x40,(a0)
lsl.w #8,d0
move.b (a0),d0
rts
Notice how we use D2, so we must save it at the start, and restore it before we return. Because we pushed D2 onto the stack, any arguments are now one long further back on the stack; that is why the argument is now accessed at the stack pointer + 8 instead of + 4. Notice that we use both kinds of comments this function. Be sure to comment your code well enough that you can figure out what you are doing when you look at your code years later. Again, the return value is left in D0.
We are not going to cover more in this post as this should be enough to get you going. If you wish to know more of the directives, especially macros, consult the binutils documentation for AS.
Now let's look at AS for the SH2. It's very nearly the same as AS for the 68000 as all platforms use most of the same directives. So we mainly need to concern ourselves with the differences. The data directives are the same, but there is a difference as far as alignment goes - words need to be on word boundaries, like the 68000, but longs need to be on long boundaries; the 68000 can take longs on word or long boundaries - the SH2 cannot. Which brings the first difference in the directives - the value used with the align directive is the power of two used to set the current address. For example, the following are the same.
68000
Code:
.align 2
.align 4
.align 16
SH2
Code:
.align 1
.align 2
.align 4
SH2 code must be word aligned, but is slightly faster if aligned to a 16 byte boundary (.align 4).
Our next difference is in the labels - the SH2 assembler needs a "_" (underscore) character in front of labels if they are defined in C code, or if you wish to access the label from C. For example.
Code:
! Cache clear line function
! On entry: r4 = ptr - should be 16 byte aligned
.align 4
.global _CacheClearLine
_CacheClearLine:
mov.l _cache_flush,r0
or r0,r4
mov #0,r0
mov.l r0,@r4
rts
nop
This function would be called from C like this.
Code:
CacheClearLine(&data_variable);
Some more differences immediately are apparent. You use "!" for a comment instead of "|"; you can still use /* */ for comments. The next difference is in how arguments are passed in; the first argument is passed in R4, the second in R5, the third in R6, and the fourth in R7. Any other arguments are pushed on the stack. Use no more than four arguments for best speed. On the SH2, you must save R8 to R14, and the PR register. For example.
Code:
! Entry: r4 = sound buffer to fill
.global _fill_buffer
_fill_buffer:
sts.l pr,@-r15 /* save return address */
mov.l r8,@-r15
mov.l r9,@-r15
mov.l r10,@-r15
mov.l r11,@-r15
mov.l r12,@-r15
mov.l r13,@-r15
mov.l r14,@-r15
mov r4,r14
! lots of code left out here as it isn't needed for the example
mov.l @r15+,r14
mov.l @r15+,r13
mov.l @r15+,r12
mov.l @r15+,r11
mov.l @r15+,r10
mov.l @r15+,r9
mov.l @r15+,r8
lds.l @r15+,pr /* restore return address */
rts
nop
A practical example of an assembly routine is my code to stretch the frame buffer contents - it takes a half a line of data and doubles it to fill an entire line.
Code:
! void ScreenStretch(int src, int width, int height, int interp);
! On entry: r4 = src pointer, r5 = width, r6 = height, r7 = interpolate
.align 4
.global _ScreenStretch
_ScreenStretch:
cmp/pl r7
bt ss_interp
! stretch screen without interpolation
0:
mov r5,r3
shll r3
mov r3,r2
shll r2
add r4,r3
add r4,r2
1:
add #-2,r3
mov.w @r3,r0
extu.w r0,r1
shll16 r0
or r1,r0
mov.l r0,@-r2
cmp/eq r3,r4
bf 1b
/* next line */
mov.w ss_pitch,r0
dt r6
bf/s 0b
add r0,r4
rts
nop
ss_interp:
! stretch screen with interpolation
0:
mov r5,r3
shll r3
mov r3,r2
shll r2
add r4,r3
add r4,r2
mov #0,r7
1:
add #-2,r3
mov.w @r3,r0
mov.w ss_mask,r1
and r0,r1 /* masked curr pixel */
shll16 r0
add r1,r7 /* add to masked prev pixel */
shlr r7 /* blended pixel */
or r7,r0 /* curr pixel << 16 | blended pixel */
mov r1,r7 /* masked prev pixel = masked curr pixel */
mov.l r0,@-r2
cmp/eq r3,r4
bf 1b
/* next line */
mov.w ss_pitch,r0
dt r6
bf/s 0b
add r0,r4
rts
nop
ss_mask:
.word 0x7BDE
ss_pitch:
.word 640
As far as the sections go, with my 32X toolchain, the text section still goes to the rom, while data goes to the SDRAM.
Okay, that's enough for now. I encourage people to look at the assembly files in the Tic Tac Toe examples, as well as the Yeti3D example.