EQU/= or Symbols
[label:] equ value ;or
label = value
A standard feature of most assemblers is to provide symbols, or what we generally call variables, to aid programming. For example:
port: equ 86h
…
mvi a, 1 ;signal external device
out port
…
Symbols can be declared only in two ways:
1. Decimal, in this case the value will be in decimal
This mode allows full arithmetic and/or algebraic equations
2. Hexadecimal, in this case the value will be in hexadecimal
No arithmetic is allowed only in declaration of this kind. However, you can use these symbols for expressions in other places perfectly well.
For example, the following code is invalid:
port: equ f0 + 6 ;invalid
…
But the following is:
base: equ 80h
port: equ base + 6
…
This is a general rule of the neutrino assembler: You cannot use hexadecimal and decimal in the same expression. Another general rule: You cannot do arithmetic with hexadecimal, period. You however, can push hexadecimal into variables and then use those variables.
Labels are optional in this form of EQU
EQU statement is as follows:
variableName = value
Both are equivalent
EQU and Arrays
EQU
handles arrays exactly like constant literal arrays in other C-like languages. Constant means no modification after you declare them, or insertion/deletion of values. They are there solely for P.O.D. reasons:
arr = [4,3,2,1]
mvi a, arr[2] ;equivalent to mvi a, 3
A sharp symbol #
signifies length, that is, saying #arr
will give you the length of the array.
arr = [4,3,2,1]
mvi a, #arr ;equivalent to mvi a, 4
Some general constraints for arrays are:
1. Arrays must have atleast a single element, or the EQU
statement will fail.
2. Empty elements (just commas without values) are simply skipped over.
3. An array cannot contain of only empty elements([,,]
is invalid)
EQU and Strings
EQU
can easily handle strings. It first converts the string into an array of its ascii values and then uses that.
arr = "hello"
mvi a, arr[2] ;equivalent to mvi a, 6CH
mvi b, #arr ;equivalent to mvi b, 05H
Unlike arrays, string literal arrays cannot be used in expressions. You have to separately declare them first.
Expression Format
The expression evaluation engine is silentmatt's excellent expr-eval and supports everything that supports, with the addition of strings as arrays.
An assemble-time string output program
The following program outputs a string to port 86H
:
port = 86H ;We output to port 86H
str = "hello" ;The string to output is hello
i = 0 ;This is our counter, or index, into the string
;The following part does this:
;First it outputs the current element in the string(array)
;Then it increments the count
;this works as arithmetic/algebraic equations are fully supported
;in EQU statements *as long as* they do not contain any hexadecimal
;literals
DUP #str ;Duplicate statements length of str times
out str[i] ;Output the element
i = i + 1 ;increment index
ENDD
hlt ;and halt
This generates the following code:
+--------+-----------+----+
| Address|Instruction|Data|
+--------+-----------+----+
| 0000 | out 68H | D3|
| 0001 | | 68|
| 0002 | out 65H | D3|
| 0003 | | 65|
| 0004 | out 6CH | D3|
| 0005 | | 6C|
| 0004 | out 6CH | D3|
| 0005 | | 6C|
| 0004 | out 6FH | D3|
| 0005 | | 6F|
| 0004 | hlt | 76|
+--------+-----------+----+
Macros
Neutrino includes a powerful macro subsystem with proper label/variable mangling, general argument substitution, nested macros, macro hoisting, and, with the help of the conditional directives, recursion.
Format
The general format of a macro is:
name: macro arg1, arg2, …
locals l1, l2, l3 …
;body
/mangledLabel: ;body
…
endm
For example:
l2: macro x, y, z
locals q
mvi a, x
/atmvb: mov b, y
mov c, z
q = 15
mvi d, q
endm
l2 15, B, C
This is a nonsensical macro, but it illustrates how a macro may be used.
Passing Names
As shown in the example, if you want to pass a name to an macro, pass the name of the register in UPPERCASE. This serves as an hint to the assembler to not parse it as an hexadecimal value. However, if used in a place where a hexadecimal value is to be used, it can and will be converted into one.
Local Label Mangling
Notice the label /atmvb:
in the previous example. It is a local label. As macros are just substituted, two conflicting labels, from say two invocations of the same macro, may overwrite each other and thus only the second label will be used in the final assembly. To solve this issue, macros have the ability to mangle
labels.
A mangled label is declared (within a macro) as /labelname
, and is replaced with /labelName_indLocInstNoXX
where XX
is the current index of the macro. The index of a macro is initialized by 0 and increases by one every time a macro is called. However, this property is internal and cannot be referenced. It is recommended that you do not use labels of this form in your assembly code, to avoid conflicts.
The /
is a hint to create a local. It is not used while actually using the label, that is, a local label declared /abc
will be used as abc
.
Locals
The locals directive allows declaration of local symbols, that is, symbols that have their names mangled. This is useful when you do not want to pollute the global namespace with symbol names.
Macro Hoisting
Macros in neutrino are hoisted, that is, a macro can be defined anywhere in code, and be called from everywhere in the code, even before the part in which the macro was defined. The same applies to labels, but not symbols.
Nesting Macros
Macros can be nested in one another. In addition, any macro may call any other macro, or declare another macro. However, it is not possible to conditionally define macros.
Recursive Macros
Macros can call themselves, and thus allow for primitive recursion:
ssum: macro n
if n > 1
ssum n - 1
endif
adi n
endm
ssum 5
hlt
Note that the assembler will happily keep recurring till it runs out of memory. It is up to the user to control how deep the recursion is and the assembler will *not* warn you if you do not control how deep your recursion goes. An infinite recursion will crash the assembler