please dont rip this site

MassMind.org Newsletter 2004/03
!!! MACRO MASTERS !!!

SX programmers are missing a bet: They don't use macros enough. And they don't use them because they are not expained well.

Well, enough! Prepare to become a MACRO MASTER! Here we go...

Macros are Programmers

Macros are code that is executed by the compiler rather than by the processor. E.g. by SASM rather than by the SX. Macros are programs that generate code for the processor just like you generate code for the processor. You write the MACRO program so that it will write code for you instead of you haveing to write the code each time yourself. Macros are like a "mini-me" that I have taught how to write code. The programming language that you write macros in, are the compiler directives like IFDEF, REPT, EQU, and so on. If you don't understand at least a little about those, please read the SASM documentation before you try for this tutorial.

Symbols ARE numbers

All Symbols are nothing more than a name for a number. In SASM, there are no string variables (except some macro parameters) so everything boils down to a 32 bit number. You can see them in the listing on the left side just right of the listing line number. The following source lines:

	;Pretend to be for an SX28
	CPU_Pins = 28

(which happen to be at lines 29 and 30 in the source file) turn into this in the listing file:

    29			;Pretend to be for an SX28
    30  =0000001C	CPU_Pins = 28

Notice that 1C is hex for 28 and that 8 hex digits are 32 bits total (4 bits per digit). Here are some more examples:

     1  =00000005           what = RA
     2  =00000004           what = FSR
     3  =00000002           what = PC
     4  =00000002           what = what

I've set a variable called "what" to different values so that the result will appear after the line number in the listing file. I think of it as asking "what is x" but it is actually assigning the value of x to the variable what and showing that value in the listing.

Here are some ways to define a symbol (called sym here) and set it to a number:

sym			;defines sym as a label to the current address.
sym	EQU	value	;permanently sets sym to value. sym can't be changed.
sym	SET	value	;initially sets sym to value. sym can be changed.
sym	= 	value	;initially sets sym to value in plain english. sym can be changed.

Since the sym = value form reads more like English, and does just what people expect it to do, this is the preferred style. Just to be clear on one point, if the above code were actually compiled, the EQU line would cause an error because it would be trying to change sym from being a label for a program address to being a general symbol. And if it got past that, the SET line would cause an error because the EQU line tells SASM not to let anything change sym. That can be... well once it a while someone, somewhere might find a need for that. In general just remember:

  sym = value   ;GOOD CODE!

Easy and simple. Just like most every other programming language. But don't try to make value be "ABC" it MUST be 123 (or something that ends up being 123 like 100 + 20 + 3). One apparent exception is single character "strings" in single quotes. These actually evaluate to their ASCII value. So what = 'A' sets it to 41. And if there are more than one character, only the first is used. what = 'ABC' still results in a 41 in what.

Labels

Now, Labels are special symbols. Labels are created whenever something, that isn't recognized as something else, is put in the far left column. e.g. no tab or space before it and it isn't a reserved word or already defined. They can have a colon after them, or not. Colons on the end don't matter. Ah! But colons on the beginning, that is a very interesting trick! And critical to the effective use of Macros.

If there is no colon on the beginning, the label is global It must be unique and will be recognized through out the file. With a colon on the beginning, the label becomes local.

One nice thing about local labels is that you can re-use the same local name over and over in your program. The only issue is that before you re-use a local label, there has to be a new global label. For example:

Starting_Label

 ...code...

:A_Local_Label

 ...code...

Middle_Label

 ...code...

:A_Local_Label

"Starting_Label" and "Middle_Label" are both global labels. The first time ":A_Local_Label" is declared, the assembler actually creates the internal name "Starting_Label:A_Local_Label". The next time it is declared, the assembler creates the internal name "Middle_Label:A_Local_Label". If there wasn't another global label declared before declaring the same local label, the assembler would have generated the same internal name twice, resulting an error.

However, you can also manually refer to a <specific> local label by putting the same internal name SASM generates in your code. If you wrote the following:

   jmp Starting_Label:A_Local_Label

