Assembler Application Note
Application Note to compile and run assembler programs on the WII
We will get GCC to generate an example assembler file for us. This is how we will proceed:
- Setup the template example in devkitpro\examples\wii\template to compile and run it successfully on the Wii
- Put the initialization code into a separate file to keep the program as short as possible
- Modify the makefile to avoid that the assembler file which gcc generates gets deleted and remove debugging code and optimization
- Make a small change in the resulting assembler file
- Compile the assembler file with as.exe
- Link the object file made with as.exe by calling gcc
- Use wiiload to run it on the Wii
Setup the template example
To compile and run the template example is described here: Windows guide
Put the initialization code in a separate file
To put the initialization code in a separate file cut out this part of the code in template.c and put it into a separate file which you can call e.g. "function.c".
#include <stdio.h>
#include <stdlib.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
void initwii(void) {
static void *xfb = NULL;
static GXRModeObj *rmode = NULL;
// Initialise the video system
VIDEO_Init();
// This function initialises the attached controllers
WPAD_Init();
// Obtain the preferred video mode from the system
// This will correspond to the settings in the Wii menu
rmode = VIDEO_GetPreferredMode(NULL);
// Allocate memory for the display in the uncached region
xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
// Initialise the console, required for printf
console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
// Set up the video registers with the chosen mode
VIDEO_Configure(rmode);
// Tell the video hardware where our display memory is
VIDEO_SetNextFramebuffer(xfb);
// Make the display visible
VIDEO_SetBlack(FALSE);
// Flush the video register changes to the hardware
VIDEO_Flush();
// Wait for Video setup to complete
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
}
This is the remaining C source file for template.c:
#include <stdio.h>
#include <stdlib.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
//static void *xfb = NULL;
//static GXRModeObj *rmode = NULL;
void initwii(void); //prototype
//---------------------------------------------------------------------------------
int main(int argc, char **argv) {
//---------------------------------------------------------------------------------
initwii();
// The console understands VT terminal escape codes
// This positions the cursor on row 2, column 0
// we can use variables for this with format codes too
// e.g. printf ("\x1b[%d;%dH", row, column );
printf("\x1b[2;0H");
printf("Hello World!");
while(1) {
// Call WPAD_ScanPads each loop, this reads the latest controller states
WPAD_ScanPads();
// WPAD_ButtonsDown tells us which buttons were pressed in this loop
// this is a "one shot" state which will not fire again until the button has been released
u32 pressed = WPAD_ButtonsDown(0);
// We return to the launcher application via exit
if ( pressed & WPAD_BUTTON_HOME ) exit(0);
// Wait for the next frame
VIDEO_WaitVSync();
}
return 0;
}
Now there are two files in the "source" directory: template.c and function.c which you can compile with the existing makefile for the template project.
Change the makefile and generate an assembler source file
Before compiling this to an assembler source file you have to make a change to the makefile. You have to add the switch -save-temps to the compiler options so the assembler file generated by GCC will not be deleted after linking. Also remove debugging code (-g) and optimisation (-O2) to keep the assembler file short and readable. You can also add the switch -fverbose-asm but we do not need to do that here.
CFLAGS = -save-temps -Wall $(MACHDEP) $(INCLUDE)
#CFLAGS = -save-temps -fverbose-asm -Wall $(MACHDEP) $(INCLUDE)
#CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)
After running the makefile the resulting assembler code is in the file template.s in the "build" directory next to the "source" directory. This file is shown below. You can just select "Make" from the "Programmer's Notepad" editor for this.
.file "template.c"
.section .rodata
.align 2
.LC0:
.string "\033[2;0H"
.align 2
.LC1:
.string "Hello World!"
.section ".text"
.align 2
.globl main
.type main, @function
main:
stwu 1,-40(1)
mflr 0
stw 31,36(1)
stw 0,44(1)
mr 31,1
stw 3,24(31)
stw 4,28(31)
bl initwii
lis 9,.LC0@ha
la 3,.LC0@l(9)
crxor 6,6,6
bl printf
lis 9,.LC1@ha
la 3,.LC1@l(9)
crxor 6,6,6
bl printf
.L2:
bl WPAD_ScanPads
li 3,0
bl WPAD_ButtonsDown
mr 0,3
stw 0,8(31)
lwz 0,8(31)
rlwinm 0,0,0,24,24
cmpwi 7,0,0
beq 7,.L3
li 3,0
bl exit
.L3:
bl VIDEO_WaitVSync
b .L2
.size main, .-main
.ident "GCC: (GNU) 4.2.4 (devkitPPC release 17)"
Below is this file again with comments added which shall explain the assembler statements. Note: e.g. r1 stands for GPR1.
.file "template.c"
.section .rodata /* beginning of Read Only data segment */
.align 2 /* align to 4-byte boundary
*/
.LC0:
.string "\033[2;0H" /* define string LC0 */
.align 2 /* align to 4-byte boundary */
.LC1:
.string "Hello World!" /* define string LC1 */
.section ".text" /* beginning of text(code) segment */
.align 2 /* align to 8-byte boundary */
.globl main /* make main global for linker */
.type main, @function /* declare main as a function */
main:
/* Following are special instructions for the PPC processor */
/* r0-r31 denote the general purpose registers of the PowerPC, r1=SP */
/* Setup SVR4 ABI stack frame */
stwu 1,-40(1) /* store word with update*/
/* save r1(SP) at address r1-40 then set(update) r1 to r1-40*/
mflr 0 /* move from LR (Link Register) */
/* save the return address in r0 */
stw 31,36(1) /* store word */
/* store r31 at address r1+36 */
stw 0,44(1) /* store word */
/* store r0 at address r1+44 */
mr 31,1 /* move register */
/* Move r1 to r31 */
stw 3,24(31) /* store word */
/* store r3 at address r31+24 */
stw 4,28(31) /* store word */
/* store r4 at address r31+28 */
/* done setting up stack frame */
bl initwii /* bl = branch with Link Register command */
/* call initwii */
/* pass address of LC0 string in r3 for printf function now */
lis 9,.LC0@ha /* Load Immediate Shifted */
/* load upper halfword of address of "LC0" into r9 */
la 3,.LC0@l(9) /* load address */
/* add lower halfword of address of "LC0" to r9 and load into r3 */
crxor 6,6,6 /* Condition Register XOR on bit 6 */
/* used to indicate that no floating point arguments are passed - ABI */
bl printf /* bl = branch with Link Register command */
/* call printf */
/* pass address of LC1 string in r3 for printf function now */
lis 9,.LC1@ha /* Load Immediate Shifted */
/* load upper halfword of address of "LC1" into r9*/
la 3,.LC1@l(9) /* load address */
/* add lower halfword of address of "LC1" to r9 and load into r3 */
crxor 6,6,6 /* Condition Register XOR on bit 6 */
/* used to indicate that no floating point arguments are passed - ABI */
bl printf /* bl = branch with Link Register command */
/* call printf */
.L2: /* Input loop */
bl WPAD_ScanPads /* bl = branch with Link Register command */
/* call WPAD_ScanPads */
li 3,0 /* Load Immediate */
/* set r3 to 0 */
bl WPAD_ButtonsDown /* bl = branch with Link Register command */
/* calls WPAD_ButtonsDown and returns value in r3 */
mr 0,3 /* move register */
/* move r3 to r0 */
stw 0,8(31) /* store word */
/* store r0 at address r31+8 */
lwz 0,8(31) /* Load Word and Zero */
/* Load r0 with word at address r31+8 - redundant here */
rlwinm 0,0,0,24,24 /* Rotate Left Word Immediate then AND with Mask */
/* rotation=0 -> just clear all bits except bit 24 in big-endian format */
/* to check if home button pressed below with cmpwi */
cmpwi 7,0,0 /* Compare Word Immediate instruction */
/* compares whether r0 is zero and sets Condition Register Field 7 accordingly */
beq 7,.L3 /* beq = branch if equal command */
/* branch to label L3 if cmpwi found that r0 is equal to 0 */
/* = home button not pressed */
li 3,0 /* Load Immediate */
/* set r3 to 0 - the return code for exit */
bl exit /* bl = branch with Link Register command */
/* call exit - frees stackframe again */
.L3:
bl VIDEO_WaitVSync /* bl = branch with Link Register command */
/* call VIDEO_WaitVSync */
b .L2 /* b = branch command */
/* jump to L2 label to call WPAD_ScanPads again */
.size main, .-main /* allow gcc to include debugging info */
.ident "GCC: (GNU) 4.2.4 (devkitPPC release 17)" /* not used by as */
There is also a function.s assembler file in the "build" directory which contains the file function.c in AS assembler code. This is a template for assembler subroutines to be linked to a C program.
Modify the assembler source
Open the file "template.s" in Programmer's Notepad now. Our small change will be to change the output string:
.LC1:
.string "Hello Assembler!"
Save the file after making the change. This is an extremly small change but sufficient for this application note so far.
Compile the assembler source
Now you have to compile this assembler file. For this go into a command prompt window within Windows. There go into the "build" directory and assemble the file template.s with the command:
\devkitpro\devkitPPC\bin\powerpc-gekko-as.exe -o template.o template.s
If there is no message, everything worked as it should and AS will compile the file template.s to template.o which is an object file which needs to be linked now.
You could put this command into a batch file or make a new entry for this in the TOOLS menu of Programmer's Notepad.
Link the assembler source
Run the Makefile again from Programmer's Notepad. Since template.o has a later date/time than template.c now, make will not compile template.c again but just link template.o and function.o to a template.dol/elf file. The makefile will have GCC call the linker ld.exe for us and include the standard libraries by default.
Run the code on the WII
The file template.dol can be transferred to the Wii by selecting RUN in Programmer's Notepad.