Little doc for HuC
the PC Engine C compiler

previous home : utopia.consolemul.com
home : www.zeograd.com
www.magickit.com/mkit

In this documentation, you'll find how to use this compiler, how to interface it with your own asm routine and such. If you want to see anything added there or just want some explanation, tell me. I'll try my best to make it clearer.

What's new ?

Known bugs

C/asm interface

You'll find here some trick to understand how this compiler works. Before trying to write your own asm routine, read that first. Even if you don't intend to make asm, I think you should read it at least once, knowledge can't harm.

Whatever you write in a asm block (i.e. beginning by #asm and ended by #endasm) is directly passed to the assembler, without any modification from the C compiler. You can write this way your own assembler lib function (when speed is needed for example) but it's harder to write and support than C code, of course.

You must be aware that C function names are limited to 24 chars and when passed to assembler are preceded with a '_' char. That's why you can't have 2 functions with the same 24 first characters and need to add a _ when calling a function directly from an assembly block. (The part concerning the length of identifier is not as important as when this document was first wrote, it was then limited to 8)

Argument passage is done trough a special stack, not the internal stack of the pc engine (that is 256 bytes long only and must be saved whenever possible) but a stack situated in RAM, at the end of the RAM to be more precise. The head of this stack is pointed by the variable __stack ( 2 '_' characters before ).

Since the tail is fixed at the very end of the RAM ($4000), the head 'goes down' when you add an item in it. Here's the pseudo code for adding an item on it ("pushing" a value).

__stack -= sizeof(var);
*__ stack = var;

This is exactly what is done in assembler before calling a function, to set the value of the various arguments.

outdated : If you want to write your own assembler function with parameters, you must restore the value of __stack because it's the calling program that must 'clean' the stack and not the called sub routine (contrary to the pascal convention) . This is due to the C calling convention which allows variable number of arg for subroutines (which is not supported by HuC and is likely to be never supported).

Now, David Michel decided (for optimisation purpose) to use anew the pascal convention for passing arguments and passing the first argument in the A:X registers instead of using the stack. This means that the stack must be restored in the called function, no more in the calling one.

Once in the subroutine, argument are then situated 'above' the stack (i.e. *__stack, *(__stack+1), and so on.. ). As for the order, rightmost C declared arguments are found on the 'bottom' of the stack. A little example to understand better...

                                             +------------+
dummy(val,offset,x, reg_arg) __stack -> |//// x /////| char val; +------------+ int offset; __stack + 1 -> |\\\\\\\\\\\\|
char x; +-- offset --+ int reg_arg; __stack + 2 -> |\\\\\\\\\\\\|
+------------+
__stack + 3 -> |/// val ////|
+------------+
| |

You may even consider the following pseudo code is executed to set the value of the argument using the stack :

  reg_arg = value of reg X + 256 * value of reg A;
  x = *__stack;
  offset = (*(__stack + 1) + 256 * (*(__stack + 2))
  val = *(__stack + 3)

Sprite handling

Sprites are handled trough a table of 64 items, one for each sprite, each entry being 8 bytes long.

We are reusing the method of the magic kit, that's to say that we keep a local copy of this table in RAM on which we work and then we copy this local table to the real one (located in the video ram) each frame. This method allows faster manipulation (RAM is easier to access than video memory).

This local SATB (Sprite Attribute TaBle) is initialized by satb_init(), future call to this function will just clear it.

You can change every attribute of each sprite, individually. There are 4 attributes (using 16 bits each, which make this 64 sprites long table using 512 bytes). 2 are for the position (x,y), one for the pattern and the last one contains various flags...

When you want to change anything in a sprite, the method is the following : first, you must tell the system what is the sprite you want to deal with spr_set(no) where no is the number of the sprite (in 0-63).

Once done, you have a few functions you can use to modify the sprite you've chosen. spr_x(x_coord) and spr_y(y_coord) respectively change the position of the sprite on screen.

spr_pri(priority) allows to set the priority of the sprite, that's to say behind or in front of tiles. Priority of 0 makes it behind the tiles map and 1 draw them front of them.

spr_pal(pal_number) changes the palette for this sprite. There are 16 palettes of 16 colors for sprites. You can change the palette using set_sprpal(palette), supposing you have a palette defined somewhere else with the incpal or defpal statement we'll also talk about.

Last function to control flags is the more generic spr_ctrl(ctrl_mask, ctrl_value) function. It controls the mirror flags (you can flip a sprite, either horizontally or vertically) and size flags (sprites can be 16x16, 32x16, 16x32, 32x32, 16x64 or 32x64). To use this, you must provide as the mask, the one corresponding to the flag you want to change or both (combining them with a logical 'or' i.e. | ) and on value, well, the value. There's a bunch of #define statements in the file huc.h which make it easy to use. A few examples, to draw the 13th sprite horizontally flipped, write :

spr_set(13);
spr_ctrl(FLIP_MAS, FLIP_X);

As for drawing a sprite flipped both horizontally and vertically, while setting the size to 32x64, simply put :

spr_ctrl(FLIP_MAS | SIZE_MAS, FLIP_X | FLIP_Y | SZ_32x64);

The last function to handle sprite is the spr_pattern(pattern_address) function. It's used to change the pattern of a sprite, i.e. the real shape of it. This 16 bits address is the place in the video memory where resides the pattern. The address is in word, not in bytes, which allows to access the whole 0x20000 bytes of video. You must not forget to load this pattern from the rom with another asm piece of code. First, you must define the pattern in a data bank including pieces of pcx files (or even creating yours with hexa numbers), then loading these datas in the video memory using the load_sprite statement. The load_sprite function takes the label of the data first, then the address in the video ram where it should be situated, THIS IS THE VERY SAME PARAMETER THAT SHOULD BE USED IN THE SPR_PATTERN FUNCTION, and finally the number of sprite that should be loaded. This number is in 32x64 big sprites. So loading 8 16x16 sprites requires only the value 1 here...

Finally, you shouldn't forget to update the true satb with the local one using the function satb_update(), which should be also accompanied by a vsync(1) call in order to let the user see the changes.

PSEUDO INSTRUCTIONS

This part describes all the instructions which are not real C statements. They are HuC built in instructions and are processed at the compilation time, they are not call to library function for example (although they often produce calls to library functions). Since they're specific to HuC, they deal with all which is pc engine specific such as banking, sprites and tiles...

Outdated :

BRIEF SUMMARY OF THE PC ENGINE HARDWARE

The goal of this documentation is not to explain how does the pc engine works but since it's quite near of the machine architecture, I fell obliged to talk a bit about it to fully understand the utility of HuC. For a complete pc engine documentation, don't worry, we're also working on it. Until it's complete, you would like to check the one done my J.C. Restemeyer and on which ours will be based.

Concerning the memory management, you must be aware that the pc engine owns a very little addressable memory, 64 kB. In order to access the whole 1MB which can be present on a HuCard (plus some more special part of memory), there're a system of banking which allow you to access some part of the HuCard at a given moment. The 64 kb is divided in 8 pieces of 8 kb each. Each one of these piece is controlled by a special register indicating the "mapping" that's to say the bank we are accessing trough the memory range controlled by this register. For example, let's say you're setting MMR (Memory Management Unit I think) number 4 to the value 30. Since MMR4 controls 0x8000-0xA000 memory range, you'll be able to read the 15th byte of bank 30 at 0x800F and the last byte of bank 30 at position 0x9FFF.

Normal banks are numbered from 0-127, above these, you can find special banks. The bank numbered 0xFF is the Input/Output bank. When you read or write in memory where this bank is mapped, you won't read or write in some memory but rather access some hardware ports. For example, writing at position 0 of this bank will change the current video register (forget about its use, it's an example), sound stuff is at offset 0x800, cdrom stuff at 0x1800 and such... It's the way the program can communicate with the hardware. Bank 0xF8 is the ram for the coregraphx (there may be more for supergraphx). Whenever available the backup RAM is located at bank 0xF7.

There's also something I didn't explain. In order to make it more easy to handle all of this, we generally "schedule" a bank to be placed at a certain place (such as 0x4000 or 0xC000 and such). We way, we can use absolute jumps and such, we are not limited to relative addressing. Whatever, you must be aware that each bank is prepared for a certain position, not forgetting that some MMR are reserved. Indeed, it's not compulsory but most of roms, not to say absolutely all, uses a certain scheme. MMR0 (controlling 0x0000-0x2000) is mapped to the I/O bank for controlling the hardware. MMR1 (controlling 0x2000-0x4000) is mapped to the RAM bank. MMR7 (the last one) is mapped to bank 0. This one should always be mapped since interruption vectors are taken from the very end of the RAM (and thus in this bank), this bank generally contain general purpose functions (BIOS) and it's the only one which is mapped by default when booting anew. So code at the beginning of this bank will be the 'boot' code.

Outdated : As for easing creation of ROM from HuC, we've also decided to use 0x4000-0x6000 for data such as sprites, tiles. If you've read everything, you'll notice we still have 0x6000-0xDFFF for our purposes. Code can be placed in any of these and when telling HuC to use a new bank, it will try to feed all this bank so that you won't have overlapping function calls. About function calls, we're still working on it but we already have a working solution.

All of this to show you that a good spreading of the code will make things faster and smaller...

Thanks to the new magic kit used, the assembler is able to map automaticaly data and code. This means that you don't use to specify any "newbank" statement since the assembler will spread code and data in various banks and will take care of mapping stuff before using it. As far as I remember the code only uses one bank and another one will only be used to contain a wraper for each function that maps correctly a bank before calling a function. Two banks are used for startup code (hucard and cd one). One will contain data (such as sprites, tiles and such) and another one for constant data (as the string constant in C programs). Remain the two classical first MMR filled with the hardware access bank and the RAM one.

As you can see, everything is used now and transparently for us, C coders :)