A Macro can be compared with a chorus in a song.
Most of the time the chorus in a song is only defined once.
The simple text chorus will mark those places where the chorus text should be pasted.
You can also think of a Macro being a subroutine for the assembler.
There is a clear difference between a Macro (a subroutine for the assembler) and a normal subroutine for the target processor.
A Macro must be defined before it can be used.
There is no such thing as forward referenced Macros.
When a Macro is called (expanded) all lines of the Macro will be interpreted one by one as if it were lines coming directly from a normal source file. At the end of the expansion of the Macro the assembler continues from the source file following the line which called the Macro expansion.
Why would we use Macros when normal subroutines are more efficient when it comes to memory use?
Macros are defined using two dedicated directives with the SB-Assembler.
The .MA directive marks the start of the Macro definition and the .EM directive marks the end.
The next example shows a Macro definition that pushes the registers P, A, X and Y of a 65C02 processor onto the stack:
PUSHALL .MA ; Start of the definition PHP ; Save P register PHA ; Save Accu PHX ; Save X register PHY ; Save Y register .EM ; End of the definition
Please note that this example can't be replaced by a simple subroutine because that would make the return address inaccessible for that subroutine.
So the .MA directive starts the Macro definition.
At the same time the Macro is given the unique name PUSHALL.
NOTE: Macro names and Label names are stored separately in the SB-Assembler. This makes it perfectly legal to use Labels with the same name as Macro definitions.
The .EM directive finally terminates the Macro definition. Everything will be back to normal after that.
In version 2 a single Macro definition may not be larger than 64k bytes (-16 bytes). Please tell me if that's not enough for you. Maybe I can throw in a good word for you at your favourite shrink. Allright, if 64k really isn't enough for you, then try version 3 because there is no limit there any more.
When the .EN directive is found during Macro expansion in Version 3 of the SB-Assembler, all nested Macro expansions will be terminated. Then the current source file is closed and assembly continues from the previous level of source file, if any. Otherwise assembly will be terminated all together.
Once you have defined a Macro you can call it as often as you need it. This calling of a Macro is called Expanding.
You expand a Macro by typing >macroname in the instruction field of a program line.
The macroname is the name of the Macro you wish to expand at this point.
This is an example of a Macro expansion in a program:
TEST NOP Just an imaginary program line >PUSHALL NOP Another imaginary program line
If you take a look at the List file of this segment of the program you could see something like this:
100 TEST NOP Just an imaginary program line 101 >PUSHALL M1 PHP M2 PHA M3 PHX M4 PHY 102 NOP Another imaginary program line
The line numbers on the left indicate where we are in our program.
This comes in handy when errors are detected in your program.
As you can see the expanding Macro uses the lines of the Macro definition literally.
Well, almost literally.
Maybe you've noticed that the comment fields that were present during the definition don't exist anymore after expansion.
Don't use the ; symbol during the Macro definition if you want to keep the comment field in your definition. This will produce a larger symbol table though.
Version 3 of the SB-Assembler doesn't have the old DOS memory limitations. Therefore the ; has no special meaning to the Version 3 of the SB-Assembler. Everything is included in the Macro definition, even comments which are preceded by a semicolon.
A List Option exists to control the listing of expanding Macros (See .LI directive).
The List Options MON and MOFF can switch the listing of expanding Macros on or off.
Expanding Macros will not be listed to the list file if the MOFF option is chosen.
The example above will look something like this when the MOFF option is selected:
100 TEST NOP Just an imaginary program line 101 >PUSHALL 102 NOP Another imaginary program line
It goes without saying that the Macro will be expanded, even though it is not listed!
We will encounter a small problem if we want to define a label within a Macro.
This label will be defined every time we expand the Macro.
And it is not very likely that this label will get the same value every time we define it.
This is unacceptable!
Here is a small example of the problem in 8051 code:
1 WAIT .MA 2 MOV R0,#10 ; Initialize delay 3 LOOP NOP 4 DJNZ R0,LOOP ; Count down to 0 5 .EM 6 7 START >WAIT ; Expand the Macro once M1 MOV R0,#10 M2 LOOP NOP M3 DJNZ R0,LOOP 8 >WAIT ; Expand the Macro again M1 MOV R0,#10 M2 LOOP NOP <--- Produces extra definition error M3 DJNZ R0,LOOP <--- Jumps back to the first LOOP
As you can see the label LOOP is defined twice in this example and obviously with a different value the second time.
Please note that the label LOOP was not declared during the definition of the Macro yet. The label LOOP is first declared when the Macro is expanded for the first time.
The solution to the problem is found in Macro Labels.
Every Macro expansion gets a unique internal number. Every time you use a Macro Label this internal Macro number is appended to the Macro Label's name, making the label unique again.
In Version 2 of the SB-Assembler the internal Macro number ranges from 1 to 255, excluding 46 (decimal point).
This immediately shows the limitation of the Macro Labels.
Version 3 of the SB-Assembler has almost 4 times a bigger range for Macro labels than Version 2.
Up to 999 Macro expansions can exist in between two adjacent Global labels before you'll get into trouble.
The next example shows the solution that Macro Labels offer for the problem found in the previous example:
1 WAIT .MA 2 MOV R0,#10 ; Initialize delay 3 :LOOP NOP 4 DJNZ R0,:LOOP ; Count down to 0 5 .EM 6 7 START >WAIT ; Expand the Macro once M1 MOV R0,#10 M2 :LOOP NOP M3 DJNZ R0,:LOOP 8 >WAIT ; Expand the Macro again M1 MOV R0,#10 M2 :LOOP NOP <--- No problem this time M3 DJNZ R0,:LOOP <--- Correct jump this time
The program in the example above looks a lot like the one in the previous example, but it is working so much better this time.
Apart from the advantages of Macro Labels they also have some disadvantages. Macro Labels "live" only inside the expanding Macro. So they can only be reached from within the Macro that defined them.
The program example below will not work:
1 WAIT .MA 2 MOV R0,#10 ; Initialize delay 3 :LOOP NOP 4 DJNZ R0,:LOOP ; Count down to 0 5 .EM 6 7 START >WAIT ; Expand the Macro once M1 MOV R0,#10 M2 :LOOP NOP M3 DJNZ R0,:LOOP 8 JMP :LOOP <--- Lable doesn't exist any more
Macros can also handle parameters in the SB-Assembler. A parameter is a variable piece of text that is pasted in the expanding Macro at predefined positions.
Parameters follow the Macro expansion call in the operand field.
Multiple parameters are separated from each other by commas.
Empty parameters are also allowed.
A parameter number is assigned to every parameter entered in the operand field of the Macro expansion call. The first parameter will be parameter ]1, the second is ]2, the third is ]3, .., etc. Please note that the tenth parameter will be parameter ]0, not ]10 because that would be 2 digits.
The parameters are referred by the parameter identifiers ]x, where x is the parameter number that was assigned to it.
The parameter numbers range from ]1 to ]9 for the first 9 parameters and ]0 means the tenth parameter (not the first).
The parameter ]x is inserted in the expanding Macro line every time the SB-Assembler encounters the parameter identifier ]x (where x is a digit from 1 to 0).
I think this calls for a small example to clarify it all. Imagine a Z80 routine to copy a piece of memory. The Z80 has a very nice instruction that handles this fully automatically. But this instruction does require the proper setting of some registers. A Macro with three parameters will simplify the process for the programmer.
1 COPY .MA SOURCE,DEST,LENGTH 2 LD BC,]3 ; Number of bytes to copy 3 LD HL,]1 ; Source pointer 4 LD DE,]2 ; Destination pointer 5 LDIR ; Start copying 6 .EM 7 8 .CR Z80 9 10 RANGE1 .EQ $1000 Source's begin address 11 RANGE2 .EQ $2000 Destination's begin address 12 13 START >COPY RANGE1,RANGE2,$200 M1 LD BC,$200 M2 LD HL,RANGE1 M3 LD DE,RANGE2 M4 LDIR 14 NOP The rest of the program
The text behind the .MA directive are only comments and may be anything you like. You could type the name of your favourite movie star here, but it's more useful to type the names of the expected parameters in this case. This also shows the expected order in which the parameters should be given.
During the expansion fo the Macro parameter ]3 is completely replaced by the text $200.
Please note that this is a text, not a value $200.
Eventually the SB-Assembler treats that text as a value when it tries to interpret the composed program line.
Please note that the Z80 doesn't need the # symbol to represent immediate addressing mode, although it is permitted to use it in the SB-Assembler version of the Z80 Cross-Overlay.
Please note that it is perfectly legal to put the .CR Z80 line after the definition of the Macro. It is not recommended for clarity reasons, but it is done in this example to emphasize that the mnemonics inside the Macro definition are not interpreted. Outside a Macro definition you would get an error message when no appropriate Cross-Overlay was loaded.
I can think of an even weirder example using parameters. Have a look at the next program.
.CR 6502 SOURCE .EQ $40 Just any memory location MADMAC .MA labelfield,opfield,operandfield,commentfield ]1 ]2 ]3 ]4 .EM >MADMAC START,CLC,,'Clear Carry' >MADMAC ,LDA,SOURCE,'Get source' >MADMAC ,ADC,#10,'Add 10 to it' >MADMAC ,STA,SOURCE,'Save result'
This program shows an insane application of Macros. This demonstrates that the parameters are treated just as a piece of text by the expanding Macros.
All that the Macro MADMAC does is paste parameter ]1 in the label field, parameter ]2 is pasted in the instruction field, parameter ]3 is pasted in the operand field, and finally parameter ]4 is pasted in the comment field.
The list file could look something like this after assembly:
1 .CR 6502 2 3 SOURCE .EQ $40 Just any memory location 4 5 MADMAC .MA labelfield,opfield,operandfield,commentfield 6 ]1 ]2 ]3 ]4 7 .EM 8 9 >MADMAC START,CLC,,'Clear Carry' M1 START CLC Clear Carry 10 >MADMAC ,LDA,SOURCE,'Get source' M1 LDA SOURCE Get source 11 >MADMAC ,ADC,#10,'Add 10 to it' M1 ADC #10 Add 10 to it 11 >MADMAC ,STA,SOURCE,'Save result' M1 STA SOURCE Save result
Empty parameters consist of a single comma, like the label field parameters in front of the LDA, ADC and STA instructions.
Every time the SB-Assembler finds a comma when it expects to find a parameter this comma will be interpreted as an empty parameter.
Such a parameter will be replaced by no text at all.
There is one special parameter which is the parameter counter ]# .
This parameter indicates the number of parameters that were used when calling the currently expanding Macro.
You can use this parameter together with conditional directives to adapt your Macro to the total number of parameters available.
Macros may be nested endlessly.
Nesting Macros means that one Macro calls for the expansion of another Macro, which can call yet another Macro, etc.
The next example shows a 6502 program with a nested Macro. The 6502 doesn't have a decrement Accu instruction. In this example we define a decrement Accu Macro, which will be called by another Macro.
1 .CR 6502 Use the 6502 Cross-Overlay 2 3 WAIT .MA STARTVALUE 4 PHA ; Save Accu contents 5 LDA ]1 ; Load the start value 6 :LOOP >DECA ; Decrement Accu 7 BNE :LOOP ; Repeat until Accu is 0 8 PLA ; Restore Accu 9 .EM 10 11 DECA .MA 12 SEC ; Prepare C for subtraction 13 SBC #1 ; Decrement Accu by 1 14 .EM 15 16 START NOP Just an imaginary instruction 17 >WAIT 10 Wait a while M1 PHA M2 LDA #10 M3 :LOOP >DECA M1 SEC <--- Nested Macro M2 SBC #1 <--- Nested Macro M4 BNE :LOOP M5 PLA 18 RTS The rest of the program
As you can see it doesn't matter to the SB-Assembler in which order the Macros are defined. It is perfectly legal to use a Macro call to a Macro that is not defined yet while defining another Macro. Remember that nothing is interpreted during the definition of a Macro.
Please note that the line counter restarts at M1 when a new nested Macro is called. The line number continues where it left off once the nested Macro is completed.
This example also demonstrates a different purpose of Macros: Combining a few instructions into a new instruction. A subroutine would be very inefficient here.
Exit Macro Expansion
The .XM directive is specially designed to exit a Macro expansion prematurely. This exit can be either conditional or unconditional.
The exit is unconditional when no expression follows the .XM directive.
In that case the Macro definition ends immediately and the SB-Assembler continues as if the natural end of the Macro expansion was reached.
Conditional exits will abort the Macro expansion if the expression that follows the .XM directive evaluates to be true (<> 0). If the expression is false ( = 0 ), the Macro expansion continues as it normally would.
You will often find the conditional .XM directive together with the parameter counter ]# to create a "do for every parameter" structure.
The next program shows an example of the use of the conditional .XM directive together with the parameter counter.
1 REGPUSH .MA regpairlist (maximum 7) 2 PUSH ]1 ; Save 1st register pair 3 .XM ]#=1 ; Done when 1 parameter 4 PUSH ]2 ; Save 2nd register pair 5 .XM ]#=2 ; Done when 2 parameters 6 PUSH ]3 ; Save 3d register pair 7 .XM ]#=3 ; Done when 3 parameters 8 PUSH ]4 ; Save 4th register pair 9 .XM ]#=4 ; Done when 4 parameters 10 PUSH ]5 ; Save 5th register pair 11 .XM ]#=5 ; Done when 5 parameters 12 PUSH ]6 ; Save 6th register pair 13 .XM ]#=6 ; Done when 6 parameters 14 PUSH ]7 ; Save 7th register pair 15 .EM 16 17 START >REGPUSH AF,BC,HL M1 PUSH AF M2 .XM 3=1 M3 PUSH BC M4 .XM 3=2 M5 PUSH HL M6 .XM 3=3 18 19 >REGPUSH IX,IY M1 PUSH IX M2 .XM 2=1 M3 PUSH IY M4 .XM 2=2 20 NOP Rest of the program
The .XM directive is used here to stop the expansion of the Macro when all available parameters are processed.
The second example demonstrates a little variation on the example above.
The Macro has the same function, but this time it is intended for the 65C02 processor.
1 REGPUSH .MA register list (max 4) 2 PH]1 ; Save 1st register 3 .XM ]#=1 ; Done when 1 parameter 4 PH]2 ; Save 2nd register 5 .XM ]#=2 ; Done when 2 parameters 6 PH]3 ; Save 3d register 7 .XM ]#=3 ; Done when 3 parameters 8 PH]4 ; Save 4th register 9 .EM 10 11 START >REGPUSH A,X,Y,P M1 PHA M2 .XM 4=1 M3 PHX M4 .XM 4=2 M5 PHY M6 .XM 4=3 M7 PHP 12 13 >REGPUSH X,Y M1 PHX M2 .XM 2=1 M3 PHY M4 .XM 2=2 14 NOP Rest of the program
The program above clearly demonstrates that the parameters are literally treated as pieces of text.
This text can be pasted at any position you want, in this case the text will become part of the instruction.
It goes without saying that the result of the pasting procedure should generate legal code. A parameter like "SILLY EXAMPLE" would result in the mnemonic PHSILLY and operand EXAMPLE. I think it is obvious that you won't find that mnemonic in the 65C02 data sheet.