home *** CD-ROM | disk | FTP | other *** search
-
- --==>> Basic Assembler Tutorial <<==--
-
- (C) D.J.Holden 1990
-
- This short text will not attempt to teach you how to write in Assembler for
- the Archimedes. I assume that you are already familiar with ARM machine code
- and the use of the Basic Assembler. If you are not I can recommend the book
- 'Archimedes Assembly Language' by Mike Ginns published by Dabs Press. My
- purpose is to describe some of the more advanced ways in which the Basic
- Assembler can be used. My primary purpose is to help you to understand how
- to use Macros with SAsm but everything in this text is written so that it
- can be applied directly to the Basic Assembler. In fact SAsm has a more
- powerful type of Macro, the Expanded Macro, but this cannot be used by
- the Basic Assembler so will not be described here and you are refered to
- the Manual to find out about them.
-
- Simple use of Macros
- ~~~~~~~~~~~~~~~~~~~~
- A Macro in assembler terms is a way of writing a pre-defined piece of code
- which the assembler uses when it finds a specific instruction in the source
- code. This means that if you have a series of instructions that occur many
- times in the program you need only write it once and each time you need that
- routine you invoke the macro and the assembler will assemble the
- instructions you have previously defined or 'expand' the macro. This not
- only saves time and effort and makes the code easier to follow but also
- avoids mistakes. Once you have written and tested a macro the chance of
- error caused by mistyping a register number or mnemonic is minimised.
-
- There are as many different ways of defining and invoking macros as there
- are assemblers, but most permit the passing of PARAMETERS so the macro
- definition may be modified in some way each time it is expanded.
-
- The Basic Assembler allows the use of macros in assembly language by means
- of Functions. These are perfectly normal Basic Functions and should be
- defined after the END statement in your program.
-
- For example, suppose that you needed to repeatedly use the instructions;
-
- MOV r0,#21
- MOV r1,#0
- SWI "OS_Byte"
-
- This is the familiar 'FX21,0' which flushes the keyboard buffer. To save
- writing this over and over again define a function 'FNflush'
-
- DEFFNflush
- [OPT PASS
- MOV r0,#21
- MOV r1,#0
- SWI "OS_Byte"
- ]
- =0
-
- Now each time you want to flush the keyboard buffer instead of writing the
- actual instruction in your code you just write
-
- FNflush
-
- and when the code is assembled Basic will look for the Function 'flush' and
- do whatever is required, in this case assemble the three instructions.
-
- You can use the same principle to flush any buffer rather than just the
- keyboard. In this case you could pass the number of the buffer you wish to
- flush as a Parameter.
-
- DEFFNflush (buffer_number)
- [OPT PASS
- MOV r0,#21
- MOV r1,#buffer_number
- SWI "OS_Byte"
- ]
- =0
-
- Now to flush the keyboard buffer you would use FNflush (0) or the printer
- buffer FNflush (3) and so on.
-
- You can see that with sensibly chosen names your code can be made much
- easier to follow and therefore to modify and debug. FNflush (0) is more
- easily recognised as a macro to flush buffer number 0 than the actual code,
- when you would first need to remember what OSByte 21 does.
-
- The previous examples are very simple and contain only a few lines of code
- but sometimes macros can be quite complex and contain multi-option
- conditional assembly and themselves call other macros.
-
-
- Writing Macros
- ~~~~~~~~~~~~~~
- You should take note of three things about the previous examples. The first
- is that once Basic has entered the Function it has forgotten that it was
- actually assembling machine code. It remembers again when it leaves the
- Function but inside the Function you need to open the assembler brackets,
- define the OPT setting and close them again before you finish.
-
- I have used the variable PASS to determine the OPT setting and you should
- use whatever variable you have used in the main part of your code so that
- the macro will be assembled with the same OPT setting as the rest. The
- exception to this is where forward references are required in a macro and
- this will be described later.
-
- The second thing is that the macro ends with '=0'. This is called a 'Null
- Function' and it means that the assembler doesn't require any result to be
- returned so it is just a way of ending the function. If you were writing
- Basic you would normally use a Procedure rather than a Function in these
- circumstances but you can't call Procedures from within the assembler so we
- use a Null Function instead.
-
- The third thing, which follows on from the second, is that in 'normal' basic
- a Function must always be called as <something>=FNwhatever. When using
- Functions as Macros in assembler they are NOT called in this way which is
- why the result returned is irrelevant.
-
-
- Forward References in Macros
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- You must remember that when a macro is expanded the code is assembled as a
- part of the main code after substituting any parameters so any labels will
- be defined EACH TIME THE MACRO IS CALLED. At first sight this might not seem
- a problem but when Basic assembles a program it records all the labels on
- the first pass and uses the definitions it has found on the second. This
- means that if a label is defined twice then the LAST definition is the one
- that will be used throughout the second pass.
-
- This is no problem when writing loops with backward branches because no
- matter how many times the macro is expanded the current definition for a
- backward label is the last definition which is the one that is required. I
- often use this to avoid creating a new label for a simple loop by using the
- label 'loop' at the start, then when I branch back to 'loop' the branch
- instruction is assembled with the offset to the last 'loop'. Use this with
- caution as it is easy to move or modify the code and upset everything.
-
- With a forward branch things are different. For example, consider this
- simple macro;
-
- DEFFNtest
- [OPT PASS
- MOV r3,#4
- MOV r4,#3
- CMP r0,r1
- BNE skip
- MOV r3,#3
- MOV r4,#4
- .skip MOV r0,#&FF
- ]
- =0
-
- This would really be better using conditional instructions but consider what
- happens during assembly if the macro has been used more than once.
-
- On the first pass the assembler will record the value of 'skip' as the
- address of the label the LAST time the macro was expanded. When the second
- pass is started the value of skip will therefore be wrongly set for the
- FIRST time the macro was expanded and any branch will go straight to the
- LAST macro expansion, not at all what was intended.
-
- Making the label a LOCAL variable doesn't help because it is the same LOCAL
- variable each time the macro is called. To get around this problem we must
- use subterfuge and force the assembler to do two passes within the macro
- each time it assembles it, therefore ensuring that the labels are updated
- whenever it is called.
-
- The code now becomes;
-
- DEFFNtest
- old_P%=P% : old_O%=O%
- FOR pass%=4 TO 6 STEP 2
- P%=old_p% : O%=old_o%
- [OPT pass%
- MOV r3,#4
- MOV r4,#3
- CMP r0,r1
- BNE skip
- MOV r3,#3
- MOV r4,#4
- .skip MOV r0,#&FF
- ]
- NEXT
- =0
-
- This will go into its own two pass loop each time it is assembled and so
- ensure that the forward branch always goes to the correct 'skip'.
-
- It works as follows. Firstly the values of P% and O% are copied to old_O%
- and old_P%. This is necessary so that the actual P% and O% can be reset to
- the correct value for the second pass. Next a FOR-NEXT loop is created
- around the code using the variable pass% instead of PASS which I normally
- use in the main part of my code. You can of course make these variable LOCAL
- if you wish to avoid conflict with others defined in the main part of the
- program. I have used OPT settings of 4 and 6 to give offset assembly with no
- listing but this can be changed if required.
-
- When Basic expands the Function it will make two passes for each pass of the
- main code. The first pass will define the label 'skip' and the second pass
- will set the branch offset, so each branch will go to the correct 'skip'
-
- Conditional Assembly
- ~~~~~~~~~~~~~~~~~~~~
- This means writing a part of a program which can be assembled in more than
- one way depending upon a CONDITION. You can use any condition you like,
- whether you are holding down a certain key (my favourite), whether you have
- set a certain variable at the start of the program, whether it's after
- midnight on Friday or anything else you care to devise.
-
- There are two main uses for conditional assembly in stand-alone machine code
- programs, although when you are assembling code to be used as a sub routine
- within a Basic program there are lots of others. The main reasons are to
- produce different versions and to assemble different code in a macro
- depending upon the parameters passed.
-
- I make use of the first to assemble different versions of Shareware programs
- for Registered and Unregistered users. For example the Standard and Advanced
- versions of SAsm are both assembled from a single set of source files. This
- means that when I improve the program or add features I need only change one
- file. I just hold down the SHIFT key when assembling the files and use
- conditional assembly to leave out the features I don't want the Standard
- version to have. You can also use conditional assembly to create versions
- for different countries by writing messages in alternative languages and
- assembling the appropriate version.
-
- The second use is important because it enables you to use a single macro
- definition to produce a whole series of different sets of code. For example
- the FNmov macro which is built in to SAsm will load a register with any 32
- bit positive or negative number. It does this by assembling different
- instructions depending upon the sign of the number and will assemble from
- one to four instructions depending upon it's size. All this is completely
- transparent when you are writing the code of course, the macro uses
- conditional assembly to make all the decisions for you.
-
- The simplest method is with an IF THEN - (ELSE) - ENDIF construct, although
- you can use WHILE - ENDWHILE or CASE statements. As an example suppose that
- you include the following line near the start of your program;
-
- IF INKEY(-1) shift%=TRUE ELSE shift%=FALSE
-
- The variable shift% will return TRUE if the shift key was held down and
- FALSE if not. Now consider the following code somewhere in the program.
-
- ........ ; some code
- ........
- ] : REM leave assembler
- IF shift%=TRUE THEN
- [OPT PASS ; back in assembler
- swi "OS_WriteS"
- equs "The SHIFT key was held" ; display a message
- equb 0 : align
- ] : REM back to Basic
- ELSE
- [OPT PASS ; back in assembler
- swi "OS_WriteS"
- equs "The SHIFT key was NOT held" ; display a message
- equb 0 : align
- ] : REM back to Basic
- ENDIF
- [OPT PASS
- ........ ;back to the program
- ........
-
- This will display a message which will be different depending upon whether
- or not you held down the SHIFT key when the code was assembled.
-
- For another example;
-
- DEFFNadd (reg_1,reg_2,number)
- [OPT PASS
- ADD reg_2,reg_1,#(number AND &FF)
- ]
- IF number AND &FF00 >0 THEN
- [OPT PASS
- ADD reg_2,reg_2,#(number AND &FF00)
- ]
- ENDIF
- =0
-
- This will assemble instructions to add a 16 bit number 'number' to register
- 'reg_1' and put the result in 'reg_2'. The first 'ADD' instruction to add
- the bottom 8 bits will always be assembled but the second instruction to add
- the top 8 bits will only be assembled if there is something to add.
-