PowerPC Command Set -Browse-

Contents | Start

4. Extras
4.1
4.2
4.3
Macro Commands
Label
Calling Omikron Basic Functions


This chapter explains a few special Assembler Library functions as well as how to call Omikron Basic functions from the assembler code.



4.1 Macro Commands

Macro commands consist of several individual commands. Macro commands are very useful to take care of repetitive tasks such as backing up and restoring register contents. The Assembler Library provides the following macro commands:


Liw Gpr_D,Simm32
Gpr_D General Purpose Register (0 - 31).
Simm32 Signed Long Integer (-$80000000 - $7FFFFFFF).
This command is similar to the command Li already introduced last chapter; however, this command allows for the loading of a 32 bit operator Simm32 into a general purpose register.
If
-$8000 <= Simm32 <= $7FFF applies, only one command is required, otherwise two.


Cfti Gpr_D,Fpr_S
Gpr_D General Purpose Register (0 - 31).
Fpr_S Floating Point Register (0 - 31).
This macro command was integrated because there is no direct link between the integer unit and the floating point unit. It copies a floating point number from the floating point register Fpr_S to the general purpose register Gpr_D.

This also changes the content of
Fpr0.

Citf Fpr_D,Gpr_S
Fpr_D Floating Point Register (0 - 31).
Gpr_S General Purpose Register (0 - 31).
This command does the exact opposite of Cfti. It copies an integer number from the general purpose register Gpr_S to the floating point register Fpr_D.

This also changes the registers
R0 and Fpr0.
Store_Register Spr_Mask[,Gpr_Mask[,Fpr_Mask]]
Spr_Mask The individual bits in this value have the following significance:
BIT(0,Spr_Mask) = 1 : Saves link register.
BIT(1,Spr_Mask) = 1 : Saves condition register.
BIT(2,Spr_Mask) = 1
: Saves count register.
BIT(3,Spr_Mask) = 1
: Saves exception register.
Gpr_Mask Each of the 32 bits in this value represents a general purpose register. The content of the register is saved if the bit is set.
Fpr_Mask Each of the 32 bits in this value represents a floating point register. The content of the register is saved if the bit is set.
You will probably need this macro command quite frequently. It saves the content of several registers to the dynamic stack (R31). This process is controlled by three register masks. The dynamic stack pointer is automatically reduced according to the number and length of the saved registers.

If
Spr_Mask <> 0 applies, R0 is changed.

Restore_Register Spr_Mask[,Gpr_Mask[,Fpr_Mask]]
Spr_Mask The individual bits in this value have the following significance:

BIT(0,Spr_Mask) = 1 : Loads link register.
BIT(1,Spr_Mask) = 1 : Loads condition register.
BIT(2,Spr_Mask) = 1
: Loads count register.
BIT(3,Spr_Mask) = 1
: Loads exception register.
Gpr_Mask Each of the 32 bits in this value represents a general purpose register. The content of the register is loaded if the bit is set.
Fpr_Mask Each of the 32 bits in this value represents a floating point register. The content of the register is loaded if the bit is set.
This is practically the reversal of the previous command. You will probably use both commands as a pair (e.g., start and end of a subroutine or in front of and behind a BASIC function call). It loads the content of several registers from the dynamic stack (R31). This process is controlled by three register masks. The dynamic stack pointer is automatically increased according to the number and length of the restored registers.

If
Spr_Mask <> 0 applies, R0 is changed.
Of course, you can easily define additional macro commands yourself. All you need is an Omikron Basic procedure and write the desired mnemonics into the procedure. Then you have to call this procedure within the assembler program.




4.2 Label

Having to calculate the jump distance is always a bother when using jump commands. This is done relatively easily with the PowerPC processor because every command is exactly 4 bytes long but manual calculations are rather cumbersome for greater jump distances. To facilitate this task, the Assembler Library offers the use of labels:

Label Lbl$
Lbl$ A string used as branch mark or label. Labels are case sensitive. Special characters may be used for label definitions as well.
This command defines a label named Lbl$. Just write Label Lbl$ in front of the assembler command to which you want to jump or branch off to. The jump itself requires one of the special jump commands listed as follows:

