2253 lines
85 KiB
Plaintext
2253 lines
85 KiB
Plaintext
|
A Proposed Assembly Language Syntax For 65c816 Assemblers
|
|||
|
by Randall Hyde
|
|||
|
|
|||
|
|
|||
|
This is a proposed standard for 65c816 assembly language. The
|
|||
|
proposed standard comes in three levels: subset, full, and extended. The
|
|||
|
subset standard is intended for simple (or inexpensive) products,
|
|||
|
particularly those aimed at beginning 65c816 assembly language programmers.
|
|||
|
The full standard is the focus of this proposal. An assembler meeting the
|
|||
|
full level adopts all of the requirements outlined in this paper. The
|
|||
|
extended level is a mechanism whereby a vendor can claim full compliance
|
|||
|
with the standard and point out that there are extensions as well. An
|
|||
|
assembler cannot claim extended level compliance unless it also complies with
|
|||
|
the full standard. An assembler, no matter how many extensions are
|
|||
|
incorporated, will have to claim subset level unless the full standard is
|
|||
|
supported. This ensures that programmers who do not use any assembler
|
|||
|
extensions can assemble their programs on any assembler meeting the full or
|
|||
|
extended compliance levels.
|
|||
|
|
|||
|
In addition to the items required for compliance, this proposal
|
|||
|
suggests several extensions in the interests of compatibility with existing
|
|||
|
65c816 assemblers. These recommendations are not required for full
|
|||
|
compliance with the standard, they're included in this proposal as suggestions
|
|||
|
to help make conversion of existing programs easier. The suggestions are
|
|||
|
presented in two levels: recommended and optional. Recommended items should
|
|||
|
be present in any decent 65c816 package. Inclusion of the optional items
|
|||
|
is discouraged (since there are other ways to accomplish the same operation
|
|||
|
within the confines of the standard) but may be included in the assembler
|
|||
|
at the vendor's discretion to help alleviate conversion problems.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
65c816 Instruction Mnemonics
|
|||
|
----------------------------
|
|||
|
|
|||
|
|
|||
|
All of the following mnemonics are required at the subset, full,
|
|||
|
and extended standard levels.
|
|||
|
|
|||
|
The following mnemonics handle the basic 65c816 instruction set:
|
|||
|
|
|||
|
ADC - add with carry
|
|||
|
AND - logical AND
|
|||
|
BCC - branch if carry clear
|
|||
|
BCS - branch if carry set
|
|||
|
BEQ - branch if equal
|
|||
|
BIT - bit test
|
|||
|
BMI - branch if minus
|
|||
|
BNE - branch if not equal
|
|||
|
BPL - branch if plus
|
|||
|
BRA - branch always
|
|||
|
BRK - break point instruction
|
|||
|
BVC - branch if overflow clear
|
|||
|
BVS - branch if overflow set
|
|||
|
CLC - clear the carry flag
|
|||
|
CLD - clear the decimal flag
|
|||
|
CLI - clear the interrupt flag
|
|||
|
CLP - clear bits in P
|
|||
|
CLR - store a zero into memory
|
|||
|
CMP - compare accumulator
|
|||
|
CPX - compare x register
|
|||
|
CPY - compare y register
|
|||
|
CSP - call system procedure
|
|||
|
DEC - decrement acc or memory
|
|||
|
DEX - decrement x register
|
|||
|
DEY - decrement y register
|
|||
|
EOR - exclusive-or accumulator
|
|||
|
HLT - halt (stop) the clock
|
|||
|
INC - increment acc or memory
|
|||
|
INX - increment x register
|
|||
|
INY - increment y register
|
|||
|
JMP - jump to new location
|
|||
|
JSR - jump to subroutine
|
|||
|
LDA - load accumulator
|
|||
|
LDX - load x register
|
|||
|
LDY - load y register
|
|||
|
MVN - block move (decrement)
|
|||
|
MVP - block move (increment)
|
|||
|
NOP - no operation
|
|||
|
ORA - logical or accumulator
|
|||
|
PHA - push accumulator
|
|||
|
PHP - push p
|
|||
|
PHX - push x register
|
|||
|
PHY - push y register
|
|||
|
PLA - pop accumulator
|
|||
|
PLP - pop p
|
|||
|
PLX - pop x register
|
|||
|
PLY - pop y register
|
|||
|
PSH - push operand
|
|||
|
PUL - pop operand
|
|||
|
RET - return from subroutine
|
|||
|
ROL - rotate left acc/mem
|
|||
|
ROR - rotate right acc/mem
|
|||
|
RTI - return from interrupt
|
|||
|
RTL - return from long subroutine
|
|||
|
RTS - return from short subroutine
|
|||
|
SBC - subtract with carry
|
|||
|
SED - set decimal flag
|
|||
|
SEI - set interrupt flag
|
|||
|
SEP - set bits in P
|
|||
|
SHL - shift left acc/mem
|
|||
|
SHR - shift right acc/mem
|
|||
|
STA - store accumulator
|
|||
|
STX - store x register
|
|||
|
STY - store y register
|
|||
|
SWA - swap accumulator halves
|
|||
|
TAD - transfer acc to D
|
|||
|
TAS - transfer acc to S
|
|||
|
TAX - transfer acc to x
|
|||
|
TAY - transfer acc to y
|
|||
|
TCB - test and clear bit
|
|||
|
TDA - transfer D to acc
|
|||
|
TSA - transfer S to acc
|
|||
|
TSB - test and set bit
|
|||
|
TSX - transfer S to X
|
|||
|
TXA - transfer x to acc
|
|||
|
TXS - transfer x to S
|
|||
|
TXY - transfer x to y
|
|||
|
TYA - transfer y to acc
|
|||
|
TYX - transfer y to x
|
|||
|
WAI - wait for interrupt
|
|||
|
XCE - exchange carry with emulation bit
|
|||
|
|
|||
|
Comments:
|
|||
|
|
|||
|
CLP replaces REP in the original 65c816 instruction set, since CLP
|
|||
|
is a tad more consistent with the original 6502 instruction set. See
|
|||
|
"recommended options" for the status of REP. CLR replaces the STZ
|
|||
|
instruction. Since STA, STX, and STY are used to store 65c816 registers,
|
|||
|
STZ seems to imply that there is a Z register. Using CLR (clear) eliminates
|
|||
|
any confusion. CSP (call system procedure) replaces the COP mnemonic. COP
|
|||
|
was little more than a software interrupt in both intent and implementation.
|
|||
|
CSP helps make this usage a little clearer. HLT replaces the STP mnemonic.
|
|||
|
STP, like the STZ mnemonic, implies that the P register is being stored
|
|||
|
somewhere. HLT (for halt) is just as obvious as "stop the clock" yet it
|
|||
|
doesn't have the same "look and feel" as a store instruction. JML and JSL
|
|||
|
are not really required by the new standard; but see recommended options
|
|||
|
concerning these two instructions. Most of the new 65c816 push and pull
|
|||
|
instructions have been collapsed into two instructions: PSH and PUL.
|
|||
|
|
|||
|
PEA label becomes PSH #label
|
|||
|
PEI (label) becomes PSH label
|
|||
|
PER label becomes PSH @label
|
|||
|
PHB becomes PSH DBR
|
|||
|
PHD becomes PSH D
|
|||
|
PHK becomes PSH PBR
|
|||
|
|
|||
|
PLB becomes PUL DBR
|
|||
|
PLD becomes PUL D
|
|||
|
|
|||
|
These mnemonics are more in line with the original design of the 6502
|
|||
|
instruction set whereby the mnemonic specifies the operation and the operand
|
|||
|
specifies the addressing mode and address. The RET instruction gets converted
|
|||
|
to RTS or RTL, depending on the type of subroutine being declared. RTS and
|
|||
|
RTL still exist in order to force a short or long return. SHL and SHR (shift
|
|||
|
left and shift right) are used instead of ASL and LSR. The 6500 family has
|
|||
|
NEVER supported an arithmetic shift left instruction. The operation performed
|
|||
|
by the ASL mnemonic is really a logical shift left. To simplify matters, SHL
|
|||
|
and SHR are used to specify shift left and shift right. SWA (swap accumulator
|
|||
|
halves) is used instead of XBA. Since this is the only instruction that
|
|||
|
references the "B" accumulator, there's no valid reason for even treating
|
|||
|
the accumulator as two distinct entities (this is just a carry-over from the
|
|||
|
6800 MPU). Likewise, since the eight-bit accumulator cannot be distinguished
|
|||
|
from the 16-bit accumulator on an instruction by instruction basis (it depends
|
|||
|
on the setting of the M bit in the P register), the accumulator should always
|
|||
|
be referred to as A, regardless of whether the CPU is in the eight or sixteen
|
|||
|
bit mode. Therefore, instructions like TCD, TCS, TDC, and TSC should be
|
|||
|
replaced by TAD, TAS, TDA, and TSA. For more info on these new mnemonics,
|
|||
|
see the section on "recommended options".
|
|||
|
|
|||
|
|
|||
|
Built-in Macros
|
|||
|
---------------
|
|||
|
|
|||
|
The following instructions actually generate one or more instructions.
|
|||
|
They are not required at the subset level, but are required at the full and
|
|||
|
extended levels.
|
|||
|
|
|||
|
|
|||
|
ADD - emits CLC then ADC
|
|||
|
BFL - emits BEQ (branch if false)
|
|||
|
BGE - emits BCS
|
|||
|
BLT - emits BCC
|
|||
|
BTR - emits BNE (branch if true)
|
|||
|
BSR - emits PER *+2 then BRA (short) or PER *+3 then BRL (long)
|
|||
|
SUB - emits SEC then SBC
|
|||
|
|
|||
|
|
|||
|
Recommended Options
|
|||
|
-------------------
|
|||
|
|
|||
|
The following mnemonics are aliases of existing instructions. The
|
|||
|
(proposed) standard recommends that the assembler support these mnemonics,
|
|||
|
mainly to provide compatibility with older source code, but does not
|
|||
|
recommend their use in new programs. Some (or all) of these items may be
|
|||
|
removed from the recommended list in future revisions of the standard. None
|
|||
|
of these recommended items need be present at the subset level. If these
|
|||
|
are the only extensions over and above the full syntax, the assembler
|
|||
|
CANNOT claim to be an extended level assembler.
|
|||
|
|
|||
|
ASL BRL COP JML JSL LSR PEA PEI PER
|
|||
|
PHB PHK PHK PLB PLD REP TCD TCS TDC
|
|||
|
TSC TRB WDM XBA
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Symbols, Constants, and Other Items
|
|||
|
-----------------------------------
|
|||
|
|
|||
|
Symbols may contain any reasonable number of characters at the full
|
|||
|
level. At the subset compliance level, at least 16 characters should be
|
|||
|
supported and 32 is recommeded. A "reasonable" number of characters should
|
|||
|
be at least 64 if the implementor needs a maximum value.
|
|||
|
|
|||
|
Symbols must begin with an alphabetic character and may contain
|
|||
|
(only) the following symbols: A-Z, a-z, 0-9, "_", "$", and "!". The
|
|||
|
assembler must be capable of treating upper and lower case alphabetic
|
|||
|
characters identically. Note that this does not disallow an assembler from
|
|||
|
allowing the programmer to choose that upper and lower case be distinct, it
|
|||
|
simply requires that in the default case, upper and lower case characters
|
|||
|
are treated identically. Note that the standard does not require case
|
|||
|
sensitivity in the assembler (and, in fact, recommends against it).
|
|||
|
Therefore, anyone foolish enough (for many, many reasons) to create variables
|
|||
|
that differ only in the case of the letters they contain is risking port-
|
|||
|
ability problems (as well as maintenence, readability, and other problems).
|
|||
|
|
|||
|
The following symbols are reserved and may not be redefined within
|
|||
|
the program:
|
|||
|
|
|||
|
A, X, Y, S, DBR, PBR, D, M, P
|
|||
|
|
|||
|
Nor may these symbol appear as fields to a record or type definition (which
|
|||
|
will be described later).
|
|||
|
|
|||
|
|
|||
|
Constants take six different forms: character constants, string
|
|||
|
constants, binary constants, decimal constants, hexadecimal constants and
|
|||
|
set constants.
|
|||
|
|
|||
|
Character constants are created by surrounding a single character by
|
|||
|
a pair of apostrophes or quotation marks, e.g., "s", "a", '$', and 'p'. If
|
|||
|
the character is surrounded by apostrophes, then the ASCII code for that
|
|||
|
character WITH THE H.O. BIT CLEAR will be used. If the quotation marks are
|
|||
|
used, then the ASCII code for the character WITH THE H.O. BIT SET will be
|
|||
|
used. If you need to represent the apostrophe with the H.O. bit clear or a
|
|||
|
quotation mark with the H.O. bit set, simply double up the characters, e.g.,
|
|||
|
|
|||
|
'''' - emits a single apostrophe.
|
|||
|
"""" - emits a single quotation mark.
|
|||
|
|
|||
|
String constants are generated by placing a sequence of two or more
|
|||
|
characters within a pair of apostrophes or quotation marks. The choice of
|
|||
|
apostrophe or quotation mark controls the H.O. bit, as for character
|
|||
|
constants. Likewise, to place an apostrophe or quote within a string
|
|||
|
delimited by the same character, just double up the apostrophe or quotation
|
|||
|
mark:
|
|||
|
|
|||
|
'This isn''t bad!' - generates --This isn't bad--
|
|||
|
"He said ""Hello""" - generates --He said "Hello"--
|
|||
|
|
|||
|
|
|||
|
Binary integer constants consist of a sequence of 1 through 32 zeros
|
|||
|
or ones preceded by a percent sign ("%"). Examples:
|
|||
|
|
|||
|
%10110010
|
|||
|
%001011101
|
|||
|
%10
|
|||
|
%1100
|
|||
|
|
|||
|
Decimal integer constants consist of strings of decimal digits without
|
|||
|
any preceding characters. E.g., 25, 235, 8325, etc. Decimal constants
|
|||
|
may be (optionally) preceded by a minus sign.
|
|||
|
|
|||
|
Hexadecimal constants consist of a dollar sign ("$") followed by
|
|||
|
a string of hexadecimal digits (0..9 and A..F). Values in the range $0
|
|||
|
through $FFFFFFFF are allowed.
|
|||
|
|
|||
|
Set constants are only required at the full and extended compliance
|
|||
|
levels. A set constant consists of a list of items surrounded by braces,
|
|||
|
e.g., {0,3,5}. For more information, see the .SET directive.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Address Expressions
|
|||
|
-------------------
|
|||
|
|
|||
|
Most instructions and many pseudo-opcode/assembler directives require
|
|||
|
operands of some sort. Often these operands contain some sort of address
|
|||
|
expression (some, ultimately, numeric or string value). This proposed
|
|||
|
standard defines the operands, precision, accuracy, and available operations
|
|||
|
that constitutes an address expression.
|
|||
|
|
|||
|
Precision: all integer expressions are computed using 32 bits. All string
|
|||
|
expressions are computed with strings up to 255 characters in length. All
|
|||
|
floating point operations are performed using IEEE 80-bit extended floating
|
|||
|
point values (i.e., Apple SANE routines). All set operations are performed
|
|||
|
using 32 bits of precision.
|
|||
|
|
|||
|
Accuracy: all integer operations (consisting of two 32-bit operands and an
|
|||
|
operator on those operands) must produce the correct result if the actual
|
|||
|
result can fit within 32 bits. If an overflow occurs, the value is truncated
|
|||
|
and only the low order 32 bits are retained. If an underflow occurs, zero
|
|||
|
is used as the result. If an overflow or underflow occurs, a special bit will
|
|||
|
be set (until the next value is computed) that can be tested by the ".IFOVR"
|
|||
|
and ".IFUNDR" directives. Other than that, such errors are ignored. All
|
|||
|
arithmetic is performed using unsigned arithmetic operations. All
|
|||
|
floating point operations follow the IEEE (and Apple SANE) suggestions, and
|
|||
|
are otherwise ignored by the assembler. Any string operation producing a
|
|||
|
string longer than 255 characters produces an assembly time error. All set
|
|||
|
operations must be exact.
|
|||
|
|
|||
|
Integer operations: The following integer operations must be provided at all
|
|||
|
compliance levels:
|
|||
|
|
|||
|
+ (binary) adds the two operands.
|
|||
|
- (binary) subracts second operand from the first.
|
|||
|
* multiplies the two operands.
|
|||
|
/ divides the first operand by the second.
|
|||
|
\ divides the first operand by the second and returns the remainder.
|
|||
|
& logically ANDs the two operands.
|
|||
|
| logically ORs the two operands.
|
|||
|
^ logically XORs the two operands.
|
|||
|
|
|||
|
|
|||
|
=
|
|||
|
<> These operators compare the two operands (unsigned comparison) and
|
|||
|
< return 1 if the comparison is true, 0 otherwise.
|
|||
|
>
|
|||
|
<=
|
|||
|
>=
|
|||
|
|
|||
|
- (unary) negates (2's complement) the operand
|
|||
|
~ (unary) complements (inverts - 1's complement) the operand
|
|||
|
|
|||
|
|
|||
|
The following operators must be provided at the full and extended compliance
|
|||
|
levels:
|
|||
|
|
|||
|
<- shifts the first operand to the left the number of bits specified by the
|
|||
|
second operand.
|
|||
|
-> shifts the first operand to the right the number of bits specified by the
|
|||
|
second operand.
|
|||
|
|
|||
|
@ (unary) subtracts the location counter at the beginning of the current
|
|||
|
statement from the following address expression.
|
|||
|
|
|||
|
% (ternary, e.g.: X%Y:Z) This operator extracts bits Y through Z from X and
|
|||
|
returns that result right justified.
|
|||
|
|
|||
|
|
|||
|
Floating point operations: floating point numbers and operations are required
|
|||
|
only at the full and extended levels. The following operations must be
|
|||
|
available as well:
|
|||
|
|
|||
|
+ adds the two operands.
|
|||
|
- subtracts the second operand from the first.
|
|||
|
* multiplies the two operands.
|
|||
|
/ divides the first operand by the second.
|
|||
|
- (unary) negates the operand.
|
|||
|
|
|||
|
=
|
|||
|
<> These operators compare the two operands and
|
|||
|
< return 1 if the comparison is true, 0 otherwise.
|
|||
|
>
|
|||
|
<=
|
|||
|
>=
|
|||
|
|
|||
|
|
|||
|
|
|||
|
String operations: strings and string operations are not required at the
|
|||
|
subset level, but the standard recommends their presence. The following
|
|||
|
string operations must be provided at the full and extended levels:
|
|||
|
|
|||
|
+ concatenates two strings
|
|||
|
% (ternary, e.g., X%Y:Z) returns the substring composed of the characters in
|
|||
|
X starting at position Y of length Z. Generate an error if X doesn't
|
|||
|
contain sufficient characters.
|
|||
|
|
|||
|
=
|
|||
|
<> These operators compare the two operands and
|
|||
|
< return 1 if the comparison is true, 0 otherwise.
|
|||
|
>
|
|||
|
<=
|
|||
|
>=
|
|||
|
|
|||
|
|
|||
|
Set operations: sets and set operations are required only at the full and
|
|||
|
extended levels. The following set operations must be provided:
|
|||
|
|
|||
|
+ union of two sets (logical OR of the bits).
|
|||
|
* intersection of two sets (logical AND of the bits).
|
|||
|
- set difference (set one ANDed with the NOT of the second set)
|
|||
|
|
|||
|
= returns 1 if the two sets are equal, zero otherwise.
|
|||
|
<> returns 1 if the two sets are not equal, zero otherwise.
|
|||
|
< returns 1 if the first set is a proper subset of the second.
|
|||
|
<= returns 1 if the first set is a subset of the second.
|
|||
|
> returns 1 if the first set is a proper superset of the second.
|
|||
|
>= returns 1 if the first set is a superset of the second.
|
|||
|
|
|||
|
% (ternary, e.g., X % Y:Z) extracts elements Y..Z from X and returns those
|
|||
|
items.
|
|||
|
|
|||
|
|
|||
|
In addition to the above operators, several pre-defined functions are also
|
|||
|
available. Note that these functions are not required at the subset
|
|||
|
compliance level, only at the full and extended levels:
|
|||
|
|
|||
|
float(i) - Converts integer "i" to a floating point value.
|
|||
|
trunc(r) - Converts real "r" to a 32-bit unsigned integer (or generates an
|
|||
|
error).
|
|||
|
valid(r) - returns "1" if r is a valid floating point value, 0 otherwise
|
|||
|
(for example, if r is NaN, infinity, etc.)
|
|||
|
length(s)- returns the length of string s.
|
|||
|
lookup(s)- returns "1" if s is a valid symbol in the symbol table.
|
|||
|
value(s) - returns value of symbol specified by string "s" in the symbol
|
|||
|
table.
|
|||
|
type(s) - returns type of symbol "s" in symbol table. Actual values
|
|||
|
returned are yet to be defined.
|
|||
|
mode(a) - returns the addressing mode of item "a". Used mainly in macros.
|
|||
|
STR(s) - returns string s with a prefixed length byte.
|
|||
|
ZRO(s) - returns string s with a suffixed zero byte.
|
|||
|
DCI(s) - returns string s with the H.O. bit of its last char inverted.
|
|||
|
RVS(s) - returns string s with its characters reversed.
|
|||
|
FLP(s) - returns string s with its H.O. bits inverted.
|
|||
|
IN(v,s) - returns one if value v is in set s, zero otherwise.
|
|||
|
|
|||
|
|
|||
|
The following integer functions must be present at all compliance levels:
|
|||
|
|
|||
|
LB(i),
|
|||
|
LBYTE(i),
|
|||
|
BYTE(i) - returns the L.O. byte of i.
|
|||
|
HB(i),
|
|||
|
HBYTE(i) - returns byte #1 (bits 8-15) of i.
|
|||
|
BB(i),
|
|||
|
BBYTE(i) - returns bank byte (bits 16-23) of i.
|
|||
|
XB,
|
|||
|
XBYTE(i) - returns H.O. byte of i.
|
|||
|
LW(i),
|
|||
|
LWORD(i),
|
|||
|
WORD(i) - returns L.O. word of i.
|
|||
|
HW(i),
|
|||
|
HWORD(i) - returns H.O. word of i.
|
|||
|
WORD(i)
|
|||
|
|
|||
|
Pack(i,j)- returns a 16-bit value whose L.O. byte is the L.O. byte of i and
|
|||
|
whose H.O. byte is the L.O. byte of j.
|
|||
|
|
|||
|
Pack(i,j,k,l)- returns a 32-bit value consisting of (i,j,k,l) where i is the
|
|||
|
L.O. byte and l is the H.O. byte. Note: l is optional. If
|
|||
|
it isn't present, substitute zero for l.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
The order of evaluation for an expression is strictly left to right
|
|||
|
unless parentheses are used to modify the precedence of a sub-expression.
|
|||
|
Since parentheses are used to specify certain indirect addressing modes, the
|
|||
|
use of paretheses to override the strict left-to-right evaluation order
|
|||
|
introduces some ambiguity. For example, should the following be treated
|
|||
|
as jump indirect through location $1001 or jump directly to location $1001?
|
|||
|
|
|||
|
JMP ($1000+1)
|
|||
|
|
|||
|
The ambiguity is resolved as follows: if the parenthesis is the first char-
|
|||
|
acter in the operand field, then the indirect addressing mode is assumed.
|
|||
|
Otherwise, the parentheses are used to override the left-to-right precedence.
|
|||
|
The example above would be treated as a jump indirect through location $1001.
|
|||
|
If you wanted to jump directly to location $1001 in this fashion, the state-
|
|||
|
ment could be modified to
|
|||
|
|
|||
|
JMP 0+($1000+1)
|
|||
|
|
|||
|
so that the parenthesis is no longer the first character in the operand
|
|||
|
field.
|
|||
|
|
|||
|
The use of parentheses to override the left-to-right precedence is
|
|||
|
only required at the full and extended compliance levels. It is not
|
|||
|
required at the subset compliance level.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Expression Types
|
|||
|
----------------
|
|||
|
|
|||
|
Expressions, in addition to having a value associated with
|
|||
|
them, also have a specific type. The three basic types of expressions are
|
|||
|
integer, floating point, and string expressions. Integer expressions can
|
|||
|
be broken down into subtypes as well. A hierarchical diagram is the easiest
|
|||
|
way to describe integer expressions:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
integers ------ constants ------------ user defined (enumerated) types
|
|||
|
| |
|
|||
|
| +----- simple numeric constants
|
|||
|
|
|
|||
|
|
|
|||
|
+-- addresses ------------ direct page addresses
|
|||
|
|
|
|||
|
+----- absolute addresses --- full 16-bit
|
|||
|
| |
|
|||
|
| +- relative 8-bit
|
|||
|
|
|
|||
|
+----- long addresses
|
|||
|
|
|||
|
This diagram points out that there are two types of integer expres-
|
|||
|
sions: constants and addresses. Further, there are two types of constants
|
|||
|
and four types of addresses. Before discussion operations on these different
|
|||
|
types of integer values, their purpose should be presented.
|
|||
|
|
|||
|
Until now, most 65xxx assembler did little to differentiate between
|
|||
|
the different types of integer values. In this proposed standard, however,
|
|||
|
strong type checking is enforced. Whereas in previous assemblers you could
|
|||
|
use the following code:
|
|||
|
|
|||
|
label equ $1000
|
|||
|
lda #Label
|
|||
|
sta Label
|
|||
|
|
|||
|
such operations are illegal within the confines of the new standard. The
|
|||
|
problem with this short code segment is that the symbol "label" is used as
|
|||
|
both an integer constant (in the LDA instruction) and as an address
|
|||
|
expression (in the STA instruction). To help prevent logical errors from
|
|||
|
creeping into a program, the assembler doesn't allow the use of addresses
|
|||
|
where constants are expected and vice versa. To that end, a new assembler
|
|||
|
directive, CON, is used to declare constants while EQU is used to declare
|
|||
|
an (absolute) address. Symbols declared by CON cannot be (directly) used
|
|||
|
as an address. Likewise, symbols declared by EQU (and others) cannot be
|
|||
|
used where a constant is expected (such as in an immediate operand).
|
|||
|
|
|||
|
Although this type checking can be quite useful for locating bugs
|
|||
|
within the source file, it can also be a source of major annoyance. Some-
|
|||
|
times (quite often, in fact) you may want to treat an address expression
|
|||
|
as a constant or a constant expression as an address. Two functions are
|
|||
|
used to coerce these expressions to their desired form: PTR and OFS.
|
|||
|
PTR(expr) converts the supplied constant expression to an address expression.
|
|||
|
OFS(expr) converts the supplied address expression to a constant expression.
|
|||
|
The following is perfectly legal:
|
|||
|
|
|||
|
Cons1 CON $5A
|
|||
|
DataLoc EQU $1000
|
|||
|
lda #OFS(DataLoc)
|
|||
|
sta PTR(Cons1)
|
|||
|
|
|||
|
For more information, see the section on assembler directives. PTR and OFS
|
|||
|
are required at all compliance levels of this proposed standard.
|
|||
|
|
|||
|
While any constant value may be used anywhere a constant is allowed,
|
|||
|
the 65c816 microprocessor must often differentiate between the various types
|
|||
|
of address expressions. This is particularly true when emitting code since
|
|||
|
the length of an instruction depends on the particular address expression.
|
|||
|
If an expression contains only constants, direct page values, absolute
|
|||
|
values, or long values, there isn't much of a problem. The assembler uses
|
|||
|
the specified type as the addressing mode. If the expression contains mixed
|
|||
|
types, the resulting type is as follows:
|
|||
|
|
|||
|
Expression contains: Result is:
|
|||
|
| |
|
|||
|
| |
|
|||
|
+------------+-- Constants - Constant
|
|||
|
| |
|
|||
|
+-- Direct | - Direct
|
|||
|
|
|
|||
|
+--+ Absolute - Absolute
|
|||
|
|
|
|||
|
+--+- Long - Long
|
|||
|
|
|||
|
Allowable forms:
|
|||
|
|
|||
|
constant
|
|||
|
direct constant+direct
|
|||
|
absolute constant+absolute
|
|||
|
long constant+long
|
|||
|
absolute+long
|
|||
|
constant+absolute+long
|
|||
|
|
|||
|
|
|||
|
This says that if you expression contains only constants, then the
|
|||
|
result is a constant. If it contains a mixture of constants and direct
|
|||
|
page addresses, the result is a direct page address. Note that direct page
|
|||
|
addresses cannot be mixed with other types of addresses. An error must be
|
|||
|
reported in this situation (although you could get around it with an
|
|||
|
expression of the form "abs+OFS(direct)"). Likewise, adding a constant to
|
|||
|
an absolute address produces an absolute address. Adding an absolute and
|
|||
|
a long address produces a long address, etc.
|
|||
|
|
|||
|
Sometimes, you need to force an expression to be a certain type.
|
|||
|
For example, the instruction "LDA $200" normally assembles to a load
|
|||
|
absolute from location $200 in the current data bank. If you need to force
|
|||
|
this to location $200 in bank zero, regardless of the content of the DBR,
|
|||
|
the address expression must be coerced to a long address. Coercion of this
|
|||
|
type is accomplished with the ":D", ":A", ":L", and ":S" expression suffixes.
|
|||
|
To force "LDA $200" to be assembled using the long address mode, the in-
|
|||
|
struction is modified to be "LDA $200:L". The coercion suffix must always
|
|||
|
follow the full address expression. The ":S" (for short branches) suffix
|
|||
|
is never required, since a short branch (for BRA and BSR) is always assumed,
|
|||
|
but it is included for completeness. For BRA and BSR, the ":L" suffix is
|
|||
|
used to imply a long branch (+/- 32K) rather than the long addressing mode.
|
|||
|
|
|||
|
Caveats: If ":D" or ":A" is used to coerce a large address expression
|
|||
|
to direct or absolute, the high order byte(s) of the expression are truncated
|
|||
|
and ignored. The assembler must assume that when a programmer uses these
|
|||
|
constructs he knows exactly what he's doing. Therefore, "LDA $1001:D" will
|
|||
|
happily assemble this instruction into a "LDA $01" instruction despite the
|
|||
|
actual value of the address expression.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Addressing Mode Specification
|
|||
|
-----------------------------
|
|||
|
|
|||
|
65c816 addressing modes are specified by certain symbols in the op-
|
|||
|
erand field. A quick rundown follows:
|
|||
|
|
|||
|
Addressing mode Format(s) Example(s)
|
|||
|
--------------- ------------------ ----------------------
|
|||
|
|
|||
|
Immediate #<expression> LDA #0
|
|||
|
=<expression> CMP =LastValue
|
|||
|
|
|||
|
Direct Page <expression> LDA DPG
|
|||
|
<expression>:D LDA ANY:D
|
|||
|
|
|||
|
Absolute <expression> LDA ABS
|
|||
|
<expression>:A LDA ANY:A
|
|||
|
|
|||
|
Long <expression> LDA LONG
|
|||
|
<expression>:L LDA ANY:L
|
|||
|
|
|||
|
Accumulator {no operand} ASL
|
|||
|
INC
|
|||
|
|
|||
|
Implied {no operand} CLC
|
|||
|
SED
|
|||
|
|
|||
|
Direct, Indirect,
|
|||
|
Indexed by Y (<direct expr>),Y LDA (DPG),Y
|
|||
|
(<direct expr>).Y LDA (ANY:D).Y
|
|||
|
|
|||
|
Direct, Indirect,
|
|||
|
Indexed by Y, Long [<direct expr>],Y LDA [DPG],Y
|
|||
|
[<direct expr>].Y LDA [DPG].Y
|
|||
|
|
|||
|
Direct, Indexed by X,
|
|||
|
Indirect (<direct expr>,X) LDA (DPG,X)
|
|||
|
(<direct expr>.X) LDA (ANY:D.X)
|
|||
|
|
|||
|
Direct, Indexed by X <direct expr>,X LDA DPG,X
|
|||
|
<direct expr>.X LDA DPG.X
|
|||
|
|
|||
|
Direct, Indexed by Y <direct expr>,Y LDX DPG,Y
|
|||
|
<direct expr>.Y LDX DPG.Y
|
|||
|
|
|||
|
Absolute, Indexed by X <abs expr>,X LDA ABS,X
|
|||
|
<abs expr>.X LDA ANY:A.X
|
|||
|
|
|||
|
Long, Indexed by X <long expr>,X LDA ANY:L,X
|
|||
|
<long expr>.X LDA LONG.X
|
|||
|
|
|||
|
Absolute, Indexed by Y <abs expr>,Y LDA ANY:A,Y
|
|||
|
<abs expr>.Y LDA ABS.Y
|
|||
|
|
|||
|
Program Counter
|
|||
|
Relative (branches) <expression> BRA ABS
|
|||
|
@<expression> BRA @ABS
|
|||
|
|
|||
|
PC Relative (PSH) @<expression> PSH @ABS
|
|||
|
|
|||
|
Absolute, Indirect (<abs expr>) JMP (ABS)
|
|||
|
|
|||
|
Absolute, Indexed,
|
|||
|
Indirect (<abs expr>,X) JMP (ABS,X)
|
|||
|
(<abs expr>.X) JMP (ABS.X)
|
|||
|
|
|||
|
Direct, Indirect (<dpg expr>) LDA (DPG)
|
|||
|
STA (ANY:D)
|
|||
|
|
|||
|
Stack Relative <expr8>,S LDA 2,S
|
|||
|
<expr8>.S LDA 2.S
|
|||
|
|
|||
|
Stack Relative,
|
|||
|
Indirect, Indexed (<expr8>,S),Y LDA (2,S),Y
|
|||
|
(<expr8).S),Y LDA (2.S),y
|
|||
|
(<expr8),S).Y LDA (2,S).y
|
|||
|
(<expr8).S).Y LDA (2.S).y
|
|||
|
|
|||
|
Block Move <long expr>,<long expr> MVN LONG,LONG
|
|||
|
MVP LONG,LONG
|
|||
|
|
|||
|
|
|||
|
<dpg expr>, DPG- Any direct page expression or symbol.
|
|||
|
<abs expr>, ABS- Any absolute expression or symbol.
|
|||
|
<long expr>, Long- Any long expression or symbol.
|
|||
|
expr8- Any expression evaluating to a value less than
|
|||
|
256.
|
|||
|
|
|||
|
|
|||
|
Note: the only real difference between the existing standard and the proposed
|
|||
|
standard is that the period (".") can be used to form an indexed address ex-
|
|||
|
pression. This is compatible (in practice, as well as philosophy) with the
|
|||
|
record structure mechanism supported by this proposed standard. This syntax
|
|||
|
for the various addressing modes is required at all compliance levels.
|
|||
|
|
|||
|
Suggestion: (<dpg expr>):L, (<dpg expr>):L,Y, and (<dpg expr):L.Y
|
|||
|
should be allowed as substitutes for [<dpg expr>], [<dpg expr>],Y, and
|
|||
|
[<dpg expr].Y, respectively. This, however, is not required by this proposed
|
|||
|
standard.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Assembler Directives and Pseudo-Opcodes
|
|||
|
---------------------------------------
|
|||
|
|
|||
|
An assembler directive is a message to the assembler to change some
|
|||
|
status or otherwise affect the assembly operation. It does not generate any
|
|||
|
object code. A pseudo-opcode, on the other hand, is not a standard 65c816
|
|||
|
instruction but does generate object code. Examples of assembler directives
|
|||
|
include instructions that turn the listing on or off, define procedures,
|
|||
|
equate labels to values, etc. Examples of pseudo-opcodes include instructions
|
|||
|
like .BYTE which emit bytes of object code based on the instruction's
|
|||
|
parameters.
|
|||
|
|
|||
|
|
|||
|
Equates:
|
|||
|
--------
|
|||
|
|
|||
|
Probably the most important assembler directives are the equates.
|
|||
|
The equate directives let you associate a value and a type with a symbol.
|
|||
|
The possible equates use the syntax:
|
|||
|
|
|||
|
<label> .EQU <16-bit value>
|
|||
|
<label> .EDP <8-bit value>
|
|||
|
<label> .EQL <24-bit value>
|
|||
|
<label> .CON <32-bit value>
|
|||
|
<label> .FCON <SANE floating point value>
|
|||
|
|
|||
|
All except .FCON are required at all compliance levels. .FCON is required
|
|||
|
at the full and extended levels.
|
|||
|
|
|||
|
.EQU lets you define a absolute symbol; an address whose value is
|
|||
|
relative to the DBR. An error should be generated if the value in the
|
|||
|
operand field requires more than 16 bits. The type of the operand expression
|
|||
|
is ignored. It may be a constant expression, a direct page expression, or
|
|||
|
even a long address expression. As long as it's an integer expression an
|
|||
|
can fit into 16 bits, it's quite acceptable.
|
|||
|
|
|||
|
.EDP (equate to direct page) is used to define direct page symbols.
|
|||
|
Again, the operand field may be of any integer type as long as the result
|
|||
|
fits into 8 bits. A recommended synonym for .EDP is .EPZ (equate to page
|
|||
|
zero) in deference to the 6502's zero page addressing mode.
|
|||
|
|
|||
|
.EQL (equate long) defines long address expressions. As usual, the
|
|||
|
operand field may contain any integer expression that fits within 24 bits.
|
|||
|
|
|||
|
.CON (constant) is used to define integer numeric constants. Any
|
|||
|
32 bit numeric value may be specified in the operand field.
|
|||
|
|
|||
|
.FCON (floating point constant) is used to declare symbolic floating
|
|||
|
point constants. Such constants must be stored in the symbol table as
|
|||
|
80-bit SANE extended values.
|
|||
|
|
|||
|
In addition to the typed equates, this proposed standard also allows
|
|||
|
an untyped equate, which takes the form:
|
|||
|
|
|||
|
<label> = <operand>
|
|||
|
|
|||
|
where "<operand>" is any valid operand that may appear in the operand field
|
|||
|
of any instruction. <operand>'s type may be integer, string, floating point
|
|||
|
and may also include an addressing mode. The following are all legal:
|
|||
|
|
|||
|
lbl = 5
|
|||
|
lbl = 5.5
|
|||
|
lbl = "Five"
|
|||
|
lbl = Array,X
|
|||
|
lbl = (dp,s),y
|
|||
|
|
|||
|
Labels defined by "=" may appear anywhere the operand field specified for
|
|||
|
that label is allowed. In general, a simple string substitution should be
|
|||
|
performed when a label defined by "=" is used. Note: a label declared by
|
|||
|
"=" can be redefined without error throughout the program. The "=" directive
|
|||
|
is required only at the full and extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Data Definitions:
|
|||
|
-----------------
|
|||
|
|
|||
|
While the equates are probably the most important assembler
|
|||
|
directives, the data definition instructions are probably the most important
|
|||
|
pseudo-opcodes around. These instructions are classed into four groups
|
|||
|
determined by the types of operands they accept. In the following paragraphs
|
|||
|
all optional items are enclosed within braces.
|
|||
|
|
|||
|
The first group of data reservation instructions accept any integer
|
|||
|
type expression as operands. They are:
|
|||
|
|
|||
|
{label} .BYTE {expr1, expr2, ..., exprn}
|
|||
|
{label} .WORD {expr1, expr2, ..., exprn}
|
|||
|
{label} .LONG {expr1, expr2, ..., exprn}
|
|||
|
|
|||
|
If a label is present, it is treated as a statement label within the current
|
|||
|
segment and assigned the value of the location counter before any bytes are
|
|||
|
emitted. For the .BYTE opcode, one byte of data is emitted for each operand
|
|||
|
in the operand field, that byte being the L.O. byte of each expression.
|
|||
|
Operands are purely optional. If no operand appears, then an indeterminate
|
|||
|
value is emitted. The .WORD opcodes outputs two bytes for each expression in
|
|||
|
the operand field (or two indeterminate bytes if no operand is present). The
|
|||
|
.LONG instruction outputs four bytes for each operand. These three pseudo-
|
|||
|
opcodes must be present at all compliance levels.
|
|||
|
|
|||
|
The next group of pseudo-opcodes are used to create tables of
|
|||
|
addresses. As such, they only allow symbols that have been defined by
|
|||
|
.EQU, .EQL, "=" (as applicable), statement labels, procedure labels, and
|
|||
|
segment labels in their operand fields. They are:
|
|||
|
|
|||
|
{label} .OFFS expr1 {,expr2, ..., exprn}
|
|||
|
{label} .ADRS expr1 {,expr2, ..., exprn}
|
|||
|
{label} .PTR expr1 {,expr2, ..., exprn}
|
|||
|
|
|||
|
.OFFS outputs two bytes for each operand; .ADRS outputs three bytes for
|
|||
|
each operand; and .PTR outputs four bytes for each operand. These three
|
|||
|
pseudo-opcodes are only required at the full and extended compliance levels.
|
|||
|
|
|||
|
The third group of declarations are used to create constant tables.
|
|||
|
As such, they only allow symbols declared by .CON. They are:
|
|||
|
|
|||
|
{label} .SHORT expr1 {,expr2, ..., exprn}
|
|||
|
{label} .INTEGER expr1 {,expr2, ..., exprn}
|
|||
|
{label} .LONGINT expr1 {,expr2, ..., exprn}
|
|||
|
|
|||
|
These pseudo-ops output one, two, and four bytes respectively. These
|
|||
|
pseudo-opcodes are not required at the subset compliance level, they are
|
|||
|
required only at the full and extended levels.
|
|||
|
|
|||
|
Note: non-symbolic constants are allowed in any of the above
|
|||
|
pseudo-opcodes. Only symbols should have their type information checked.
|
|||
|
|
|||
|
The last group of data declaration pseudo-opcodes are used to
|
|||
|
initialize floating point values. These pseudo-ops are:
|
|||
|
|
|||
|
{label} .FLOAT {item1, item2, ..., itemn}
|
|||
|
{label} .DOUBLE {item1, item2, ..., itemn}
|
|||
|
{label} .EXTENDED {item1, item2, ..., itemn}
|
|||
|
{label} .COMP {item1, item2, ..., itemn}
|
|||
|
|
|||
|
each instruction generates operands of 4, 8, 10, or 8 bytes in length,
|
|||
|
respectively. If the operand field is left blank, the corresponding bytes
|
|||
|
contain an indeterminate value, but the assembler should initialize them to
|
|||
|
NaN (not a number). These four pseudo-opcodes are required only at the
|
|||
|
full and extended levels.
|
|||
|
|
|||
|
Although not required by the standard, the following data declaration
|
|||
|
directives are recommended and should be supported:
|
|||
|
|
|||
|
{label} .HBYTE expr1 {,expr2, ..., exprn}
|
|||
|
{label} .BBYTE expr1 {,expr2, ..., exprn}
|
|||
|
{label} .XBYTE expr1 {,expr2, ..., exprn}
|
|||
|
{label} .HWORD expr1 {,expr2, ..., exprn}
|
|||
|
|
|||
|
the first three reserve one byte of memory for each operand and store the
|
|||
|
H.O (bits 8-15), bank (bits 16-23), or extra byte (bits 24-31) respectively.
|
|||
|
.HWORD reserves two bytes composed of bits 16-31 for each operand.
|
|||
|
|
|||
|
|
|||
|
Arrays:
|
|||
|
-------
|
|||
|
|
|||
|
Space for arrays and data tables can be reserved using the data
|
|||
|
declaration statement mentioned above in conjunction with the "DUP" operator.
|
|||
|
DUP is a binary operator that takes the form:
|
|||
|
|
|||
|
count DUP (list)
|
|||
|
|
|||
|
where count is some constant value and list is a (possibly empty) list of
|
|||
|
values. The items in (list) are repeated "count" times. For example, the
|
|||
|
following .BYTE statement reserves space for an array of 64 bytes and
|
|||
|
initializes each byte to zero:
|
|||
|
|
|||
|
MyArray .BYTE 64 DUP (0)
|
|||
|
|
|||
|
The following statement reserves 256 bytes consisting of the values 1, 2, 3,
|
|||
|
4, 5, 6, 7, and 8 repeated 32 times:
|
|||
|
|
|||
|
MyArray .BYTE 32 DUP (1,2,3,4,5,6,7,8)
|
|||
|
|
|||
|
|
|||
|
The DUP operator is fully recursive. That is, one of the items in
|
|||
|
the list may, itself, be a list defined by the DUP operator. For example,
|
|||
|
|
|||
|
Example .BYTE 16 DUP (0,1,2 DUP (3,4,5))
|
|||
|
|
|||
|
reserves 128 bytes consisting of the list "0,1,3,4,5,3,4,5" repeated 16 times.
|
|||
|
|
|||
|
If the DUP list is empty, e.g., "16 dup ()", then exactly one item
|
|||
|
is reserved for each entry, but it is not initialized. The following example
|
|||
|
reserves space for 128 uninitialized words:
|
|||
|
|
|||
|
OffsetTable .WORD 128 DUP ()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Type definitions:
|
|||
|
-----------------
|
|||
|
|
|||
|
Enumerated data types can be declared with the ".TYPE" directive.
|
|||
|
This directive takes the form:
|
|||
|
|
|||
|
{label} .TYPE item1 {,item2, ..., itemn}
|
|||
|
|
|||
|
The items in the list are assigned consecutive values starting from zero.
|
|||
|
For example, in the following .TYPE statement, the symbols red, green, and
|
|||
|
blue are assigned the values zero, one, and two, respectively:
|
|||
|
|
|||
|
colors .TYPE red,green,blue
|
|||
|
|
|||
|
The symbols in the operand field of a .TYPE statement must be unique and
|
|||
|
undefined elsewhere (within the current scope, more on that later). The
|
|||
|
.TYPE statement above is almost identical to the statements:
|
|||
|
|
|||
|
red .con 0
|
|||
|
green .con 1
|
|||
|
blue .con 2
|
|||
|
|
|||
|
However, there is one major difference. The .TYPE statement also defines a
|
|||
|
symbol specified in the label field. This symbol can be used as a pseudo-
|
|||
|
opcode to reserve space for values of the specified type. In the example
|
|||
|
above, "colors" could be used as a pseudo-opcode to reserve space for the
|
|||
|
values red, green, and blue. To differentiate type declarations from other
|
|||
|
instructions, a special lead-in character is used. The slash ("/") is
|
|||
|
recommended by this standard, but the user should have the option of choosing
|
|||
|
this character via a setup program for the assembler. From the example
|
|||
|
above, colors could be used as a pseudo-opcode in the following manner:
|
|||
|
|
|||
|
Christmas /colors red,green
|
|||
|
Ocean /colors blue,green
|
|||
|
Sky /colors blue
|
|||
|
/colors red
|
|||
|
Primaries /colors red,blue,green
|
|||
|
|
|||
|
Unlike other data reserving pseudo-opcodes, a "/colors" definition only
|
|||
|
allows symbols that appear in the operand field of the associated .TYPE
|
|||
|
statement or one of those symbols in a expression that contains a single
|
|||
|
such symbol plus or minus a numeric constant, as long as the result is still
|
|||
|
within the range of symbols declared for that type. E.g.,
|
|||
|
|
|||
|
Okay /colors red,green+1,blue
|
|||
|
NotOkay1 /colors blue+2 ;Outside allowable range
|
|||
|
NotOkay2 /colors red+blue ;can't add two such symbols
|
|||
|
NotOkay3 /colors $25 ;Not red, green, or blue
|
|||
|
|
|||
|
If you need to coerce an expression to the proper form, simply use the type
|
|||
|
name as a pseudo-function. E.g.,
|
|||
|
|
|||
|
ThisIsOkay /colors colors(0),blue ;Same as red, blue
|
|||
|
|
|||
|
If the operand is not appropriate, the assembler should generate a warning
|
|||
|
and emit the code as though the .BYTE statement were used.
|
|||
|
|
|||
|
|
|||
|
If there isn't a label starting in column one of a .TYPE statement
|
|||
|
then the symbols defined in the operand field are applied to the previous
|
|||
|
.TYPE statement. This allows you to create .TYPEs where several symbols
|
|||
|
(which couldn't possibly fit on a single line) are declared as constants.
|
|||
|
E.g.,
|
|||
|
|
|||
|
colors .TYPE red, yellow, blue
|
|||
|
.TYPE orange, green violet
|
|||
|
.TYPE brown, black, white
|
|||
|
|
|||
|
All of these symbols will be associated with "colors". A maximum of 256
|
|||
|
symbols can be associated with a symbol via the .TYPE statement. Whenever
|
|||
|
the data reservation form is used, exactly one byte is reserved for each
|
|||
|
item in the operand field. If you need to reserve more than a single byte
|
|||
|
for each item, use the record declarations described next.
|
|||
|
|
|||
|
The DUP operator can be used to define enumerated data type arrays,
|
|||
|
e.g.,
|
|||
|
|
|||
|
LotsOfRed /colors 16 DUP (red)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Another form of the .TYPE statement allows you to declare byte
|
|||
|
subrange values. A definition of this type takes the form:
|
|||
|
|
|||
|
label .TYPE start..stop
|
|||
|
|
|||
|
where start and stop are constant values in the range 0..255 and
|
|||
|
start <= stop. Examples:
|
|||
|
|
|||
|
LessThan10 .TYPE 0..9
|
|||
|
Nibbles .TYPE 0..$F
|
|||
|
PrimaryColors .TYPE red..blue ;From above, is red, yellow, blue
|
|||
|
|
|||
|
|
|||
|
Implementation of the .TYPE statement is required only at the full
|
|||
|
and extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Records:
|
|||
|
--------
|
|||
|
|
|||
|
A record data structure can be defined with the ".RECORD" and ".ENDR"
|
|||
|
directives using the syntax:
|
|||
|
|
|||
|
label .RECORD
|
|||
|
<data declarations>
|
|||
|
.ENDR
|
|||
|
|
|||
|
This creates a template, but does not generate any code. An example might
|
|||
|
be:
|
|||
|
|
|||
|
CursorPosn .RECORD
|
|||
|
ROW .BYTE 0
|
|||
|
COLUMN .BYTE 0
|
|||
|
.ENDR
|
|||
|
|
|||
|
This definition creates the type "CursorPosn". Like the .TYPE definitions,
|
|||
|
the symbol defined by .RECORD can be used as a pseudo-opcode to reserve
|
|||
|
storage for a variable. For example, to declare a variable of type
|
|||
|
"CursorPosn" the following statement is used:
|
|||
|
|
|||
|
MyCursor /CursorPosn
|
|||
|
|
|||
|
This statement reserves two bytes, initialized to zeros, at the current
|
|||
|
location counter.
|
|||
|
|
|||
|
Access to the fields of the record is accomplished by using the
|
|||
|
"." operator, just like Pascal. E.g.,
|
|||
|
|
|||
|
lda MyCursor.ROW ;Fetches first byte.
|
|||
|
lda MyCursor.COLUMN ;Fetches the second byte.
|
|||
|
|
|||
|
|
|||
|
In the example above, the ROW and COLUMN fields of each variable
|
|||
|
declared with CursorPosn are always initialized to zero. Any other value
|
|||
|
could have been used by substituting the appropriate value, or an
|
|||
|
indeterminate value could have been specified by the definition:
|
|||
|
|
|||
|
CursorPosn .RECORD
|
|||
|
ROW .BYTE
|
|||
|
COLUMN .BYTE
|
|||
|
.ENDR
|
|||
|
|
|||
|
|
|||
|
On occasion, you may want each record variable definition to
|
|||
|
specify the initial values. This can be accomplished by specifying
|
|||
|
parameters in the record definition. Parameters are specified by the
|
|||
|
symbols: ?0, ?1, ..., ?9. ?0 corresponds to the first parameter, ?1 to
|
|||
|
the second, etc. Consider the following record and variable definitions:
|
|||
|
|
|||
|
CursorPosn .RECORD
|
|||
|
ROW .BYTE ?0
|
|||
|
COLUMN .BYTE ?1
|
|||
|
.ENDR
|
|||
|
|
|||
|
HomePosn /CursorPosn 0,0
|
|||
|
LowerRight /CursorPosn 23,79
|
|||
|
MyCursor /CursorPosn 5,10
|
|||
|
|
|||
|
|
|||
|
The only problem with this definition form is that each CursorPosn
|
|||
|
variable must supply exactly two operands. Sometimes you may want to have
|
|||
|
a default value in the event an operand isn't specified. This is accomplished
|
|||
|
using a record defintion of the form:
|
|||
|
|
|||
|
CursorPosn .RECORD ?0=0,?1=0
|
|||
|
ROW .BYTE ?0
|
|||
|
COLUMN .BYTE ?1
|
|||
|
.ENDR
|
|||
|
|
|||
|
This definition instructions the assembler to allow zero or more parameters,
|
|||
|
defaulting ?0 and ?1 to zero if their respective entries aren't present.
|
|||
|
The .DEFAULT directive can also be used, particularly if you run out of
|
|||
|
room on the .RECORD line:
|
|||
|
|
|||
|
OpenRec .RECORD ?0=0, ?1=1
|
|||
|
.DEFAULT ?2=ZRO('Hello there'), ?3=2
|
|||
|
FirstItem .WORD ?0
|
|||
|
.LONG ?3
|
|||
|
SecondItem .BYTE ?1, ?2
|
|||
|
.ENDR
|
|||
|
|
|||
|
|
|||
|
Record definitions are required at the full and extended compliance
|
|||
|
levels, they are not required at the subset compliance level.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Sets:
|
|||
|
-----
|
|||
|
|
|||
|
Bit string types can be declared using the .SET directive. .SET is
|
|||
|
used in a manner quite similar to .TYPE except the items in the operand field
|
|||
|
can be any constant whose value is less than 32. Up to 32 items may
|
|||
|
appear in the operand field of a .SET definition. The syntax is
|
|||
|
|
|||
|
label .SET item1 {,item2, ..., itemn} ;n <= 32.
|
|||
|
|
|||
|
An alternate form is to specify the name of some type variable in the operand
|
|||
|
field. The following definition creates a set of integers in the range
|
|||
|
0..9:
|
|||
|
|
|||
|
LessThan10 .TYPE 0..9
|
|||
|
SetOfDigits .SET LessThan10
|
|||
|
|
|||
|
|
|||
|
Declaring a set variable is quite similar to declaring an enumerated
|
|||
|
type variable or a record variable: simply use the set name as a pseudo-opcode
|
|||
|
prefaced by a "/":
|
|||
|
|
|||
|
Digits /SetOfDigits
|
|||
|
|
|||
|
|
|||
|
Set constants are specified by placing the items in the set within
|
|||
|
a pair of braces. E.G.:
|
|||
|
|
|||
|
BitValues .TYPE 0..7
|
|||
|
SetOfBitValues .SET BitValues
|
|||
|
Bits /SetOfBitValues {0,1,2,3}
|
|||
|
;
|
|||
|
;
|
|||
|
lda #{0,2,7}
|
|||
|
sta Bits
|
|||
|
|
|||
|
|
|||
|
The assembler, by default, should allow set constants composed of
|
|||
|
the integer values 0..31. This allows programmers to easily deal with bits
|
|||
|
by bit numbers rather than the integers those bit patterns represent. For
|
|||
|
example, to strip all but the H.O. two bits in the (8-bit) accumulator, the
|
|||
|
instruction "AND #{6,7}" makes a lot more sense than "AND #$C0". All other
|
|||
|
entities appearing within "{" and "}" must appear somewhere in the operand
|
|||
|
field of a .SET statement (or must be a member of a .TYPE definition if that
|
|||
|
type appears in the operand field of a .SET).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Macros:
|
|||
|
-------
|
|||
|
|
|||
|
Macros are created using the .MACRO and .ENDM directives. The syntax
|
|||
|
for a macro definition is
|
|||
|
|
|||
|
label .MACRO {default parameter values}
|
|||
|
<macro body>
|
|||
|
.ENDM
|
|||
|
|
|||
|
Macros are invoked by placing an underscore, followed by the macro name (the
|
|||
|
label in the .MACRO statement). The user should be able to change the macro
|
|||
|
lead-in character from underscore to some other character via an assembler
|
|||
|
set up program.
|
|||
|
|
|||
|
All labels declared within the macro are local to that definition unless the
|
|||
|
".GLOBAL" directive is used to extend their scope. In general, global
|
|||
|
macro labels (except, possibly, those defined by "=") are not useful anyway
|
|||
|
since a duplicate label error might occur on the second invocation of the
|
|||
|
macro.
|
|||
|
|
|||
|
The macro body consists of a sequence of assembler statements. Most
|
|||
|
reasonable statements may be included in the macro body. The standard does
|
|||
|
not required nested macro definitions. Nor need the macro definitions allow
|
|||
|
.RECORD, .TYPE, or .SET definitions (since labels are local to the macro,
|
|||
|
such definitions are dubious anyway).
|
|||
|
|
|||
|
Macro parameters are specified using ?0, ?1, ..., ?9, just as for
|
|||
|
.RECORD definitions. "?#" can be used to determine the actual number of
|
|||
|
parameters present. "?:expr" can be used to select a parameter using a
|
|||
|
numeric expression. For example, "?:?#-1" returns the value of the last
|
|||
|
parameter specified. Default values for the parameters can be specified
|
|||
|
in the .MACRO operand field, or in a .DEFAULT statement, just like specifying
|
|||
|
default values for .RECORD parameters. E.g.,
|
|||
|
|
|||
|
MyMacro .MACRO ?0=0, ?1=2
|
|||
|
.DEFAULT ?2="Hello there"
|
|||
|
.BYTE ?0
|
|||
|
.WORD ?1
|
|||
|
.BYTE ?2
|
|||
|
.ENDM
|
|||
|
|
|||
|
then:
|
|||
|
|
|||
|
_MyMacro 10,20
|
|||
|
|
|||
|
generates the bytes:
|
|||
|
10, 20, 0, Hello there
|
|||
|
|
|||
|
|
|||
|
Macros, by the very nature, allow a variable number of parameters.
|
|||
|
If more parameters are specified than there are references for, the extra
|
|||
|
parameters are ignored. If fewer parameters are specified than there are
|
|||
|
references for, the additional references will be treated as undefined
|
|||
|
symbols. If you want to be able to force the user to enter an exact number
|
|||
|
of parameters, then use the ?# in the default field to specify a fixed number
|
|||
|
of parameters. The following macro definition requires the user to enter
|
|||
|
exactly two parameters whenever TwoParms is invoked:
|
|||
|
|
|||
|
TwoParms .MACRO ?#=2
|
|||
|
lda ?0
|
|||
|
sta ?1
|
|||
|
.ENDM
|
|||
|
|
|||
|
If the number of parameters is fixed at a certain value, default values
|
|||
|
are not allowed in the macro definition.
|
|||
|
|
|||
|
Since macro parameters, in a macro invocation, are separated by
|
|||
|
commas, you cannot directly create a macro of the form:
|
|||
|
|
|||
|
LDAIX .MACRO ?#=1
|
|||
|
lda ?0
|
|||
|
.ENDM
|
|||
|
|
|||
|
and invoke it by:
|
|||
|
|
|||
|
_LDAIX LBL,X
|
|||
|
|
|||
|
intending the "LDA LBL,X" instruction to be generated. Instead, the macro
|
|||
|
mechanism will think that LBL and X are two different parameters and generate
|
|||
|
an error since only a single parameter is allowed. The "<<" and ">>" symbols
|
|||
|
are used as an escape mechanism to parenthesize such operands. To handle the
|
|||
|
case above, the following statement could be used:
|
|||
|
|
|||
|
_LDAIX <<LBL,X>>
|
|||
|
|
|||
|
and this would generate the instruction "LDA LBL,X".
|
|||
|
|
|||
|
The lookup, value, type, and mode functions are quite useful for
|
|||
|
dealing with macro parameters. The exact values returned by these functions
|
|||
|
will be described at a later time.
|
|||
|
|
|||
|
For additional information on macros and dealing with macro para-
|
|||
|
meters, see the sections on conditional assembly and while loops.
|
|||
|
|
|||
|
Macros are required only at the full and extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Address Expression Functions:
|
|||
|
-----------------------------
|
|||
|
|
|||
|
Format:
|
|||
|
|
|||
|
label .FUNC {default parameter values}
|
|||
|
<function body>
|
|||
|
.RETURN expr
|
|||
|
.ENDF
|
|||
|
|
|||
|
The .FUNC statement lets programmers define their own address
|
|||
|
expression functions that can be used in operand fields of assembly language
|
|||
|
statements. The function body typically contains a sequence of equates
|
|||
|
and other value computing statements; it may not contain any code generating
|
|||
|
statements.
|
|||
|
|
|||
|
Like a macro definition, all symbols defined inside an address
|
|||
|
expression function are local to that function. Likewise, default parameters
|
|||
|
may be declared in the operand field of the .FUNC statement or via the
|
|||
|
.DEFAULT statement. Alternately, you can specify that a fixed number of
|
|||
|
parameters are required by using the "?#=expr" item in the operand field
|
|||
|
of the .FUNC statement.
|
|||
|
|
|||
|
The expression following the .RETURN statement is the value returned
|
|||
|
by the addressing mode function. Note that more than one .RETURN may appear
|
|||
|
within the function (perhaps within the confines of a conditional assembly
|
|||
|
sequence). If more than one .RETURN statement is encountered, all but the
|
|||
|
last are ignored. The expression returned in the .RETURN operand field may
|
|||
|
contain addressing modes in addition to the actual expression value. In
|
|||
|
general, anything allowed as a macro parameter can be returned as an address
|
|||
|
expression value.
|
|||
|
|
|||
|
An address expression function is invoked by placing the function
|
|||
|
name in some other expression followed by the parameters enclosed within
|
|||
|
parentheses. The parentheses are required even if the parameter list is
|
|||
|
empty (just like the "C" programming language). Examples follow:
|
|||
|
|
|||
|
StripLONibble .FUNC ?#=1
|
|||
|
value = ?0 AND $F0
|
|||
|
.RETURN value
|
|||
|
.ENDF
|
|||
|
;
|
|||
|
AppendTXT .FUNC ?#=1
|
|||
|
string = ?0 + ".TXT"
|
|||
|
.RETURN string
|
|||
|
.ENDF
|
|||
|
;
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
LDA #StripLONibble($FF)
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
.BYTE AppendTxt("MyString")
|
|||
|
|
|||
|
The LDA instruction generates
|
|||
|
|
|||
|
LDA #$F0,
|
|||
|
|
|||
|
the .BYTE statement becomes
|
|||
|
|
|||
|
.BYTE "MyString.TXT"
|
|||
|
|
|||
|
The latter example demonstrates that address expression functions can
|
|||
|
return any valid type. This includes strings, records, sets, and any
|
|||
|
other entity allowed in an operand field. Consider the following:
|
|||
|
|
|||
|
LBLX .FUNC ?#=2
|
|||
|
L = ?0-?1,X
|
|||
|
.RETURN L
|
|||
|
.ENDF
|
|||
|
|
|||
|
LDA LBLX($100,10)
|
|||
|
|
|||
|
This generates the code:
|
|||
|
|
|||
|
LDA $100-10,X
|
|||
|
|
|||
|
|
|||
|
Address expression functions are required only at the full and
|
|||
|
extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
The Label Type
|
|||
|
--------------
|
|||
|
|
|||
|
The ".LABEL" directive is used to declare a valueless symbol, that is, one which
|
|||
|
is defined but is assigned no particular value. The syntax for the .LABEL directive is:
|
|||
|
|
|||
|
.LABEL symbol1 {, symbol2, ..., symboln}
|
|||
|
|
|||
|
Each symbol appearing in the operand field is inserted into the symbol table as a "label"
|
|||
|
typed symbol.
|
|||
|
|
|||
|
Label-typed symbols are useful mainly in macros and in the operand fields of
|
|||
|
conditional assembly statements. The only operations you can perform using label-typed
|
|||
|
symbols are "=" and "<>". Most of the reserved symbols in the assembler (such as A, X,
|
|||
|
Y, DBR, D, M, S, etc.) are actually label-typed symbols.
|
|||
|
|
|||
|
An example of where you might use a label-typed symbol follows:
|
|||
|
|
|||
|
CmpReg .MACRO ?#=2
|
|||
|
.IF ?0=A
|
|||
|
cmp ?1
|
|||
|
.ELSE
|
|||
|
.IF ?0=X
|
|||
|
cpx ?1
|
|||
|
.ELSE
|
|||
|
.IF ?0=Y
|
|||
|
cpy ?1
|
|||
|
.ELSE
|
|||
|
.PAUSE
|
|||
|
.ENDIF
|
|||
|
.ENDIF
|
|||
|
.ENDIF
|
|||
|
.ENDM
|
|||
|
|
|||
|
The "=" equate can also be used to defined label-typed symbols by specifying a
|
|||
|
label-typed symbol in the operand field, e.g.,
|
|||
|
|
|||
|
ACC = A
|
|||
|
XReg = X
|
|||
|
etc.
|
|||
|
|
|||
|
Note that the last equate above does not allow you to enter indexed by X addressing modes as
|
|||
|
|
|||
|
<expression>,XReg
|
|||
|
|
|||
|
it simply allows you to use a statement of the form:
|
|||
|
|
|||
|
.IF XReg=X
|
|||
|
|
|||
|
and wind up assemblying the code after the ".IF".
|
|||
|
|
|||
|
The ".LABEL" directive is required at the full and extended compliance levels; it
|
|||
|
is not required at the subset compliance level.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Procedures:
|
|||
|
-----------
|
|||
|
|
|||
|
At the full and extended compliance levels, the .PROC and .ENDP
|
|||
|
directives can be used to declare 65c816 procedures (subroutines). Procedure
|
|||
|
declarations take the form:
|
|||
|
|
|||
|
procname .PROC {near|far}
|
|||
|
|
|||
|
<procedure body>
|
|||
|
|
|||
|
.ENDP
|
|||
|
|
|||
|
If an operand appears after the .PROC statement, it must be either "near" or
|
|||
|
"far". If no operand appears, "near" is assumed.
|
|||
|
|
|||
|
The procedure name that appears in the label field of the .PROC
|
|||
|
statement is assigned the current value of the location counter at that
|
|||
|
point in the program. It is also given the type of near procedure or
|
|||
|
far procedure, depending upon the .PROC operand field.
|
|||
|
|
|||
|
All labels defined inside a procedure are local to that procedure
|
|||
|
unless the .GLOBAL directive is used to extend their scope beyond the
|
|||
|
procedure. Therefore, labels inside one procedure may be reused outside
|
|||
|
that procedure. If a label inside a procedure is already defined outside
|
|||
|
that procedure an error is not generated, instead the new label supercedes
|
|||
|
the old one INSIDE THE PROCEDURE (scoping rules are the same as for Pascal).
|
|||
|
Procedures may be nested inside one other, the scoping rules used by Pascal
|
|||
|
apply in such situations.
|
|||
|
|
|||
|
Inside the procedure, RET can be used in place of RTS or RTL. The
|
|||
|
assembler will automatically choose the appropriate version depending upon
|
|||
|
whether the procedure is a near or far procedure. If RTS is used inside a
|
|||
|
FAR procedure or RTL is used inside a NEAR procedure, the assembler will
|
|||
|
generate a warning.
|
|||
|
|
|||
|
The assembler automatically assembles JSR using the absolute or
|
|||
|
long addressing mode depending upon the procedure definition. If the
|
|||
|
assembler supports the JSL mnemonic and a JSL is used to call a NEAR
|
|||
|
procedure, the assembler must generate an warning. If the address expression
|
|||
|
following a JSR was coerced using the ":A" or ":L" suffixes, no warning will
|
|||
|
be generated if the incorrect distance was specified. I.e., the following
|
|||
|
does NOT generate an error:
|
|||
|
|
|||
|
JSR mysub:L
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
mysub .PROC NEAR
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
|
|||
|
If you use a coercion operator, the assembler assumes that you know what
|
|||
|
you are doing.
|
|||
|
|
|||
|
Note that the use of the .PROC statement is optional. You may con-
|
|||
|
tinue to build and call subroutines without the .PROC directive. However,
|
|||
|
using .PROC allows the assembler to perform additional type checking on
|
|||
|
certain operations. An external data flow analysis program can also use the
|
|||
|
procedure declarations to help locate logical bugs in your code.
|
|||
|
|
|||
|
.PROC and .ENDP are required at all compliance levels of the
|
|||
|
standard.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Module Communication Directives:
|
|||
|
--------------------------------
|
|||
|
|
|||
|
Three directives, .GLOBAL, .PUBLIC, and .EXTERNAL, are used to
|
|||
|
communicate symbolic values across procedure, segment, and module boundaries
|
|||
|
(a module is any one source file which is assembled as a whole unit). The
|
|||
|
.GLOBAL directive is used to make symbols visible outside of procedures,
|
|||
|
macros, functions, and records. The .PUBLIC directive is used to make
|
|||
|
certain symbols visible outside the current module. The .EXTERNAL directive
|
|||
|
is used to make symbols defined outside the current module visible within
|
|||
|
the module.
|
|||
|
|
|||
|
The syntax for the .PUBLIC and .GLOBAL directives is identical, it
|
|||
|
takes the form:
|
|||
|
|
|||
|
.PUBLIC symbol1 {,symbol2, ..., symboln}
|
|||
|
and, .GLOBAL symbol1 {,symbol2, ..., symboln}
|
|||
|
|
|||
|
A label is not allowed in the label field of either mnemonic. The symbols
|
|||
|
specified in the operand field of these two instructions are made known
|
|||
|
outside the procedure or module where they currently reside. If a procedure
|
|||
|
is nested inside another, the .GLOBAL statement makes its symbols known
|
|||
|
only to the procedure encompassing the nested procedure. In the following
|
|||
|
example, LCL is known only inside procedure X1 and X2, not to the whole
|
|||
|
program:
|
|||
|
|
|||
|
X1 .PROC
|
|||
|
.
|
|||
|
.
|
|||
|
X2 .PROC
|
|||
|
.GLOBAL LCL
|
|||
|
.
|
|||
|
.
|
|||
|
.ENDP
|
|||
|
.ENDP
|
|||
|
|
|||
|
If you wanted to make LCL visible at the level above X1, then another
|
|||
|
.GLOBAL statement must appear inside the X1 procedure declaring LCL to
|
|||
|
be global to that procedure.
|
|||
|
|
|||
|
Another alternative is to use the .PUBLIC statement. Any symbol
|
|||
|
declared public with .PUBLIC is instantly visible throughout the program
|
|||
|
(within the confines of the scoping rules). However, keep in mind that
|
|||
|
symbols declared as public are visible outside the current module as well
|
|||
|
and may intefere with other modules.
|
|||
|
|
|||
|
The .EXTERNAL directive is used to obtain access to symbols declared
|
|||
|
outside the current module. The syntax for the .EXTERNAL directive is:
|
|||
|
|
|||
|
.EXTERNAL symbol1:type {,symbol2:type, ..., symboln:type}
|
|||
|
|
|||
|
Again, no label is allowed in the label field of the .EXTERNAL directive.
|
|||
|
The type item is any of NEAR, FAR, CONST, DIRECT, ABS, or LONG.
|
|||
|
|
|||
|
Note: symbols declared with "=", .MACRO, .RECORD, .SET, and .TYPE
|
|||
|
may not appear as operands to the .GLOBAL, .PUBLIC, or .EXTERNAL directives.
|
|||
|
|
|||
|
These directives are not required at the subset compliance level,
|
|||
|
only at the full and extended levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Segments:
|
|||
|
---------
|
|||
|
|
|||
|
Segments are used to group a collection of logically and physically
|
|||
|
related entities within a program. A segment may contain the program code,
|
|||
|
variables, stack area, direct page area, or other such data. Typically
|
|||
|
a segment is a load module. That is, a segment is loaded as a whole into
|
|||
|
memory. If a program consists of two or more segments, they need not all
|
|||
|
reside in memory at the same time. The memory manager/loader may load
|
|||
|
segments as needed into memory.
|
|||
|
|
|||
|
Segment definitions are required at all compliance levels. All
|
|||
|
programs must consist of at least one segment (this is a source of minor
|
|||
|
incompatibility with existing assemblers). The most general form of the
|
|||
|
segment definition is:
|
|||
|
|
|||
|
label .SEGMENT TYPE=expr {,ALIGN=expr} {,ORG=expr} {,NOCODE}
|
|||
|
|
|||
|
<segment body>
|
|||
|
|
|||
|
.ENDS
|
|||
|
|
|||
|
|
|||
|
.SEGMENT lets you declare any general type of segment. The symbol in the
|
|||
|
label field need not be unique, but if it is redefined elsewhere within the
|
|||
|
current scope, it must appear on a .SEGMENT definition whose type is exactly
|
|||
|
the same as the current definition.
|
|||
|
|
|||
|
Unlike .PROCs, .MACROs, etc., symbols defined inside a segment are
|
|||
|
not local to the segment, but are instantly visible to the reset of the
|
|||
|
module. If you need to declare local variables within a segment, use the
|
|||
|
.LOCAL and .RELEASE directives.
|
|||
|
|
|||
|
The type of segment must be specified in the .SEGMENT operand field.
|
|||
|
The actual segment types will be defined at a later date. For now, assume
|
|||
|
the types used by the Apple //GS loader are specified after the TYPE= item.
|
|||
|
The segment type describes the attributes of the segment, attributes such
|
|||
|
as whether the segment is relocatable or absolute, fixed or movable, etc.
|
|||
|
|
|||
|
The optional ALIGN operand is used to determine some number of bytes
|
|||
|
to which this segment (portion) must be aligned. If ALIGN=1 , the segment
|
|||
|
will be aligned on any byte boundary. If ALIGN=2 then the segment will be
|
|||
|
aligned on a word boundary, etc. Any value between 1 and $10000 can be used
|
|||
|
(ALIGN=$10000 will align the segment on a bank boundary).
|
|||
|
|
|||
|
The ORG=expr option can be used to fix the starting address of the
|
|||
|
segment. This option isn't normally used with code-generating segments.
|
|||
|
It's mainly used to define I/O port addresses and other absolute variables.
|
|||
|
|
|||
|
The NOCODE option is used to declare that a segment will not generate
|
|||
|
any code (i.e., it's just used to declare variables). If any 65c816 instruct-
|
|||
|
ion appears in a NOCODE segment, an error will be generated. All data
|
|||
|
declaring pseudo-opcodes (e.g., .BYTE) must specify indeterminate values else
|
|||
|
an error will be reported.
|
|||
|
|
|||
|
If multiple segments with the same name appear in a module (or
|
|||
|
across modules, for that matter), they will be combined into a single,
|
|||
|
contiguous module by the assembler and/or linker. Consider the following:
|
|||
|
|
|||
|
MyCode .SEGMENT Type=$1AF
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
.ENDS
|
|||
|
;
|
|||
|
MyData .SEGMENT Type=$100
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
.ENDS
|
|||
|
;
|
|||
|
MyCode .SEGMENT Type=$1AF
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
.ENDS
|
|||
|
|
|||
|
|
|||
|
Although MyCode appears in two completely disjoint areas, the assembler/linker
|
|||
|
will combine these items into a single segment. Segments appear in the
|
|||
|
load module in the order they are declared in the source file. In the
|
|||
|
example above, segment MyCode appears before segment MyData (even though
|
|||
|
a portion of MyCode appears after MyData, MyCode was still declared before
|
|||
|
MyData).
|
|||
|
|
|||
|
Segments may be nested, but they don't follow any scoping rules.
|
|||
|
Declaring one segment inside another is no different that declaring those
|
|||
|
two segments completely separate.
|
|||
|
|
|||
|
If you have two separate segments (different names but the same
|
|||
|
type), you can combine them together using the .GROUP directive. This
|
|||
|
directive takes the form
|
|||
|
|
|||
|
label .GROUP seg1, seg2 {,seg3, ..., segn}
|
|||
|
|
|||
|
Referring to "label" refers to the segment obtained by combining the
|
|||
|
segments in the .GROUP operand field.
|
|||
|
|
|||
|
To simply segment usage, there are six predeclared segments. They
|
|||
|
may be declared with the directives:
|
|||
|
|
|||
|
.CODE .DATA .DIRECT
|
|||
|
.STACK .VAR .CONST
|
|||
|
|
|||
|
.CODE is used to declare static, code-generating segments which allow
|
|||
|
65c816 instructions. .DATA is used to declare static data-generating
|
|||
|
segments. .CONST is identical to .DATA except data items inside the
|
|||
|
.CONST directive are read-only. Any attempt to write to items inside a
|
|||
|
.CONST segment should generate an error by the assembler or data flow
|
|||
|
analysis programs. .DIRECT is used to declare segments containing direct
|
|||
|
page variables. This is a NOCODE segment, so only definitions are allowed,
|
|||
|
initial values are illegal. .STACK segments are also NOCODE segments. They
|
|||
|
are useful for declaring stack space down in bank zero. The .VAR segment
|
|||
|
is used like the .DATA segment, except .VAR segments are NOCODE segments.
|
|||
|
They are used for declaring unintialized variables in main RAM.
|
|||
|
|
|||
|
The syntax for these six directives is
|
|||
|
|
|||
|
label .xxxx {ALIGN=expr | ORG=expr}
|
|||
|
|
|||
|
<segment body>
|
|||
|
|
|||
|
.ENDS
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
The ASSUME Directive
|
|||
|
--------------------
|
|||
|
|
|||
|
With the addition of the bank registers and the mode bits in the
|
|||
|
65c816 processor, an assembler can no longer determine the proper addressing
|
|||
|
mode to use in all circumstances without help from the programmer. For
|
|||
|
example, if the assembler encounters an instruction of the form "LDA Label"
|
|||
|
and Label is a statement label inside some segment (i.e., not declared with
|
|||
|
EDP, EQU, EQL, or other type-defining directive), it has no idea whether to
|
|||
|
use the direct, absolute, or long addressing mode. To do so would require
|
|||
|
that the assembler know the current values of the direct page and data bank
|
|||
|
registers at assembly time. Frankly, it is not possible for the assembler
|
|||
|
to always know the content of these registers, hence the programmer must
|
|||
|
manually supply this information to the assembler. This information, as well
|
|||
|
as some other useful information, is supplied to the assembler via the
|
|||
|
.ASSUME directive.
|
|||
|
|
|||
|
The .ASSUME directive uses the syntax:
|
|||
|
|
|||
|
.ASSUME operand1 {,operand2, ..., operandn}
|
|||
|
|
|||
|
where operand(i) is one of the following:
|
|||
|
|
|||
|
DBR:expression24
|
|||
|
DBR:NOTHING
|
|||
|
DP:expression16
|
|||
|
DP:NOTHING
|
|||
|
M:expression1
|
|||
|
M:NOTHING
|
|||
|
X:expression1
|
|||
|
X:NOTHING
|
|||
|
CPU:cpu_type
|
|||
|
|
|||
|
where expression24 is an expression yielding a 24-bit value, expression16 is
|
|||
|
an expression yielding a 16-bit value, expression1 is an expression yielding
|
|||
|
zero or one, NOTHING is a reserved word, and cpu_type is one of {6502, 65c02,
|
|||
|
65802, 65816} or one of the later versions of the 65c816 microprocessor.
|
|||
|
|
|||
|
DP (direct page) is used to let the assembler know where the direct
|
|||
|
page register is pointing. If a segment name is given as the expression,
|
|||
|
that segment must be one that resides in bank zero and is of type DIRECT.
|
|||
|
If the assembler encounters a symbol declared in a segment that is assumed
|
|||
|
to be a direct page segment via the DP:expression operand, the assembler will
|
|||
|
reference that location using the direct page addressing mode (if posssible).
|
|||
|
If the "DP:NOTHING" form is used, the assembler will only use the direct page
|
|||
|
addressing mode if a symbol was declared with the EDP equate. None of the
|
|||
|
segments will be treated as direct page segments, even if they were declared
|
|||
|
as type DIRECT. If you want to simultaneously refer to several segments as
|
|||
|
direct page segments, group them together using the .GROUP directive and
|
|||
|
specify the group name as the expression value after the DP:, i.e.,
|
|||
|
|
|||
|
DPGroup .GROUP DPSeg1, DPSeg2, DPSeg3
|
|||
|
.ASSUME DP:DPGroup
|
|||
|
|
|||
|
By default, the assembler should assume DP:NOTHING.
|
|||
|
|
|||
|
|
|||
|
DBR is used to tell the assembler which segment/bank the DBR (data
|
|||
|
bank register) points at. References to variables within that segment will
|
|||
|
be assembled as absolute references (unless that segment name is also
|
|||
|
specified after DP:expr, in which case the direct page addressing mode will
|
|||
|
be used, if possible). If DBR:NOTHING is specified, absolute addressing will
|
|||
|
be used only for those symbols declared via EQU, all other references will
|
|||
|
be assumed to be long references. Note that the H.O. eight bits of the
|
|||
|
24-bit expression are used. Therefore, to set the DBR assumption to an
|
|||
|
absolute bank in memory, an expression of the form:
|
|||
|
|
|||
|
.ASSUME DBR:$200000 ;Assume DBR=$20
|
|||
|
|
|||
|
must be used. By default, the assembler should assume DBR:NOTHING.
|
|||
|
|
|||
|
Normally, a programmer should use "#" and "=" to specify eight or
|
|||
|
sixteen bit immediate operand sizes. To help ensure upwards compatibility
|
|||
|
with existing source code, a mechanism has been added whereby the "#" is
|
|||
|
used and the .ASSUME directive controls the size of immediate operands. This
|
|||
|
task is achieved using the M:expr and X:expr operands. Normally the assembler
|
|||
|
defaults to M:NOTHING and X:NOTHING. In this mode, "#" specifies 8-bit
|
|||
|
immediate operands and "=" specifies 16-bit operands. If the expression
|
|||
|
following the M or X is zero or one, then any immediate operand containing
|
|||
|
an equal sign is flagged as an error and the "#" specifies an eight-bit
|
|||
|
operand if the expression was 1, a sixteen-bit operand if the expression was
|
|||
|
zero. If the expression evaluates to any other value an error is generated.
|
|||
|
Note that M only affects accumulator and memory operations while X affects
|
|||
|
the index register operations. It is perfectly permissible to have an
|
|||
|
.ASSUME of the form:
|
|||
|
|
|||
|
.ASSUME M:NOTHING,X:1
|
|||
|
|
|||
|
The "=" immediate specifier would be allowed for accumulator operations but
|
|||
|
not for X/Y index register operations.
|
|||
|
|
|||
|
To help ensure compatibility with the existing defacto standard,
|
|||
|
LONGI, LONGA, SHORTI, and SHORTA should be provided as built-in macros
|
|||
|
generating the appropriate .ASSUME statement.
|
|||
|
|
|||
|
The "CPU:cpu_type" operand to the .ASSUME statement lets users
|
|||
|
specify the exact 6500 family CPU they are using. The effect of this
|
|||
|
operand is to "disconnect" certain instructions. If a certain CPU is
|
|||
|
specified and a programmer uses an addressing mode or instruction which isn't
|
|||
|
available on that CPU, the assembler will generate an error. By default,
|
|||
|
the assembler should assume the CPU of the machine on which the assembler
|
|||
|
is intended to run (e.g., 65c816 for Apple //GS machines). If the assembler
|
|||
|
is running on a different processor other than a 6500 family chip, it should
|
|||
|
default to 65c816. The user should be able to choose this default value
|
|||
|
from an assembler set-up program.
|
|||
|
|
|||
|
The .ASSUME directive, and all operands available to it, must be
|
|||
|
supported at all compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Local Symbols
|
|||
|
-------------
|
|||
|
|
|||
|
In addition to local labels automatically specified inside procedures,
|
|||
|
macros, and expression functions, you can also explicitly declare local sym-
|
|||
|
bols within the source file. User-defined local symbols come in two
|
|||
|
varieties: numeric and symbolic.
|
|||
|
|
|||
|
Up to 10 active numeric local labels can be specified at any given
|
|||
|
time. The numeric local labels are similar to those used by D. E. Knuth
|
|||
|
in "The Art of Computer Programming, Vol 1", although the syntax is different.
|
|||
|
Numeric local labels are declared by placing a caret (up-arrow) in front of
|
|||
|
a single decimal digit in the label field. Examples follow:
|
|||
|
|
|||
|
^0 LDX #05
|
|||
|
^9 DEX
|
|||
|
^4 LDA LBL
|
|||
|
|
|||
|
Numeric local labels are referenced with the ">n" and "<n" items, where "n"
|
|||
|
represents a single decimal digit. If the greater than symbol prefaces a
|
|||
|
digit, then the next occurrence of that numeric local label in the source
|
|||
|
file is referenced. If a less than ("<") symbol is used, then the previous
|
|||
|
numeric local label is used. Examples:
|
|||
|
|
|||
|
|
|||
|
LDX #5
|
|||
|
^0 CLR Array,X
|
|||
|
DEX
|
|||
|
BPL <0 ;References 2nd line above.
|
|||
|
;
|
|||
|
LDA Array+2
|
|||
|
bne >0 ;References ^0 below.
|
|||
|
TXA
|
|||
|
^0 STA Array+1
|
|||
|
|
|||
|
Note that multiple occurrences of the same numeric local label may appear
|
|||
|
within the program. The are differentiated by the "<" and ">" symbols.
|
|||
|
|
|||
|
Since "<" and ">" may appear both as operators and as the beginning
|
|||
|
of an operand, a minor ambiguity results. If you see a portion of an ex-
|
|||
|
pression like ">0", does it mean 'is some value greater than zero' or does
|
|||
|
it refer to the next occurrence of "^0"? This is easily handled from context.
|
|||
|
If the ">" or "<" appears where an operator is expected, then the appropriate
|
|||
|
operation is performed. If they appear where an operand is expected and they
|
|||
|
are followed by a single decimal digit, then they are used as lead-ins for
|
|||
|
numeric local labels. Otherwise an error must be generated.
|
|||
|
|
|||
|
Numeric local labels are great for those cases where you need to
|
|||
|
perform a short branch or to set up a small loop and you don't want to use
|
|||
|
meaningless mnemonics like "loop1", "SkipInstr12", etc. Other times, you
|
|||
|
may want to use a meaningful name like "MainLoop" or "ElseQuit", without
|
|||
|
having to worry about conflicts in other parts of the program. Such cases
|
|||
|
are easily handled by the symbolic local label facility specified by this
|
|||
|
proposal. Two assembler directives: .LOCAL and .RELEASE are used to define
|
|||
|
the scope of user-specified local labels. The syntax for these two directives
|
|||
|
is identical, it is:
|
|||
|
|
|||
|
.LOCAL label1 {,label2, ..., labeln}
|
|||
|
.RELEASE label1 {,label2, ..., labeln}
|
|||
|
|
|||
|
A label defined with .LOCAL is confined to the scope of the .LOCAL/.RELEASE
|
|||
|
pair. .LOCAL/.RELEASE pairs may be nested allowing you to redefine a symbol
|
|||
|
to any reasonable depth (say, a minimum of 8 levels).
|
|||
|
|
|||
|
Numeric local labels are required at all compliance levels. Symbolic
|
|||
|
local labels are required at the full and extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Conditional Assembly
|
|||
|
--------------------
|
|||
|
|
|||
|
Conditional assembly is handled by the .IF, .ELSE, .IF1, .IF2,
|
|||
|
.IFDEF, .IFNDEF, and .ENDIF directives. .IF is followed by a numeric
|
|||
|
address expression that yields a zero (false) or non-zero (true) result.
|
|||
|
The following code (up to the .ELSE or .ENDIF) is assembled if the result is
|
|||
|
true. Otherwise the code after the .ELSE (if it is present) is assembled in
|
|||
|
its stead. .IF1 and .IF2 assemble their respective code during passes
|
|||
|
one and two. .IFDEF and .IFNDEF accept a single symbol as their parameter
|
|||
|
and test whether or not this symbol is currently defined. The .ELSE
|
|||
|
directive can be used to assemble additional code in the event the tested
|
|||
|
condition is false. Finally, the .ENDIF directive is used to terminate
|
|||
|
a conditional assembly sequence.
|
|||
|
|
|||
|
Conditional assembly blocks can be nested to at least eight levels,
|
|||
|
preferably more. Since all conditional assembly blocks are terminated with
|
|||
|
.ENDIF, there is no need to worry about matching .ELSEs as you would, say,
|
|||
|
in Pascal. Every form of the IF statement is terminated with its own .ENDIF.
|
|||
|
|
|||
|
The .IF1 and .IF2 directives are normally used to print messages and
|
|||
|
perform other minor housekeeping chores. In general, there's absolutely no
|
|||
|
reason why anyone would want to generate code inside one of these conditional
|
|||
|
assembly blocks. Therefore, the assembler may optionally generate an error
|
|||
|
message if the location counter is modified anywhere inside the .IF1/.IF2
|
|||
|
conditional assembly block.
|
|||
|
|
|||
|
.IF, .ELSE, and .ENDIF are required at all compliance levels. .IF1,
|
|||
|
.IF2, .IFDEF, and .IFNDEF are required only at the full and extended com-
|
|||
|
pliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
While Loops
|
|||
|
-----------
|
|||
|
|
|||
|
Sometimes, especially within macros, you will need some sort of
|
|||
|
looping structure to process parameters or otherwise generate sequences of
|
|||
|
code; the .WHILE/.ENDW directives are used for this purpose. The syntax
|
|||
|
for the while section is:
|
|||
|
|
|||
|
.WHILE expression
|
|||
|
<body of loop>
|
|||
|
.ENDW
|
|||
|
|
|||
|
The instructions in the loop body are repeated as long as the expression
|
|||
|
yields a non-zero value. For the loop to terminate, the variable(s)
|
|||
|
controlling the loop must be defined using the "=" assembler directive
|
|||
|
since this is the only directive that allows you to redefine an instance
|
|||
|
of a variable.
|
|||
|
|
|||
|
The .WHILE directive is especially useful for processing a macro
|
|||
|
(or record definition) with a variable number of parameters. Consider the
|
|||
|
following macro:
|
|||
|
|
|||
|
ByteTable .MACRO
|
|||
|
ParmCnt = ?#
|
|||
|
.WHILE ParmCnt
|
|||
|
.BYTE ?:(?#-ParmCnt)
|
|||
|
ParmCnt = ParmCnt-1
|
|||
|
.ENDW
|
|||
|
.ENDM
|
|||
|
|
|||
|
_ByteTable 0,5,4,2,7
|
|||
|
|
|||
|
This example emits the five bytes 0, 5, 4, 2, and 7 into the object code
|
|||
|
stream.
|
|||
|
|
|||
|
INCLUDE Mechanism
|
|||
|
-----------------
|
|||
|
|
|||
|
A source file include mechanism is provided by the .INCLUDE directive.
|
|||
|
Its syntax is
|
|||
|
|
|||
|
.INCLUDE "filename"
|
|||
|
|
|||
|
The specified file will be inserted at the point of the .INCLUDE directive
|
|||
|
in the current assembly, as though the code were actually inserted at that
|
|||
|
point.
|
|||
|
|
|||
|
The include mechanism must be capable of nested includes up to four
|
|||
|
levels deep. The .INCLUDE directive must be supported at all compliance
|
|||
|
levels of the assembler, although assemblers operating at the subset
|
|||
|
compliance level need not support nested include files.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Programs, Modules, and Units
|
|||
|
----------------------------
|
|||
|
|
|||
|
The assembler handles three types of sources files: programs, modules,
|
|||
|
and units. Unless otherwise specified, all source files are assumed to be
|
|||
|
programs. A program is differentiated from a module or unit in that the
|
|||
|
assembler/linker assumes that control is transferred to some point in a
|
|||
|
'program' when it is loaded into memory. Modules and units are assumed to
|
|||
|
be subserviant sections of code that contain data and/or code used by
|
|||
|
programs.
|
|||
|
|
|||
|
By default, a piece of code is assumed to be a program and control
|
|||
|
is transferred to the first byte of that code when the program is loaded
|
|||
|
into memory. This helps improve compatibility with existing source files.
|
|||
|
The .PROGRAM directive can be used to explicitly declare a piece of code as
|
|||
|
a main program, as well as provide an entry address other than the first byte
|
|||
|
of code emitted. The syntax for the .PROGRAM directive is
|
|||
|
|
|||
|
.PROGRAM label
|
|||
|
|
|||
|
where "label" is a program statement label somewhere within the current
|
|||
|
assembly. The address of this label is passed on to the linker/loader where
|
|||
|
it will be used to provide a starting address for the code. All of the
|
|||
|
statements in the source file will be assembled into the program from the
|
|||
|
.PROGRAM directive till the .END directive. If a .PROGRAM directive appears
|
|||
|
in the source file, it must appear before any other statement (other than a
|
|||
|
comment or listing directive) and there may only be one .PROGRAM directive
|
|||
|
encountered per assembly. No modules or units may appear as part of a
|
|||
|
program assembly (see below).
|
|||
|
|
|||
|
The .MODULE directive is used to tell the assembler that it is
|
|||
|
assemblying an object code module which is to be linked into a separate
|
|||
|
program before execution. The .PUBLIC statement is used as the means
|
|||
|
to communicate linkage information to other modules, units, and programs.
|
|||
|
Like the .PROGRAM directive, the .MODULE directive must appear before most
|
|||
|
statements in the source file and the module is terminated with the .END
|
|||
|
directive. However, another module may appear in the source file immediately
|
|||
|
after the .END directive. Such modules are assembled as independent entries
|
|||
|
in a library. The syntax for the .MODULE directive is:
|
|||
|
|
|||
|
.MODULE ModuleName
|
|||
|
|
|||
|
The module name operand is stored as part of the source file for use by the
|
|||
|
linker, but is not otherwise refereced during the assembly process. In fact,
|
|||
|
this symbol may be redefined later in the source file.
|
|||
|
|
|||
|
The .LINK directive can be used to link a module into another module,
|
|||
|
unit, or program at assembly time. The syntax for this directive is:
|
|||
|
|
|||
|
.LINK "filename",ModuleName
|
|||
|
|
|||
|
where filename is the operating system name of the object code file or
|
|||
|
library file containing the module, and ModuleName is the actual module
|
|||
|
name specified with the .MODULE directive. The specified object code is
|
|||
|
inserted into the assembly at the point of the .LINK directive. Access to
|
|||
|
the symbols declared public within the module is accomplished using the
|
|||
|
.EXTERNAL directive.
|
|||
|
|
|||
|
Units are a much more structured form of modules. With a unit,
|
|||
|
you specify not only the symbols visible to the code using the unit, but
|
|||
|
also how that data is used. Units also allow you to pass type checking
|
|||
|
information so the assembler can check for possible logical errors during
|
|||
|
assembly. Finally, as an added bonus, within units you can link in macros,
|
|||
|
records, types, symbols defined by "=", and other entities that cannot be
|
|||
|
handled by modules and the .PUBLIC/.EXTERNAL mechanism.
|
|||
|
|
|||
|
A unit takes the form:
|
|||
|
|
|||
|
.UNIT UnitName
|
|||
|
|
|||
|
<interface section>
|
|||
|
|
|||
|
.BEGIN
|
|||
|
|
|||
|
<implementation section>
|
|||
|
|
|||
|
.END
|
|||
|
|
|||
|
Like .MODULEs, several units may appear in the source file by simply following
|
|||
|
the .END directive with the next unit definition. In fact, .MODULEs and
|
|||
|
.UNITs can be intermixed in the same source file. If more than one module or
|
|||
|
unit appears in the source file, they will be assembled into different slots
|
|||
|
in the object file generated (i.e., a library file will be generated).
|
|||
|
|
|||
|
The interface section of a unit contains those items that will be
|
|||
|
public to the unit. Equates, records, macros, types, sets, and any other
|
|||
|
non-code generating declaration can be used in the interface section (note:
|
|||
|
an exact list of items will be specified later). Such definitions will be
|
|||
|
made available to the code that uses this unit as well as to the code in the
|
|||
|
implementation section. In addition to such declarations, the interface
|
|||
|
section may also contain .PROC definitions and .ENTRY definitions. The
|
|||
|
.PROC definitions simply contain the .PROC statement (which must also appear
|
|||
|
in the implementation section), the .ENTRY definition is used in lieu of the
|
|||
|
.PUBLIC directive and takes the form:
|
|||
|
|
|||
|
label .ENTRY {NEAR or FAR}
|
|||
|
|
|||
|
An example of a simple unit might be:
|
|||
|
|
|||
|
.UNIT SimpleUnit
|
|||
|
MyMac .macro
|
|||
|
lda #0
|
|||
|
sta ?0
|
|||
|
.endm
|
|||
|
;
|
|||
|
ClrSub .proc near
|
|||
|
SetTrue .proc far
|
|||
|
SetIt .entry far
|
|||
|
;
|
|||
|
.BEGIN ;Start implemenation section.
|
|||
|
;
|
|||
|
ClrSub .proc near
|
|||
|
_MyMac $11
|
|||
|
ret
|
|||
|
.endp
|
|||
|
;
|
|||
|
SetTrue .proc far
|
|||
|
lda #1
|
|||
|
SetIt sta $23
|
|||
|
ret
|
|||
|
.endp
|
|||
|
.end
|
|||
|
|
|||
|
|
|||
|
To use the code defined in a unit, the ".USE" directive is used in
|
|||
|
a fashion not unlike the .LINK directive, namely,
|
|||
|
|
|||
|
.USE "filename",UnitName
|
|||
|
|
|||
|
where filename is the operating system pathname and UnitName is the name
|
|||
|
specified in the operand field of the unit directive. Whenever the .USE
|
|||
|
directive appears in a source file, the content of the implementation section
|
|||
|
will be listed if the source listing option is turned on.
|
|||
|
|
|||
|
Whenever the .USE or .LINK directives are employed, the corresponding
|
|||
|
object code is always inserted into the assembly. Therefore the assembler
|
|||
|
is performing double duty, it's acting as both the assembler and linker.
|
|||
|
With units, the assembler always performs the link operation. With modules,
|
|||
|
you can defer the link operation to a separate linkage step, although there
|
|||
|
are only a few instances where this would be beneficial (for example, while
|
|||
|
creating libraries).
|
|||
|
|
|||
|
All of the program linkage directives are optional at the subset
|
|||
|
compliance level, but required at the full and extended levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Listing Controls
|
|||
|
----------------
|
|||
|
|
|||
|
Several directives are used to control the appearence of the assem-
|
|||
|
bled source listing. The exact format of the listing will be specified with-
|
|||
|
in this proposal (although at a later date). The exact listing format must
|
|||
|
be adhered to so that symbolic debuggers can take advantage of an assembled
|
|||
|
source listing saved as a text file for use when stepping through a program.
|
|||
|
|
|||
|
.ON operands
|
|||
|
.OFF operands
|
|||
|
|
|||
|
These two directives are used to turn certain listing options or
|
|||
|
or off. Valid operands include LIST, OBJ, MAC, and COND. LIST controls
|
|||
|
whether or not the source file is listed and supercedes all other options.
|
|||
|
OBJ (if on) will force the assembler to display all bytes of object code
|
|||
|
emitted by an instruction, even if it takes more than one line to display it
|
|||
|
all; if off, OBJ will only display the number of emitted object code bytes
|
|||
|
that fit on the current source line. MAC controls macro expansions during
|
|||
|
the listing. If off, only the macro name, not the expansion, will be dis-
|
|||
|
played. COND controls the printing of statements in a false conditional or
|
|||
|
while loop section.
|
|||
|
|
|||
|
The .TITLE and .SUBTITLE directives let you assign titles and sub-
|
|||
|
titles to the source file. The syntax for these directives is
|
|||
|
|
|||
|
.TITLE "Title of source file"
|
|||
|
.SUBTITLE "Subtitle for this section"
|
|||
|
|
|||
|
The title is displayed at the top of each page and the subtitle is displayed
|
|||
|
immediately below the title. .TITLE always forces a page eject, .SUBTITLE
|
|||
|
never does.
|
|||
|
|
|||
|
The .PAGE directive forces an immediate page ejection on the listing.
|
|||
|
It requires no operands.
|
|||
|
|
|||
|
The .PRINTF directive has the syntax:
|
|||
|
|
|||
|
.PRINTF "Control string" {,operands}
|
|||
|
|
|||
|
It is used in a manner analogous to the PRINTF in the "C" programming
|
|||
|
language. If expressions follow the control string, "%" modifiers in the
|
|||
|
control string specify their output format. E.g.,
|
|||
|
|
|||
|
.PRINTF "Label = $%4h",Label
|
|||
|
|
|||
|
would print
|
|||
|
|
|||
|
Label = $1234
|
|||
|
|
|||
|
assuming the value associated with Label was $1234.
|
|||
|
|
|||
|
|
|||
|
The .PAUSE directive can be used to force an assembly time error.
|
|||
|
It is useful mainly in macros, records, expression functions, etc. to force
|
|||
|
an error if an illegal condition (like bad number of parameters) occurs.
|
|||
|
|
|||
|
The listing control directives are required only at the full and
|
|||
|
extended compliance levels.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Data Flow Analysis Directives
|
|||
|
-----------------------------
|
|||
|
|
|||
|
The following directives are quite useful to add-on debuggers and
|
|||
|
data flow analysis programs. They are required only at the full and extended
|
|||
|
compliance levels:
|
|||
|
|
|||
|
label .table
|
|||
|
<data table>
|
|||
|
.endt
|
|||
|
|
|||
|
For .table, the label is assigned the current value of the location counter
|
|||
|
and label is treated like a statement label. .TABLE and .ENDT are otherwise
|
|||
|
ignored.
|
|||
|
|
|||
|
label .REF label1 {, label2, ..., labeln}
|
|||
|
|
|||
|
This statement is ignored by the assembler. The statement label, if present,
|
|||
|
is also ignored.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Other Optional Goodies
|
|||
|
----------------------
|
|||
|
|
|||
|
The following are not required by this proposal, but should be
|
|||
|
provided nonetheless:
|
|||
|
|
|||
|
.system "DOS command"
|
|||
|
|
|||
|
.SYSTEM issues the specified command to the operating system. This command
|
|||
|
is useful for deleteing files during assembly, changing directories, etc.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Operation of the Assembler
|
|||
|
--------------------------
|
|||
|
|
|||
|
Given the structure of the assembler, there's no way it can accomplish
|
|||
|
its job in less than three passes without placing severe burdens on the
|
|||
|
user (I could provide you with a mathematical proof of this, but I don't want
|
|||
|
to bore you to death). Therefore, the standard specifies that the assembler
|
|||
|
must use three (or more) passes to do its job. During the first pass the
|
|||
|
assembler associates labels with segments (and groups of segments), determines
|
|||
|
whether or not those symbols are near or far, and performs other housekeeping
|
|||
|
chores fit for pass one. Pass two of the assembler is equivalent to the
|
|||
|
traditional pass one of an assembler, it computes the values for all of the
|
|||
|
symbols in the program. Pass three generates the actual object code.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
In Addition to the Assembler...
|
|||
|
-------------------------------
|
|||
|
|
|||
|
The standard should also include specifications for a run-time
|
|||
|
library to be provided with the assembler as well as a list of tools
|
|||
|
(e.g., debugger, linker, librarian, etc.) which must be provided with the
|
|||
|
product to meet the full compliance level. I would like to propose the
|
|||
|
following items in the run-time library:
|
|||
|
|
|||
|
TTY_IO: A set of routines to communicate with a text-based
|
|||
|
user console. INIT, GETC, and PUTC are the basic routines. These three
|
|||
|
routines are easily supported on any system supporting a user console.
|
|||
|
|
|||
|
TERMINAL_IO: A set of routines to communicate with a cursor-based
|
|||
|
terminal device. Routines supported should include INIT, GETC, PUTC, GOTOXY,
|
|||
|
HOME, CLREOLN, and CLREOP.
|
|||
|
|
|||
|
CONSOLE_IO: A set of routines to communicate with a DMA-based video
|
|||
|
display device. See the specifications for ANIX's CHARIO driver for the
|
|||
|
routines to be supplied with this library entry.
|
|||
|
|
|||
|
AUX_IO: A driver for a set of one or more serial communication ports.
|
|||
|
Routines should include INITA, SETUPA, GETA, PUTA, STATUSA.
|
|||
|
|
|||
|
PRT_IO: A driver for a set of one or more printer ports. Routines
|
|||
|
should include INITP, SETUPP, PUTP, and STATUSP.
|
|||
|
|
|||
|
NET_IO: A driver for a set of one or more network ports. Routines
|
|||
|
should include INITN, SETUPN, GETPacket, SendPacket, etc.
|
|||
|
|
|||
|
CLK_IO: A driver for a real time clock or clock-calendar unit.
|
|||
|
|
|||
|
FP: An IEEE floating point package for the 65c816 chip.
|
|||
|
|
|||
|
MATH: A set of integer math routines (multiply, divide, extended
|
|||
|
precision, etc.).
|
|||
|
|
|||
|
CONV: A set of conversion routines (binary -> decimal, etc.).
|
|||
|
|
|||
|
FILE_IO: A set of routines that interface to the host's operating
|
|||
|
system providing a common interface to various operating systems.
|
|||
|
|
|||
|
DVC_IO: A hardware independent device I/O package (allowing named
|
|||
|
devices which can be connected through a BIOS (like the AUX_IO and PRT_IO
|
|||
|
packages) to various hardware devices.
|
|||
|
|
|||
|
STD_IO: A set of routines to perform various I/O operations such
|
|||
|
as PRINT, PRINTF, SCANF, PUTI (integer), GETI, PUTH (hex), GETH, etc.
|
|||
|
|
|||
|
MEM_MGR: A set of memory management routines to efficiently allocate
|
|||
|
and deallocate memory.
|
|||
|
|
|||
|
|
|||
|
This is, by no means, an exhaustive list, but a quick sample of the types of
|
|||
|
routines that should be provided.
|
|||
|
|
|||
|
Apple //GS users may complain that many of these routines already
|
|||
|
exist within the confines of the Apple toolbox. The intent, however, is to
|
|||
|
provide a set of useful routines that can be utilized on ANY 65c816 system
|
|||
|
so 65c816 code can be easily ported to systems other than the Apple //GS.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|