home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Archive Magazine 1996
/
ARCHIVE_96.iso
/
discs
/
shareware
/
share_46
/
sasm
/
Docs
/
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 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.