All mnemonics for jump commands to a label are created by adding an "L' to the normal jump commands. Indicate a string instead of a number in the last parameter. The Assembler Library then automatically calculates the corresponding jump distance. Jump commands to a label are available only for relative jumps.
Label jump commands:
B_L Lbl$
Bl_L Lbl$
Bc_L Bo,Bi,Lbl$
Bcl_L Bo,Bi,Lbl$
Simplified mnemonics for jump commands jumping to a label:
Bdnz_L Lbl$ = Bc_L 16,0,Lbl$
Bdnzf_L Bi,Lbl$ = Bc_L 0,Bi,Lbl$
Bdnzfl_L Bi,Lbl$ = Bcl_L 0,Bi,Lbl$
Bdnzl_L Lbl$ = Bcl_L 16,0,Lbl$
Bdnzt_L Bi,Lbl$ = Bc_L 8,Bi,Lbl$
Bdnztl_L Bi,Lbl$ = Bcl_L 8,Bi,Lbl$
Bdz_L Lbl$ = Bc_L 18,0,Lbl$
Bdzf_L Bi,Lbl$ = Bc_L 2,Bi,Lbl$
Bdzfl_L Bi,Lbl$ = Bcl_L 2,Bi,Lbl$
Bdzl_L Lbl$ = Bcl_L 18,0,Lbl$
Bdzt_L Bi,Lbl$ = Bc_L 10,Bi,Lbl$
Bdztl_L Bi,Lbl$ = Bcl_L 10,Bi,Lbl$
Bf_L Bi,Lbl$ = Bc_L 4,Bi,Lbl$
Bfl_L Bi,Lbl$ = Bcl_L 4,Bi,Lbl$
Bt_L Bi,Lbl$ = Bc_L 12,Bi,Lbl$
Btl_L Bi,Lbl$ = Bcl_L 12,Bi,Lbl$
Simplified mnemonics for jump commands with compare condition jumping to a label:
Beq_L Cr_D,Lbl$ = Bc_L 12,Cr_D SHL 2+2,Lbl$
Beql_L Cr_D,Lbl$ = Bcl_L 12,Cr_D SHL 2+2,Lbl$
Bge_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2,Lbl$
Bgel_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2,Lbl$
Bgt_L Cr_D,Lbl$ = Bc_L 12,Cr_D SHL 2+1,Lbl$
Bgtl_L Cr_D,Lbl$ = Bcl_L 12,Cr_D SHL 2+1,Lbl$
Ble_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2+1,Lbl$
Blel_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2+1,Lbl$
Blt_L Cr_D,Lbl$ = Bc_L 12,Cr_D SHL 2,Lbl$
Bltl_L Cr_D,Lbl$ = Bcl_L 12,Cr_D SHL 2,Lbl$
Bne_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2+2,Lbl$
Bnel_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2+2,Lbl$
Bng_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2+1,Lbl$
Bngl_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2+1,Lbl$
Bnl_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2,Lbl$
Bnll_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2,Lbl$
Bns_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2+3,Lbl$
Bnsl_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2+3,Lbl$
Bnu_L Cr_D,Lbl$ = Bc_L 4,Cr_D SHL 2+3,Lbl$
Bnul_L Cr_D,Lbl$ = Bcl_L 4,Cr_D SHL 2+3,Lbl$
Bso_L Cr_D,Lbl$ = Bc_L 12,Cr_D SHL 2+3,Lbl$
Bsol_L Cr_D,Lbl$ = Bcl_L 12,Cr_D SHL 2+3,Lbl$
Bun_L Cr_D,Lbl$ = Bc_L 12,Cr_D SHL 2+3,Lbl$
Bunl_L Cr_D,Lbl$ = Bcl_L 12,Cr_D SHL 2+3,Lbl$
Beq__L Cr_D,Lbl$ = Bc_L 13,Cr_D SHL 2+2,Lbl$
Beql__L Cr_D,Lbl$ = Bcl_L 13,Cr_D SHL 2+2,Lbl$
Bge__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2,Lbl$
Bgel__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2,Lbl$
Bgt__L Cr_D,Lbl$ = Bc_L 13,Cr_D SHL 2+1,Lbl$
Bgtl__L Cr_D,Lbl$ = Bcl_L 13,Cr_D SHL 2+1,Lbl$
Ble__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2+1,Lbl$
Blel__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2+1,Lbl$
Blt__L Cr_D,Lbl$ = Bc_L 13,Cr_D SHL 2,Lbl$
Bltl__L Cr_D,Lbl$ = Bcl_L 13,Cr_D SHL 2,Lbl$
Bne__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2+2,Lbl$
Bnel__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2+2,Lbl$
Bng__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2+1,Lbl$
Bngl__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2+1,Lbl$
Bnl__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2,Lbl$
Bnll__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2,Lbl$
Bns__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2+3,Lbl$
Bnsl__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2+3,Lbl$
Bnu__L Cr_D,Lbl$ = Bc_L 5,Cr_D SHL 2+3,Lbl$
Bnul__L Cr_D,Lbl$ = Bcl_L 5,Cr_D SHL 2+3,Lbl$
Bso__L Cr_D,Lbl$ = Bc_L 13,Cr_D SHL 2+3,Lbl$
Bsol__L Cr_D,Lbl$ = Bcl_L 13,Cr_D SHL 2+3,Lbl$
Bun__L Cr_D,Lbl$ = Bc_L 13,Cr_D SHL 2+3,Lbl$
Bunl__L Cr_D,Lbl$ = Bcl_L 13,Cr_D SHL 2+3,Lbl$
After that much theory, it is time for a practical, hands-on example:
Example with label:
COMPILER "BAS_MEM 1000000"
COMPILER "MIN_SIZE 1000000"
COMPILER "WARNINGS OFF"
COMPILER "OPW 480*200"

