home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Datafile PD-CD 3
/
PDCD_3.iso
/
utilities
/
utilss
/
sasm
/
SAsm
/
Tutorial
< prev
next >
Wrap
Text File
|
1993-03-20
|
13KB
|
279 lines
--==>> 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 creat