<anywhere> in your code, the assembler would know which version of "A_Local_Label" to jump to because of the use of "Starting_Label:" in front.

Check out the following little test file:

	org 5		;move to the 5th program address.
sym:			;define a label called sym. This is a global label

	org 10		;move to the 10th program address.
:sym			;define a label called :sym. This is local to sym:

	what = sym	;do some assignments to a symbol called what
	what = :sym	;so that we can find out what values these labels have.
	what = sym:sym	;There shouldn't be any mystery at this point.

	org 15		;move up to word 15.
lab			;define another global label called lab

	org 20		;move to 20
:sym			;define another local label called :sym

	what = lab	;and again, see what values these labels have.
	what = :sym	;What will this one be?
	what = sym:sym

Here is the result in the list file. Notice the HEX values on the left. Don't you wish I had specified the org's in hex? Or used 1,2,3,4? Just trying to point out that these can be confusing. Look at the result:

     1  =00000005       org 5
     2  =00000005       sym:
     3                  
     4  =0000000A       org 10	;10 decimal is 0A hex
     5  =0000000A       :sym
     6                  
     7  =00000005               what = sym
     8  =0000000A               what = :sym
     9  =0000000A               what = sym:sym
    10                  
    11  =0000000F       org 15	;15 decimal is 0F hex
    12  =0000000F       lab
    13                  
    14  =00000014       org 20	;20 decimal is 14 hex
    15                  
    16  =00000014       :sym
    17                  
    18  =0000000F               what = lab
    19  =00000014               what = :sym
    20  =0000000A               what = sym:sym

Notice that:

Also notice that I indented what. Had I not, it would have redefined the current global label. Line 8 would fail because it would be an attempt to re-define the label what. Also, in line 8, :sym would have been undefined. Do you see why?

The subtle differences between labels and regular symbols, and between global, local, equates, sets, and assigns are critical in SASM macro programming.

If you make a symbol with a: value?



reassign?
 
Can you
change the
value later?
global?
Can it be
reffered to
from any-
where?
re-localize?
 
Does it
start a new
local space?
1 text in the left column, no colon Address NO YES YES
2 colon then text in the left column Address NO LOCAL NO
3 text EQU value Value NO YES YES
4  text EQU value Value NO YES NO
5 text = value Value YES YES YES
6  text = value Value YES YES NO
7 :text = value Value YES LOCAL NO

Be sure to catch the difference between 3 and 4 and between 5 and 6. That ONE LITTLE SPACE changes a lot. Be clear in your files, use a tab rather than a space.

Rather than this: I recommend you do this:

text1 = value text2 EQU value text3
	
text1 = value text2 EQU value text3

MACRO locals

There is another sort of a local label when it comes to macros, and ONLY in macros, which do a tricky, but needed, thing inside the compiler. These must be declared after the keyword LOCAL at the start of the macro, right after the name MACRO parmsline. Each local label is assigned a value that contains a number which is incremented each time the macro is invoked. An example may explain it better, but please don't get hung up on trying to understand everything this macro is doing, just look at the macro and then look at the result and see how the value of the lable "loc" is assigned:

tst MACRO
    LOCAL loc		;loc will only be "known" within the macro
    jmp @loc		;generate a processor instruction to long jump to whatever loc turns out to be
    IF ($ & 1 == 0)	;$ is the PC address where the next instruction will be. We are testing if it is even or odd.
    nop			;generate a processor instruction to do nothing.
        ENDIF		;but only if it would be placed on an even address in program memory.
loc 			;loc will now have the value of this address
    nop			; where we will put another nop.
    ENDM		;and that is the end of the macro

    org 25		;start at program address 25

    tst a		;invode the tst macro with a parameter of 'a'
			; please note that the parameters are ignored by the macro
    tst b		; I just added them to allow us to differentiate between the
			; three different invocations of the tst macro.
    tst c