Assembler_Init
Generate_Assembler_Code
DEF USR=Fibonacci
REPEAT
 INPUT "Enter integer: ";A
 PRINT "The"+STR$(A)+"th Fibonacci number is";USR(A)
UNTIL 0
Assembler_Exit
END

DEF PROC Generate_Assembler_Code
 COMPILER "DEF_CONST"
  R3=3:R4=4:R5=5
  Cr0=0
 COMPILER "END_CONST"
 Fibonacci=MEMORY(128)
 Code_Start Fibonacci,128:'The assembler code starts here.
                Cmpwi Cr0,R3,2
                Bltlr Cr0:'If less than 2 then out.
                Subi R3,R3,1
                Mtctr R3
                Li R3,1:'Load R3 with 0. Fibonacci number.
                Li R4,1'Load R4 with 1st Fibonacci number.
 Label "loop":Add R5,R4,R3
                Mr R4,R3
                Mr R3,R5
                Bdnz_L "loop"
                Blr :'Jump back
 Code_End
END_PROC

The assembler routine calculates the nth Fibonacci number with this one being defined as the sum of the two predecessors (f(n)=f(n-1)+f(n-2)). The first two Fibonacci numbers are f(0)=1 and f(1)=1. To calculate the nth Fibonacci number, you have to start with these two initial ones and then calculate the successive numbers of the series.


4.3 Calling Omikron Basic Functions

A frequent problem when writing assembler programs is that a function is needed, which in turn requires a complex subroutine, but this function has already been realized in Omikron Basic. Just think of such things as transcendental functions (
SIN,COS, etc.), matrixes and determinant arithmetic, SORT or input and output commands.

This chapter discusses how to utilize Omikron Basic functions from an assembler routine.

