Assembler Application Note

From WiiBrew
Jump to navigation Jump to search

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:

  1. Setup the template example in devkitpro\examples\wii\template to compile and run it successfully on the Wii
  2. Put the initialization code into a separate file to keep the program as short as possible
  3. Modify the makefile to avoid that the assembler file which gcc generates gets deleted and remove debugging code and optimization
  4. Make a small change in the resulting assembler file
  5. Compile the assembler file with as.exe
  6. Link the object file made with as.exe by calling gcc
  7. 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.