And here is the result of that little test:

    15  =00000019           org 25
    16                  
    17                      tst a
    18  0019  0A1B   m      jmp ??0000
    19               m      IF ($ & 1 == 0)
    20  001A  0000   m      nop
    21               m          ENDIF
    22  =0000001B    m  ??0000  
    23  001B  0000   m      nop
    25                  
    26                      tst b
    27  001C  0A1D   m      jmp ??0001
    28               m      IF ($ & 1 == 0)
    29               m      nop
    30               m          ENDIF
    31  =0000001D    m  ??0001  
    32  001D  0000   m      nop
    34                  
    35                      tst c
    36  001E  0A1F   m      jmp ??0002
    37               m      IF ($ & 1 == 0)
    38               m      nop
    39               m          ENDIF
    40  =0000001F    m  ??0002  
    41  001F  0000   m      nop
    43                  

First, notice that we don't really know how much space there will be between the jmp and the target of the jump (the label defined by loc) each time the macro is used. tst a has a nop between the jmp and loc, but tst b doesn't. No matter, SASM still figures out where to go. Sigh, I guess people wont be impressed by that unless they have seen error messages about "undefined forward references" before.

Notice also that in the list file, you can't find the word loc anywhere. It was replaced by ??0000 in tst a, by ??0001 in tst b and by ??0002 in tst c. Those ?? things ARE valid labels, just like loop, main, or any other lable you might put in your code. They just don't exist until the macro gets invoked.

Parameters

Parameters are passed to Macros in SASM by name unless they are preceded by a "?" and then they are passed by value. What the heck difference does it make? Well, not much usually, but it does allow for some cool tricks and it is as close as you get to strings in SASM.

The next little macro has been written, in various forms, by many different SASM users over time. It doesn't work as expected, but what they are trying to do is only include the page instruction when a jump will cross a page. After we look at it, we can discuss why it isn't that useful but more importantly, show the trap it leads people into and how to avoid it.

_jmp	MACRO address
noexpand				;don't put all this decision making stuff in the list file
	IF address/$100 == $/$100	;decide if the address is over a page from where we are now ($)
expand					;do show the result of our decision: 
	jmp address			;We don't need to page (Actually not a valid assumption)
noexpand				;more decision makeing behind the scenes
	ELSE				;the other possibility is that:
expand					;and we should show this:
	page address			;we really do need to page (Probably the best assumption)
	jmp address			; before we jump
noexpand				;hide the closing 
		ENDIF			;done making decisions
	ENDM

This macro assumes that the page bits are all set for the address you are currently at, and then decides if you need to page or not page depending on if the address is in this page or another. I don't use this because in practice, it is just about useless. You can't be sure where your code will end up and if it rolled over the top of one page into the next just before you call this macro, the page bits didn't get changed, aren't set to the local page, and you are not going to reset them when you needed to. Anyway, I'm using it to illustrate another issue.

What happens if you call it with $ or $ plus or minus some value as the address? Lets say that your trying to make a jump to an address that is just in the next page and you know it will be 10 instructions ahead of where you are... So you do a _jmp $+10. The macro sees that the address is in the next page, compiles the page $+10 and then compiles the jmp $+10. The problem is that $ changed between the page and the jmp. And it changed because address is still "$+10" and NOT the numerical value of the target. You need to call it with _jmp ?($+10) because that causes the target to be evaluated at the beginning. Actually, no; what you really need is to add a line that goes myAddress = address as the first line of the macro and then use myAddress in the rest of the macro. Why? Because then the parameter gets evaluated before it gets used anywhere in the macro. Also, because myAddress is just a local variable, you can change it if you need to pretend that your macro was called with a different value... ok, its a small point, but it comes in handy some times. The corrected macro looks like this, and it doesn't care what you call it with, it will always work correctly.

And this is how you should handle parameters: Name them after the MACRO (for more than one, separate with commas) then assign them to a local variable so you know they have been evaluated, and so you can change them if needed. Our previous __jmp macro would have been better like this:

_jmp	MACRO address
myAddress = address  	 ;GOOD CODE!
noexpand
	IF myAddress/$100 == $/$100
expand
	jmp myAddress
noexpand
	ELSE
expand
	jmp @myAddress   ;GOOD CODE!