- First, write a BASIC function with DEF FN (e.g., DEF FN Tangent#(X#)=TAN(X#)). Of course, this can also be a function with several lines.
- Determine the address of the function (e.g., Tangent = &FN Tangent#()).
- Save all registers (nonvolatile registers as well) in the assembler program with contents you might still need. It is best to use the macro command Store_Register. Do not forget the link register.
- Load all parameters to be passed to the function into the corresponding registers. The following arrangement applies:

Integers are passed in registers
R3 - R12.
Floating point numbers are passed in registers
FR1 - Fr13.
Strings are always passed on the stack as an 8-byte structure (4-byte pointer to the string plus 4-byte length of string). Here, the stack pointer (
R31) has to be decremented. As an alternative, it is also possible to pass the address determined with the address operator (&) and then use the deference operator (*) within the BASIC function to access the objects (see example 2).

If these registers are insufficient, additional parameters can be passed on the stack. The stack than has to be decremented accordingly.
- Load the address of the desired function into the link register and jump to this function (e.g., Liw R0,Tangent:Mtlr R0:Blrl).
- The function value is returned in R3 if your function is an integer type function.
The function value for float type functions is returned in
Fr1.
For string functions, the address of the string is returned in
R3 and the length in R4. This is then a temporary string in the garbage area. It is best to immediately transfer the temporary string in (R3/R4) to a permanent string since the very next BASIC command might already trigger a garbage collection which would effectively destroy the string.
- Restore registers with Restore_Register.
- The stack pointer has to be incremented accordingly if variables have been passed on the stack.

Example of calling numerical BASIC functions from assembler programs.
COMPILER "BAS_MEM 1000000"
COMPILER "MIN_SIZE 1000000"
COMPILER "WARNINGS OFF"
COMPILER "OPW 320*320"

Assembler_Init
Generate_Assembler_Code
DEG
DRAW 0,160 TO 320,160:DRAW 240,0 TO 240,320:'Coordinate axes.
LINE COLOR =2:DRAW 240,160:'Starting point.
CALL Cardioide
REPEAT COMPILER "EVENT" UNTIL 0
Assembler_Exit
END

DEF PROC Generate_Assembler_Code
 'Calculate absolute addresses of the BASIC functions.
 LOCAL Sine=&FN Sine#()
 LOCAL Cosine=&FN Cosine#()
 LOCAL Draw_Point=&FN Draw_Point(,)
 COMPILER "DEF_CONST"
  R0=0:R3=3:R4=4:R12=12
  Fr0=0:Fr1=1:Fr2=2:Fr3=3:Fr4=4:Fr5=5
  Cr0=0
 COMPILER "END_CONST"
 LOCAL Cardioide_Vars=MEMORY(296)
 'Write required floating point constants to the beginning of the
 'assembler program.
 DPOKE Cardioide_Vars,0:'Start angle.
 DPOKE Cardioide_Vars+8,360:'End angle.
 DPOKE Cardioide_Vars+16,1:'Increments.
 DPOKE Cardioide_Vars+24,1:'Constant 1.
 DPOKE Cardioide_Vars+32,50:'Radius (A).
 Cardioide=Cardioide_Vars+40
 'Calculate the cardioide formula R=2*A*(1-COS(Phi)).
 Code_Start Cardioide,256:'The assembler code starts here.
                Store_Register %1:'Preserve link register.
                Liw R12,Cardioide_Vars:'Address of the constants.
                Lfd Fr2,0,R12:'Load start value.
 Label "loop":Store_Register 0,%1000000000000,%100
                Fmr Fr1,Fr2
                Liw R0,Cosine:Mtlr R0:Blrl :'Fr1=FN Cosine#(Fr1).
                Restore_Register 0,%1000000000000,%100
                Lfd Fr0,24,R12:'Fr0=1.
                Fsub Fr4,Fr0,Fr1:'Fr4=1-COS(Phi).
                Lfd Fr0,32,R12:'Fr0=R.
                Fmul Fr4,Fr0,Fr4:'Fr4=A*(1-COS(Phi)).
                Fadd Fr3,Fr4,Fr4:'Fr3=2*A*(1-COS(Phi)).
                'The polar coordinates in Fr2 and Fr3 now have to be
                'converted to Cartesian coordinates.
                'Since the COS(Phi#) is still in Fr1
                'only SIN(Phi#) is left to calculate.
                Store_Register 0,%1000000000000,%1110
                Fmr Fr1,Fr2
                Liw R0,Sine:Mtlr R0:Blrl :'Fr1=FN Sine#(Fr1).
                Fmr Fr5,Fr1
                Restore_Register 0,%1000000000000,%1110
                Fmul Fr0,Fr3,Fr1:'X=R*COS(Phi).
                Cfti R3,Fr0
                Fmul Fr0,Fr3,Fr5:'Y=R*SIN(Phi).
                Cfti R4,Fr0
                Store_Register 0,%1000000000000,%100
                Liw R0,Draw_Point:Mtlr R0:Blrl :'Draw_Point(X,Y).
                Restore_Register 0,%1000000000000,%100
                Lfd Fr0,16,R12:'Load increment.
                Fadd Fr2,Fr2,Fr0
                Lfd Fr0,8,R12:'Load end angle.
                Fcmpo 0,Fr2,Fr0:'Compare with end angle.
                Blt_L 0,"loop"
                Restore_Register %1:'Restore link register.
                Blr :'Jump back
 Code_End
END_PROC
'Functions called by the assembler program.
DEF FN Sine#(A#)=SIN(A#)
DEF FN Cosine#(A#)=COS(A#)
DEF FN Draw_Point(X,Y)
 DRAW TO 240+X,160-Y
END_FN

The program draws a so-called cardioid (heart curve). This is a curve satisfying the equation R=2*A*(1-COS(Phi) in the direction of the polar coordinates. Since there is no assembler command for the cosine (this would require a series development to be programmed), and the sine is also required for the conversion to Cartesian coordinates it is advantageous to have a BASIC function take care of this task.
A third BASIC function draws the curve as well.

Tip: With longer assembler programs, it is rather awkward to crawl from the USR or CALL command to the location to be examined. Just define a BASIC function with only one BRK command to jump to the debugger right in the middle of the assembler program:

DEF FN Debugger
 BRK
END_FN


Call this debugger function from your assembler program at the location you want to check. The program stops in the function. All that is left to do is to use a few single steps for the function's epilog and you are where you want to be in your assembler program after the second
Blr.

Example of calling BASIC functions with strings as parameters.
COMPILER "BAS_MEM 1000000"
COMPILER "MIN_SIZE 1000000"
COMPILER "WARNINGS OFF"
COMPILER "OPW 320*320"

Assembler_Init
Generate_Assembler_Code
String1$="Omikron ":String2$="Basic ":String3$="Mac"
Result$=STRING$(32,0):'Space for result.
CALL Pass_String(L &String1$,L &String2$,L &String3$,L &Result$)
PRINT String1$
PRINT String2$
PRINT String3$
PRINT Result$
REPEAT COMPILER "EVENT" UNTIL 0
Assembler_Exit
END

DEF PROC Generate_Assembler_Code
 'Calculate absolute addresses of the BASIC functions.
 LOCAL String_Add=&FN String_Add$(,)
 LOCAL String_Add_Ptr=&FN String_Add_Ptr$(,)
 COMPILER "DEF_CONST"
  R0=0:R3=3:R4=4:R5=5:R31=31
  Fr0=0
 COMPILER "END_CONST"
 Pass_String=MEMORY(256)
 Code_Start Pass_String,256:'The assembler code starts here.
                Store_Register %1:'Preserve link register.
                'Strings to FN String_Add$(,) pass to stack.
                Lwz R4,8,R31:'&String2$.
                Lwz R3,4,R31:'&String1$.
                Lfd Fr0,0,R4:Stfdu Fr0,-8,R31:'String2 to stack.
                Lfd Fr0,0,R3:Stfdu Fr0,-8,R31:'String1 to stack.
                Liw R0,String_Add:Mtlr R0:Blrl :'FN String_Add$.
                Addi R31,R31,16:'Reset stack pointer.
                Lwz R5,16,R31:'&Result$ to R5.
                Bl_L "copy"
                'Now all of it with pointer to registers R3 and R4.
                Lwz R3,16,R31:'&Result$ to R3.
                Lwz R4,12,R31:'&String3$ to R4.
                Liw R0,String_Add_Ptr:Mtlr R0:Blrl :'FN String_Add_Ptr$.
                Lwz R5,16,R31:'&Result$ to R5.
                Bl_L "copy"
                Restore_Register %1:'Restore link register.
                Blr :'Back to BASIC.

'Subroutines:
 Label "copy":'(R3/R4) copied to R5.
                Stw R4,4,R5:'enter length.
                Lwz R5,0,R5:'Address of target string to R5.
                Srwi_ R4,R4,3:'Divide length by 8.
                Addi R0,R4,1:'Add 1.
                Mtctr R0
                Subi R3,R3,8:'Subtract 8 to use commands with
                Subi R5,R5,8:'Update.
 Label "loop":Lfdu Fr0,8,R3:'Use floating point command since 8 bytes
                Stfdu Fr0,8,R5:'can be moved per command 8.
                Bdnz_L "loop"
                Blr
 Code_End
END_PROC

'Functions called by the assembler program.
DEF FN String_Add$(A$,B$)=A$+B$
DEF FN String_Add_Ptr$(A,B)=*A$+*B$

The result is returned in (R3/R4) in the form of a temporary string. This string is in the garbage area and has to be copied to a true string (Result$) before calling the next BASIC function.
We used the pointer method for the second string addition to show this method as well. The addresses of
Result$ and String3$ are passed in registers R3 and R4. Of course, the dereference operator (*) has to be used to access the strings within the BASIC function FN String_Add_Ptr$. The result in (R3/R4) is then again copied to Result$.
Caution: Make sure to pass only BASIC strings and not fake strings to BASIC functions because BASIC strings require an additional header located in front of the actual string data.
Always use different names for the passing parameters of the BASIC function than for strings in the assembler program. It is best to define global strings right at the beginning of the BASIC program especially for use in your assembler program.

PowerPC Command Set -Browse-

Contents | Start



© 2003 Berkhan-Software
www.berkhan.com | Online Orders