Segments and the spec file

The spec file is a part of the compilation process, much like the Makefile. In fact, it is summoned during the compilation process to define several major elements of the ROM, including:

  • Segments
    • Code Segments
    • Audio segements
    • More segments
  • Waves

The boot function is the function that runs upon switching on the power.
Once they are defined, they can be called during gameplay should the need arise.


The official releases for the Nintendo 64 ranged from 4-64MB, so you can tell that this was a major component to making a large game possible. To give a bit of a comparison of two extremes, Dr Mario 64 (which was a fairly simple game for the console) was 4MB, while Conker’s Bad Fur Day and Resident Evil 2 stand at a massive 64MB because of all the graphic detail, voices, music and full-motion video. Of course, large-capacity cartridges were expensive, so there was pressure for developers to keep sizes as small as possible.

The reason why the spec file exists is that the N64 has 4MB of RDRAM. This means that the maximum amount of code that can be loaded from the cartridge is 4MB; so if the game’s ROM is larger than that, it needs to be loaded in segments through the PI.

A picture is worth a thousand words, so here is how it would work for a game:

spec file diagram

This shows how a 16MB game ROM can be accessed by only having 4MB of RAM available.

Spec structure

The spec file is written in a C-esque format, though most of the C code is written in an external file and then included by using something like this:

#include "header.h"

It is important to include nusys.h if you want to use any NuSystem functionality, which will help simplify the boot portion of the program. After that, you will have a whole bunch of segment definitions. The basic structure of a segment is like this:

	name "segmentName"
	flags FLAGS
	include "file.ext"

There are many more settings for a segment besides this, which we will get to later.

After all the segments have been defined, they are joined into a wave. The wave creates an ELF object file. This file is used for debugging, so it is only optional. It has a syntax that looks like this:

	name	"waveName"
	include	"segmentName"
	include	"segmentName2"
	include	"segmentName3"

It really is that simple. All you need to do is include all the settings for each segment between the begin and end markers. The syntax overall is fairly simple. You can even add definitions, conditionals and comments like you would in regular C code if you need to.

A detailed view on segments

The basic structure of the spec file is quite simple, where it gets a bit more complicated is defining each segment correctly.

Attributes of a segment

These are all of the properties that can be assigned to a segment. You’re not going to need all of these in every segment, but it’s good to know what they all do:

	name	"segmentName"
	entry 	bootFunc
        stack   int
	address int
	number 	int
	after 	int
	include "segment.o"
	maxsize int
	align 	int

name is the name or label of the segment. It is used in the C code you write to call upon a particular segment later on. Required.

flags are the settings for that segment. There are three options. BOOT designates the boot segment, as in this the segment that will run when you start up the console; it must be the first segment in the list. OBJECT denotes that it includes object (.o) files created by the Makefile. RAW is all other file types, mostly used for segments that refer to audio files.

entry is the boot function. This function must be included in the boot segment, and is the first function to run in the entire program. If using NuSystem, this is set to nuBoot and the boot function in the code is set to void mainproc(void).

stack is the stack address of the boot code. This is typically set to the stack size (0x2000) plus an array of 128 64-bit integers. In NuSystem, it is defined by the variable NU_SPEC_BOOT_STACK.

address, number and after are the memory location for OBJECT-type segments. address-type is meant for the CPU, number-type are for the RSP and after-type are meant to place segments right after each other to reduce fragmentation. From what I’ve seen, address-type are typically 32-bit integers, starting with 0x80000000. number-type tend to be small numbers, typically values within 1-4. I have no idea how the exact value for addresses and number are assigned. Perhaps the debugger prints them out, but they seem to be static.

include is fairly simple – it makes the segment include a file. This can be used by OBJECT-type segments to include C code and some of the N64’s microcodes as well; and RAW-type segments typically include only one file of another type. All segments must have at least one of these.

maxsize can be used to specify the maximum size of a segment in bytes.

align can change the alignment of memory. It is set by default to 16 bytes (128 bits).

Boot segment

The boot segment is the first segment to be run in the game program. If you look at the animation at the top of the guide, you’ll see that there is one segment that is always active. This is the general code segment, which often includes the boot function.

The boot segment is the first segment defined in the spec file, and has some unique properties when compared to other ones:

  • Like all segments, it has a name.
  • The flags are always BOOT OBJECT.
  • It always has an entry line, denoting the boot function.
  • Always has a stack definition.
  • It uses include for one object file based on the program’s code; the one with the boot function.
  • It can include multiple lines of microcode as well, depending on which ones are in use.

This example from simple shows how  boot segment looks like:

	name "code"
	entry boot
	stack bootStack + STACKSIZEBYTES
	include "codesegment.o"
	include "$(ROOT)/usr/lib/PR/rspboot.o"
	include "$(ROOT)/usr/lib/PR/gspFast3D.o"
	include "$(ROOT)/usr/lib/PR/gspFast3D.dram.o"
	include "$(ROOT)/usr/lib/PR/aspMain.o"

And here is a similar one from nu0, this time showing how it is set up using NuSystem:

	name	"code"
	entry 	nuBoot
        stack   NU_SPEC_BOOT_STACK
	include "codesegment.o"
	include "$(ROOT)/usr/lib/PR/rspboot.o"
	include "$(ROOT)/usr/lib/PR/gspF3DEX2.fifo.o"
	include "$(ROOT)/usr/lib/PR/gspL3DEX2.fifo.o"
	include "$(ROOT)/usr/lib/PR/gspF3DEX2.Rej.fifo.o"
        include "$(ROOT)/usr/lib/PR/gspF3DEX2.NoN.fifo.o"
        include "$(ROOT)/usr/lib/PR/gspF3DLX2.Rej.fifo.o"
	include "$(ROOT)/usr/lib/PR/gspS2DEX2.fifo.o"

Other segments

There are two other types of segments that can be defined, and they are much simpler to understand.

First off, you have OBJECT segments that are not part of the original boot segment. These can be used for a few things, for example:

  • Storing large amounts of models/textures that aren’t in repeat use. For example, one level of a game would reuse enemy texture and models that aren’t used in the rest of the game.
  • Logic segments for AI and other functions that are used in certain parts of the game, but not others. Eg, a boss’s AI, or the physics engine for swimming that is used in water levels, but not in dry ones.
  • When you want to exclude the boot function from the engine, so as to save some space in RAM.
	name "gfx"
	flags OBJECT
	after code
	include "gfx.o"

The above code creates an OBJECT segment called gfx based on a file gfx.o, and places it right after the segment called code. Simple, really.

The last type of segment is one that contains non-code information, mostly used for music since graphics are stored in the code segments. Here is what it would look like:

	name "seq"
	flags RAW
	include "sequence.seq"

This creates a RAW segment named seq, which contains the music sequence file sequence.seq.

All that’s left to do now is to learn how to load and clear segments from the RDRAM. This is quite a lot, so I will leave it for another page in the future.


Subscribe to the mailing list

Follow N64 Squid

  • RSS Feed
  • Tumblr

Popular posts