noexpand
		ENDIF
	ENDM

Keep in mind, this macro is probably useless. Also, note that I changed it from a separate page and jmp to jmp @myAddress. This is a very good idea for SASM code in general, but I wanted to illustrate another reason to use this layout: You can cause the macro parameter to be passed by value by calling the macro with _jmp ?$ as we know, but if the macro compiles the jump with jmp @\1  or jmp address it will cause an "Symbol <#> is not defined" error; instead, you must compile the jump with jmp @$??\1 (see the SASM manual section 4.4.2 "Token Pasting" and more below).

Keep it simple right? Just define a local var in the macro, set the var to your parameter name (or \1, etc..) before any code is compiled and then using the var rather than the parm when the jump is compiled.

Conditional compilation

Conditionals are a way of makeing SASM into a sort of an expert system: They allow you to put your knowledge into the compiler for your own use in the future. Why? Because no matter how smart you are, you just can't, and shouldn't remember all this junk. Once you read up on how many ports each SX has, and how that changes the first available global file register, you should never have to remember that in your code. You can just set a compiler variable with the number of pins that CPU has (or some other way of identifying the chip) and then let the compiler do all the work of placing your file register variables.

IF CpuPins > 18
 IF CpuPins > 28
GPRegOrg	=	$0A	;$0A to $0F - limited to 6 bytes - global
 ELSE
GPRegOrg	=	8	;$08 to $0F - limited to 8 bytes - global
 ENDIF
ELSE
GPRegOrg	=	7	;$07 to $0F - limited to 9 bytes - global
ENDIF

;GLOBAL VARIABLES ---------------------------------------------
			org	GPRegOrg
Temp			ds	1
flags			ds	1	;general flag register
RS232Rx_flag         	=	flags.0
RS232RxFrameErr		=	flags.1
TimerFlag		=	flags.2	;timer rollover flag
Timers			=	$	;timer
TimerAccL		ds	1	;timer accumulator low
TimerAccH		ds	1	;timer accumulator high
TimerAccT		ds	1	;timer accumulator top
		watch TimerFlag, 1, ubin
		watch TimerAccL, 24, uhex
StackPtr		ds	1	;Stack
		watch StackPtr,8,UHEX

Count			ds	1

IF $ > $10
 ERROR 'out of gobal variable space'
 ENDIF

Notice that the above code will compile just fine on an SX18 or 28, but will generate a very nice error message when you try to compile it on an SX48 or 52. You don't have to remember that, and you don't have to track down why sometimes count is getting incremented and other times it isn't.

One of the best uses for conditional compilation that I personally have used is to develop a set of macros using conditions to automatically compile the correct code to compare registers with literals, registers with registers, W with literals, etc... so that I don't have to remember how all that works.  AND they manage bank selects, special cases and paging. Nothing to forget. I'll talk about that more latter on.

Token Pasties

Token pasting allows you to combine macro parameter values into new and exciting strings. This is the only string processing in SASM.

The SASM manual is a total waste on this one. First, the C<token??token> form is not correct. The actual form is just token??token or token??text or even this??token??that where token was one of the formal parameters of the macro. text, this, and that are just text.

Token pasting only pastes tokens to text. You can't paste local lables, variables, or even the VALUE of tokens. Just the tokens themselves. In this case, tokens really means the value of the parameters. So for example, if we want to paste an array name and index together to store values in calculated postions, we could try

ary	MACRO	name, ptr, val
	myName = name
	myPtr = ptr
	myVal = val
	myName??_??myPtr = myVal	
	ENDM
	

but calling this with, for example, ary op, 1, 2 will get us myName_myPtr = myVal rather than the desired op_1 = 2. We could try:

ary	MACRO	name, ptr, val
	name??_??ptr = val	
	ENDM

and if we called that with ary op, 1, 2 it would, indeed, compile op_1 = 2. But if we try to calculate the index via ptr = 1, and call it with ary op, ptr, 2 it will make op_ptr = 2 rather than op_1 = 2.  The best we can manage in this situation is to use the standard old \# notation and add some logic to allow us to also retrieve values:

     2                  ary MACRO
     3                  NOEXPAND
     9                      IF \0 == 3		;if they give us three parameters
    10                  EXPAND			;then they must want to assign a value to an index of the array
    11                          \1??_??\2 = \3
    12                  NOEXPAND
    13                      ELSE		;otherwise, get an index of the array back out.
    14                  EXPAND
    15                          \1 = ??\1??_??\2
    16                  NOEXPAND
    17                          ENDIF
    18                      ENDM
    19                  
    20                      ary op, 1, 10	;literal index 1 of the "op" array is set to 10 (0A hex)
    29  =0000000A    m          op_1 = 10
    37                  
    38  =00000002           ptr = 2		;computed index 2
    39                      ary op, ptr, 11	; of the op array is set to 11? OOPS!
    48  =0000000B    m          op_ptr = 11
    56                      ary op, ?(ptr), 11	;but if we convert the name "ptr" to the value of ptr
    65  =0000000B    m          op_2 = 11
    73  =00000001           ptr = ptr - 1	;and we really can compute indexes. Lets go back...
    74                      ary op, ?(ptr)	;...and get that index 1 value back
    87  =0000000A    m          op = op_1
    91  =0000000A           what = op		;and there it is, safe and sound.

Here is the actual finished macro with a test to verify that the index is being sent as a number.

ary	MACRO
	LOCAL tst
NOEXPAND
	tst = ?\2?
	IF tst > '9' OR tst < '0'
		ERROR "USAGE: ary name, pointer, value WHERE: pointer is a number (use ary name, ?(ptr) ) and value is an optional value"
		ENDIF

	IF \0 == 3
EXPAND
		\1??_??\2 = \3
NOEXPAND
	ELSE
EXPAND
		\1 = ??\1??_??\2
NOEXPAND
		ENDIF
	ENDM

Callable

The "callable" macro takes care of those pesky "Address xxx is not within lower half of memory page" errors. If you haven't seen that yet, you will. Try this:

	org $300
cantcallme
	nop
	ret

	org 0
	call @cantcallme

Welcome to the club! Since the call instuction only has 8 bits available for the target addres (the address of the subroutine you are trying to call) and there are only 3 page bits, the 12 bit wide PC needs another bit from somewhere. The designers of the 16C5x processors, many years ago, solved the problem by filling in bit 8 of the PC (bit number 9) with a zero when you do a call. As a result, if a subroutine is in the upper half of a 512 word page, where bit 8 of the address is 1, you can't call it. Actually, the same thing applys to

 LowHalfPage = $
	org $100
 HighHalfPage = $


callable MACRO Name
	LOCAL 
NOEXPAND
    IF ($ & $100) != 0
		IFNDEF LowHalfPage
			ERROR "Please add 'LowHalfPage=$, org $100, HighHalfPage=$' at the start of the program."
			ENDIF
		IFNDEF HighHalfPage
			ERROR "Please set HighHalfPage to the end of the first LowHalfPage area"
			ENDIF
		IF LowHalfPage >= HighHalfPage
			error "Out of LowHalfPage space"
			ENDIF
EXPAND
;Our current location cant be called, we need to compile a jmp to here 
;from a location that CAN be called
 Name??:code = $		;don't forget where we came from
	org LowHalfPage		;move to a location we saved in low half page space
Name				;set a global label for the new routine here
	jmp @Name??:code	;to the actual code of the (now) callable routine
	LowHalfPage = $		;point to the next space in the table for next time
	org Name??:code		;move back to where we started
;ready to compile the routines code
NOEXPAND
	ELSE
EXPAND
;Our current location is callable, no need to do anything special
Name					;set a global label for the new routine here
NOEXPAND
		ENDIF	
    ENDM

;lets try it!
	org $200	;the lower half of the second page, usually no problem
	callable callmeup
	nop		;just a dummy subroutine, put whatever here
	ret

	org $300	;the UPPER half of the second page, ALWAYS a problem
	callable callmetoo
	nop
	ret

	org $400
	call @callmeup
	call @callmetoo

Here is part of the listing file we get from this:

    60                  ;lets try it!
    61  =00000200           org $200    ;the lower half of the second page, usually no problem
    62                      callable callmeup
    87               m  ;Our current location is callable, no need to do anything special
    88  =00000200    m  callmeup                ;set a global label for the new routine here
    92  0200  0000          nop			;just a dummy subroutine, put whatever here
    93  0201  000C          ret
    94                  
    95  =00000300           org $300    ;the UPPER half of the second page, ALWAYS a problem
    96                      callable callmetoo
   109               m  ;Our current location cant be called, we need to compile a jmp to here 
   110               m  ;from a location that CAN be called
   111  =00000300    m   callmetoo:code = $     ;don't forget where we came from
   112  =00000000    m      org LowHalfPage     ;move to a location we saved in low half page space
   113  =00000000    m  callmetoo               ;set a global label for the new routine here
   114  0000  0011   m      jmp @callmetoo:code ;to the actual code of the (now) callable routine
        0001  0B00
   115  =00000002    m      LowHalfPage = $     ;point to the next space in the table for next time
   116  =00000300    m      org callmetoo:code  ;move back to where we started
   117               m  ;ready to compile the routines code
   126  0300  0000          nop
   127  0301  000C          ret
   128                  
   129  =00000400           org $400
   130  0400  0011          call @callmeup
        0401  0900
   131  0402  0010          call @callmetoo
        0403  0900

Compile on demand

This next macro is nothing more than a container for a routine. It just contains a subroutine (could be any subroutine) such as a 16bit add or subtract, or a delay routine for example. The interesting thing is that it is designed to be placed in an include file and forgotten. If you call the macro in the main file, the first time, it will compile in a copy of the subroutine right at that point, with a jump around the subroutine for your main code, and then a call to the subroutine afterwards. Why would you want to do that? Because the second and following time, it only compiles in a call to the subroutine that was placed in the code the first time.

So why not just put the subroutine in like normal? Before the main code? Because with this macro, if you never use it, it takes up no space. If you use it once, it takes up only a few more words of code space than it would if it were hand coded, and then each additional use takes up the standard subroutine call. Now, you can define this macro, put it in an include file, and forget about it. It will not contribute to code bloat in your programs unless it is needed. As you continue to define more and more routines with this method, SASM grows from an assembler to a full featured language with your own library of keywords.

 invc:n = 0
invc	MACRO
	LOCAL around
NOEXPAND
    invc:n = invc:n + 1
    IF invc:n == 1
EXPAND
	jmp @around
	callable invc:code
	    ENDIF			
EXPAND
;actual code goes here
	ret
around
NOEXPAND
	ENDIF
EXPAND
	call @invc:code
NOEXPAND
    ENDM

	org $300
 invc
 invc

Compiling Conditionals

Earlier we talked about conditional compilation. The IF, IFDEF, IFNDEF, ELSE, ENDIF commands control SASM and allow us to get different bits of code out the door depending on our current situation. As I said, its a way of makeing SASM into a sort of an expert system and puts your knowledge into the compiler for your own use in the future. MiniMe compiles my code.

But what about putting decision making ability like that into the SX itself? Doing accruate and reliable conditionals in assembly language is one of the hardest tasts to master. If you haven't messed with comparison in assembly, take an asprin before you start, then read
http://www.sxlist.com/techref/ubicom/lib/flow/compcon_sx.htm
Or better yet, just use my macros from
http://www.sxlist.com/techref/ubicom/sasmcond.src


file: /Techref/new/letter/news0403.htm, 32KB, , updated: 2012/10/23 15:55, local time: 2024/12/23 15:14,
TOP NEW HELP FIND: 
3.145.2.6:LOG IN

 ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF="http://massmind.org/techref/new/letter/news0403.htm"> MassMind.org Newsletter 2004/03</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type a nice message (short messages are blocked as spam) in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.


Link? Put it here: 
if you want a response, please enter your email address: 
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

 

Welcome to massmind.org!

 

Welcome to massmind.org!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  .