MADS is oriented toward users of QA, XASM, and FA. The syntax is borrowed from QA, some macro commands and syntax come from XASM, and SpartaDOS X (SDX) syntax is inherited from FA. Additional characters are allowed in label names. Also added are support for the 65816 CPU, macros, procedures, splitting across virtual memory banks, and nested label names.
The number of labels and macros are limited only by the memory in your PC. Specifically, you can have 2147483647 (INTEGER) table entries. I'm sure this amount is sufficient. :)
Arithmetic operations are done with values of type INT64 (signed 64 bit), with the result represented with a value of type CARDINAL (unsigned 32 bit).
A line can be up to 65535 bytes, which is also the length limit of a label name. However, I have not had the opportunity to check labels as long as poems. :)
With the free compiler Free Pascal Compiler, it is possible to compile MADS for other platforms, such as Linux, Mac, OS/2, etc. For more information on how to build, see the chapter on Compilation. For more information on XASM check its home page: http://atariarea.krap.pl/x-asm/.
To compile MADS source, you can use the Delphi compiler, if Delphi 7.0 or later is installed. Another, more multi-platform way is to use the compiler package from the Free Pascal Compiler (FPC), which can be downloaded from: http://www.freepascal.org/
Run the installer, and select the folder in which you installed FPC. It is important not to use a directory name which contains '!' or other special characters, or else you will not be able to compile any files, and it should probably not be a standard path name. The command-line to start compilation could look like this (the case in the parameter names is important):
fpc -Mdelphi -v mads.pas
-Mdelphi compile in Delphi mode
-v show all errors and warnings
-O3 enable code optimization
Compared to the Delphi compiler, FPC generates bigger code,
but the speed of the compiled MADS is much faster, even by a few seconds.
The included MADS.EXE is compiled using FPC.
lda #' ' lda #" "MADS distinguishes between single quotes (ATASCII coding) and double quotes (INTERNAL coding), whereas XASM treats both forms the same (ATASCII coding). Of course, the quote types are treated the same in DTA by MADS.
lda $2000,x+ -> lda $2000,x
inx
but if the '+' or '-' sign is used with a value, it change the value of the main operand instead
(this only works with absolute indexed addressing):
lda $2000,x+2 -> lda $2002,x
Syntax: mads source [switches] -b:address Generate binary file at specific address -c Label case sensitivity -d:label=value Define a label -f CPU command at first column -hc[:filename] Header file for CC65 -hm[:filename] Header file for MADS -i:path Additional include directories -l[:filename] Generate listing -m:filename File with macro definition -o:filename Set object file name -p Print fully qualified file names in listing and error messages -s Silent mode -t[:filename] List label table -x Exclude unreferenced procedures -vu Verify code inside unreferenced procedures -u Warn of unused labels
The default filenames are:
source.lst source.obx source.lab source.h source.hea source.mac
If no extension is specified for the source file, MADS will default to the extension .ASM.
Switches can be given in any order, preceded by '/' or '-', and case does not matter. Also, switches can be combined:mads -lptd:label=value -d:label2=value source.asm mads -l -p -t source mads source.asm -lpt mads.exe "%1" -ltpi:"d:\!atari\macro\" mads -i:"c:\atari\macros\" -c source.asm -lptBy default, after assembly MADS will save the output with the extension '.OBX', which can be changed with a batch file:
mads "%1" -o:%~n1.xexYou can learn more about the operators by entering "CALL /?" in the Microsoft Windows command shell.
-b:address
Using the -b switch allows specification of a target address for a file that
does not specify an address itself via the the ORG pseudo-command.
-c
The -c switch causes label, variable, and constant names to be treated as
case-sensitive. Assembler directives and 6502/65816 CPU instructions are always recognized
regardless of case.
-d:label=value
Use the -d switch to define a new label in MADS memory from
the command-line. This switch can be used multiple times in a single invocation of MADS,
which is useful when assembling from a batch (*.BAT) file.
-f
Use the -f switch to allow CPU instructions to be recognized in the first column instead of
just labels.
-hc[:filename]
The switch -hc saves a header file for the CC65 compiler.
It also lets you specify a new name for the file. The default extension for the CC65
header file is *.H.
-hm[:filename]
The switch -hm saves a header file for MADS. It also lets you specify
a new name for the file. The default extension for the MADS is *.HEA.
Such a file contains information about the banks and values assigned to labels. Additionally,
the labels are grouped by their types: CONSTANTS, VARIABLES, PROCEDURES.
Example: mads -i:"D:\program files" -i:C:\temp -i:"D:\atari project" ...
-l:filename
The -l switch enables writing of a listing file. It also lets you specify a new name for the file.
-m:filename
The -m switch specifies a file with macro definitions, which MADS
assembles before the main *.ASM file.
-o:filename
The -o switch specifies a new name for the
Atari DOS or SpartaDOS X executable file, which is created at the end of assembly.
Example: D:\!Delphi\Masm\test.asm (29) ERROR: Missing .PROCNow just double-click the message line, and the cursor in the editor will move to the line with the error. In WUDSN IDE, error messages are added automatically to the Problems view with the correct line number.
-s
Use the -s switch to activate the so-called 'Silent mode', where no messages will be displayed,
only errors (ERROR) and warnings (WARNING).
-t[:filename]
The -t switch writes a file with referenced label definitions. It also lets you specify an
alternate name for the file.
-x
The -x switch skips assembly of unreferenced procedures defined with the .PROC directive.
-vu
The -vu switch enables verification of code inside procedures even if they are not emitted, usually used in conjunction with -x.
-u
The -u switch lists unreferenced labels in the program.
0 = no errors 2 = error occurred 3 = bad parameters, assembling not started
3 4 = 01,9033 picture equ $9033 5 = 01,00A0 scr1 equ $a0 6 7 8 01,2000 EA main nop
As with XASM, the *.LAB file stores information about labels in the program.
There are three columns:$FFF9 label for parameter in procedure defined by .PROC $FFFA label for array defined by .ARRAY $FFFB label for structured data defined by the pseudo-command DTA STRUCT_LABEL $FFFC label for SpartaDOS X symbol defined by SMB $FFFD label for macro defined by .MACRO directive $FFFE label for structure defined by .STRUCT directive $FFFF label for procedure defined by .PROC directive
Characters with special meanings in label names:
The numeric value after :: is the number of the macro call.
Mad-Assembler v1.4.2beta by TeBe/Madteam Label table: 00 0400 @STACK_ADDRESS 00 00FF @STACK_POINTER 00 2000 MAIN 00 2019 LOOP 00 201C LOOP::1 00 201C LHEX 00 0080 LHEX.HLP 00 204C LHEX.THEX 00 205C HEX 00 205C HEX.@GETPAR0.LOOP 00 2079 HEX.@GETPAR1.LOOP
#ifndef _TEST_ASM_H_ #define _TEST_ASM_H_ #define TEST_CPU65816 0x200F #define TEST_CPU6502 0x2017 #define TEST_TEXT6502 0x201F #define TEST_TEXT65816 0x2024 #endif
REQ, RNE, RPL, RMI, RCC, RCS, RVC, RVS SEQ, SNE, SPL, SMI, SCC, SCS, SVC, SVS JEQ, JNE, JPL, JMI, JCC, JCS, JVC, JVS ADD, SUB ADB, SBB ADW, SBW PHR, PLR INW, INL, IND, DEW, DEL, DED MVA, MVX, MVY MWA, MWX, MWY CPB, CPW, CPL, CPD
lda:cmp:req 20 -> lda 20
-> wait cmp 20
-> beq wait
ldx #0 -> ldx #0
mva:rne $500,x $600,x+ -> loop lda $500,x
-> sta $600,x
-> inx
-> bne loop
lda #40 -> lda #40
add:sta $80 -> clc
scc:inc $81 -> adc $80
-> sta $80
-> bcc skip
-> inc $81
-> skip
jne dest -> beq *+4
-> jmp dest
add -> clc sub -> sec
-> adc ... -> sbc ...
adb src #$40 -> lda src adb a b c -> lda a
-> clc -> clc
-> adc #$40 -> adc b
-> sta src -> sta c
sbb src #$80 -> lda src sbb a b c -> lda a
-> sec -> sec
-> sbc #$80 -> sbc b
-> sta src -> sta c
adw src #$40 -> clc adw a b c -> clc
-> lda src -> lda a
-> adc #$40 -> adc b
-> sta src -> sta c
-> scc -> lda a+1
-> inc src+1 -> adc b+1
-> sta c+1
adw src #$40 src -> clc
-> lda src
-> adc #$40
-> sta src
-> lda src+1
-> adc #$00
-> sta src+1
sbw src #$4080 -> sec sbw a b c -> sec
-> lda src -> lda a
-> sbc <$4080 -> sbc b
-> sta src -> sta c
-> lda src+1 -> lda a+1
-> sbc >$4080 -> sbc b+1
-> sta src+1 -> sta c+1
phr -> pha plr -> pla
-> txa -> tay
-> pha -> pla
-> tya -> tax
-> pha -> pla
The macro commands DEW, DEL, and DED decrement 16-bit (word), 24-bit (long), and 32-bit (dword) memory locations. The accumulator is also changed after the decrement macro commands.
inw dest -> inc dest -> inc dest
-> bne skip -> sne
-> inc dest+1 -> inc dest+1
-> skip ->
dew dest -> lda dest -> lda dest
-> bne skip -> sne
-> dec dest+1 -> dec dest+1
-> skip dec dest -> dec dest
lda src -> mva src dst sta dst -> ldy $10,x -> mvy $10,x $a0,x sty $a0,x -> ldx #$10 -> mvx #$10 dst stx dst ->
ldx <adr -> mwx #adr dst
stx dst ->
ldx >adr ->
stx dst+1 ->
mwa #0 $80 -> lda #0 mwy #$3040 $80 -> ldy <$3040
-> sta $80 -> sty $80
-> sta $81 -> ldy >$3040
-> sty $81
mwa ($80),y $a000,x -> lda ($80),y
-> sta $a000,x
-> iny
-> lda ($80),y
-> sta $a001,x
cpw temp #$4080 bcc skip cpd v0 v1 beq skip
IFT [.IF] expression ELS [.ELSE] ELI [.ELSEIF] expression EIF [.ENDIF] ERT ERT 'string'["string"] | ERT expression label EQU expression label = expression label SET expression label EXT type OPT [bcfhlmorst][+-] ORG [[expression]]address[,address2] INS 'filename'["filename"][*][+-value][,+-ofset[,length]] ICL 'filename'["filename"] DTA [abfghltv](value1,value2...)[(value1,value2...)] DTA [cd]'string'["string"] RUN expression INI expression END [.EN] SIN (centre,amp,size[,first,last]) RND (min,max,length) :repeat BLK N[one] X BLK D[os] X BLK S[parta] X BLK R[eloc] M[ain]|E[xtended] BLK E[mpty] X M[ain]|E[xtended] BLK U[pdate] S[ymbols] BLK U[pdate] E[xternal] BLK U[pdate] A[dress] BLK U[pdate] N[ew] X 'string' label SMB 'string' NMB RMB LMB #expressionThese are mostly the same as in XASM, although there are some differences with quoted strings. Both '' and "" are treated the same except in operands, where '' gives the ATASCII code of a character, and "" gives the INTERNAL code.
BLK N[one] X - set program counter to X without adding a header block
BLK D[os] X - DOS block with header $FFFF or without header when
the program counter is already set to X
BLK S[parta] X - fixed address block with header $FFFA, set program counter to X
BLK R[eloc] M[ain]|E[xtended] - relocatable block in MAIN or EXTENDED memory
BLK E[mpty] X M[ain]|E[xtended] - relocatable block reserving MAIN or EXTENDED memory
Note: The program counter is immediately raised by X bytes
BLK U[pdate] S[ymbols] - update block for updating previous SPARTA or RELOC blocks with the
addresses of SDX symbols
BLK U[pdate] E[xternal] - update block for updating addresses of external labels ($FFEE header)
Note: Does not apply to SpartaDOS X, as this is a MADS extension
BLK U[pdate] A[dress] - update block for addresses in RELOC blocks
BLK U[pdate] N[ew] X 'string' - block declaring a new symbol 'string' in a RELOC block
with address X. If the symbol name is prefixed with @,
the address is in main memory and can be invoked by
command.com
For more information on SpartaDOS X blocks, see SpartaDOS X and Atari DOS file formats and Programming SpartaDOS X.
Example: redfine label temp
temp set 12
lda #temp
temp set 23
lda #temp
Example: declare and use SDX symbol
pf smb 'PRINTF'
jsr pf
...
This example code will instruct the SDX system to insert the correct address into the JSR instruction.
cm smb 'COMTAB'
wp equ cm-1 -> ERROR!
sta wp
Use this instead:
cm smb 'COMTAB'
sta cm-1 -> OK
Note: Use labels for all symbols and declare before usage for correct operation!
Example: repeat code
:4 asl @
:2 dta a(*)
:256 dta #/8
ladr :4 dta l(line:1)
hadr :4 dta h(line:1)
Within macros, the ':' specifies a macro parameter by number if it is in decimal.
When attempting to use the character ':' to repeat lines in a macro:
.macro test
:2 lsr @
.endm
The ':' prefixed number in this case is interpreted as the second macro parameter.
To prevent this interpretation by MADS, add something after the ':'
that does nothing, such as the plus sign ('+').
.macro test
:+2 lsr @
.endm
Now, the ':' is correctly interpreted as :repeat.
b+ enable bank sensitivity b- disable bank sensitivity (default) c+ use 65816 instruction set (16bit) c- use 6502 instruction set (8bit) (default) f+ emit file as a single block (useful for cartridges) f- emit file as multiple blocks (default) h+ write DOS executable file headers (default) h- omit DOS executable file headers l+ enable writing to listing file (*.LST) l- disable writing to listing file (*.LST) (default) m+ expand macros in listing m- include only macro invocation in listing (default) o+ enable writing to object file (*.OBX) (default) o- disable writing to object file (*.OBX) r+ enable optimization for MVA, MVX, MVY, MWA, MWX, MWY r- disable optimization for MVA, MVX, MVY, MWA, MWX, MWY (default) s+ print listing to screen s- do not print listing to screen (default) t+ enable SEP/REP tracking (65816 CPU) t- disable SEP/REP tracking (65816 CPU) (default) ?+ labels beginning with '?' are local (MAE style) ?- labels beginning with '?' are temporary (default)
Example: opt c+ c - l + s + ;Sequence is important, so result is c- opt o + ;Spaces are ignored opt h-;
All options controlled by OPT can be used anywhere in the listing, e.g. if it is turned on at line 12 and off at line 20 then the listing will only contain lines 12 through 20.
OPT c+ is required to use 65816 addressing modes.If you use CodeGenie and 'OPT s+', the listing file is unnecessary as the listing is printed in the lower pane (Output Bar).
adr - assembles to ADR, setting the address in the header to ADR adr,loadadr - assembles to ADR, setting the address in the header to LOADADR [b($ff,$fe)] - writes $FFFE header (will generate 2 bytes) [$ff,$fe],adr - writes $FFFE header, setting the address in the header to ADR [$d0,$fe],adr,loadadr - writes $D0FE header, assemble at address ADR, set the address in the header to LOADADR [a($FFFA)],adr - writes SpartaDOS $FAFF header, set address in the header to ADR
Example: disable standard header and generate own header opt h- org [a($ffff),d'atari',c'ble',20,30,40],adr,loadadrBrackets [ ] are used to write a new header, which can be any length. Other values following closing ']', separated by a comma, are the assembly address and the address in the header.
Here is an example of a file with a single header, assembled at address $2000, with valid block start and end addresses in the header.
Example: gerenate DOS header opt h-f+ org [a(start), a(over-1)],$2000 start nop .ds 128 nop over
The INS pseudo-command allows inclusion of an external binary file. The included file does not have to be in the same directory as the main file being assembled. Search paths for the file can be configured using the -i switch (see Assembler switches).
Additionally, you can perform the following operations on the binary data:* invert bytes +-VALUE increase or decrease each byte by the value of the expression VALUE +OFSET skip OFSET bytes at the beginning of the file (seek to OFSET) -OFSET read OFSET bytes at the end of the file (seek to FILE_LENGTH-OFSET) LENGTH read LENGTH bytes from the fileIf the LENGTH value is not specified, the default behavior is to read to the end.
b byte data (8-bit)
a word data (16-bit)
v relocatable WORD data (16-bit)
l byte data (8-bit)
h byte data (8-bit)
t long data (24-bit)
e long data (24-bit)
f double word data (32-bit)
g double word data (32-bit) in reversed byte order (big-endian)
c ATASCII string, delimited by '' or ""
Using a star (*) at the end encodes inverse video, e.g. dta c'abecadlo'*
d INTERNAL string, delimited by '' or "";
Using a star (*) at the end encodes inverse video, e.g. dta d'abecadlo'*
Example: dta 1 , 2, 4 dta a ($2320 ,$4444) dta d'sasasa', 4,a ( 200 ), h($4000) dta c 'file', $9b dta c'invers'*
Example: generate sine table dta a(sin(0,1000,256,0,63))
Example: genenate random value dta b(rnd(0,33,256))
ift [.if] expression els [.else] eli [.elseif] expression eif [.endif]
.ALIGN n[,fill] .ARRAY label index type [= default_value] .ENDA, [.AEND] .DEF label [= expression] .ENUM label .ENDE, [.EEND] .ERROR [ERT] 'string'["string"] or .ERROR [ERT] expression .EXTRN label [,label2,...] type .IF [IFT] expression .ELSE [ELS] .ELSEIF [ELI] expression .ENDIF [EIF] .IFDEF label .IFNDEF label .LOCAL label .ENDL, [.LEND] .LINK 'filename' .MACRO label .ENDM, [.MEND] :[%%]parameter .EXITM [.EXIT] .NOWARN .PRINT [.ECHO] 'string1','string2'...,value1,value2,... .PAGES [expression] .ENDPG, [.PGEND] .PUBLIC, [.GLOBAL], [.GLOBL] label [,label2,...] .PROC label .ENDP, [.PEND] .REG, .VAR .REPT expression [,parameter1, parameter2, ...] .ENDR, [.REND] .R .RELOC [.BYTE|.WORD] .STRUCT label .ENDS, [.SEND] .SYMBOL label .SEGDEF label address length [bank] .SEGMENT label .ENDSEG .USING, [.USE] proc_name, local_name .VAR var1[=value],var2[=value]... (.BYTE|.WORD|.LONG|.DWORD) .ZPVAR var1, var2... (.BYTE|.WORD|.LONG|.DWORD) .END .EN .BYTE .WORD .LONG .DWORD .OR .AND .XOR .NOT .LO (expression) .HI (expression) .DB .DW .DS expression .BY [+byte] bytes and/or ASCII .WO words .HE hex bytes .SB [+byte] bytes and/or ASCII .CB [+byte] bytes and/or ASCII .FL floating point numbers .ADR label .LEN label .GET [index] 'filename'["filename"][*][+-value][,+-offset[,length]] .PUT [index] = value .SAV [index] ['filename',] length
Whenever there is a .SYMBOL directive, the following block update will be generated:
BLK UPDATE NEW LABEL 'LABEL'More on the declaration of SDX symbols is found in the section for the Pseudo-command SMB.
Example: .align .align $400 .align $100,$ff
Additionally, within a .REPT....ENDR block, the hash sign '#' (or directive .R) gives the current value of the loop counter (like for :repeat).
Example: .rept 12, #*2, #*3 ; A .REPT block can be combined with :rept :+4 dta :1 ; :+4 to distinguish from repeat block parameter :4 :+4 dta :2 .endr .rept 9, # ; Define 9 labels label0..label8 label:1 mva #0 $d012+# .endr
These directives can help if you want part of the program to be within one memory page, or if you write a program stored in an additional memory bank (64 pages of memory), such as:
Example: org $4000 .pages $40 ... .endpg
The .SEGMENT directive begins writing of code and data to the code segment LABEL. Exceeding the preset length of the segment generates the error message Segment LABEL error at $xxxx where XXXX is the offset in the segment.
The directive .ENDSEG ends writing to the current segment and returns to the main program block.
Example:
.segdef sdata adr0 $100
.segdef test adr1 $40
org $2000
nop
.cb 'ALA'
.segment sdata
nop
.endseg
lda #0
.segment test
ldx #0
clc
dta c'ATARI'
.endseg
adr0 .ds $100
adr1 .ds $40
Example:
.var a,b , c,d .word ; 4 variables of type .WORD
.var a,b,f :256 .byte ; 3 variables each with a size of 256 bytes
.var c=5,d=2,f=$123344 .dword ; 3 .DWORD variables with values 5, 2, and $123344
.var .byte i=1, j=3 ; 2 variables of type .BYTE with values 1, 3
.var a,b,c,d .byte = $a000 ; 4 variables of type .BYTE with addresses $A000, $A001, $A002, $A003
.var .byte a,b,c,d = $a0 ; 4 variables of type .BYTE, with the last variable 'D' having address $A0
; Note: In this form it is not possible to determine the address of variables
.proc name
.var .word p1,p2,p3 ; Declare three variable of type .WORD
.endp
.local
.var a,b,c .byte
lda a
ldx b
ldy c
.endl
.struct Point ; New structure of type Point
x .byte
y .byte
.ends
.var a,b Point ; Declare structured variables
.var Point c,d ; Equivalent to 'label DTA Point'
Declared variables are physically allocated at the end of the block, as determined by the directives
.ENDP, .ENDL, or .END. The exception is that in a .PROC block, variables declared with .VAR are
always allocated in front of the .ENDP directive even if the .VAR statements are within nested
.LOCAL blocks.
Example:
.zpvar a b c d .word = $80 ; 4 variables of type .WORD starting at address $0080
.zpvar i j .byte ; two more byte variables starting at address $0080+8
.zpvar .word a,b ; 2 variables of type .WORD
; Note: In this form it is not possible to determine the address of variables
.struct Point ; Declare new structure Point
x .byte
y .byte
.ends
.zpvar a,b Point ; Declare structured variables
.zpvar Point c,d ; Equivalent to 'label DTA Point'
The zero page variable will be assigned addresses only at the end of the block in which it was declared, at
.ENDP, .ENDL, or .END. The exception is a .PROC block where variables declared with .ZPVAR are assigned
at the .ENDP directive even if variables are declared within nested .LOCAL blocks.
Using .ZPVAR with only an address will specify the first address to assign to the next variable. The default address is $0080.
Example: .zpvar = $40The address is automatically incremented by MADS and the warning message Access violations at address $xxxx is generated if addresses are repeated. In the case of a zero page overflow, the error message Value out of range is generated.
Example: .print "End: ",*,'..',$8000-* .echo "End: ",*,'..',$8000-*
(Editor's note: Values are printed out as hexadecimal with a $ prefix.)
Example: ert "halt" ; ERROR: halt .error "halt" ert *>$7fff ; ERROR: User error .error *>$7fff
Example: .proc test (.word tmp,a,b .byte value) .byte "atari",5,22 .word 12,$FFFF .long $34518F .dword $11223344
Example: bufferlen .ds 1 ; Reserves a single byte buffer .ds 256 ; Reserves 256 bytes
Example: .by +$80 1 10 $10 'Hello' $9B ; Generates 81 8A 90 C8 E5 EC EC EF 1BValues in .BY statements may also be separated with commas for compatibility with other assemblers. Spaces are allowed since they are easier to type.
Example: .HE 0 55 AA FFValues in .HE statements may also be separated with commas for compatibility with other assemblers. Spaces are allowed since they are easier to type.
Values in .SB statements may also be separated with commas for compatibility with other assemblers. Spaces are allowed since they are easier to type.
Values in .CB statements may also be separated with commas for compatibility with other assemblers. Spaces are allowed since they are easier to type.
Values in .FL statements may also be separated with commas for compatibility with other assemblers. Spaces are allowed since they are easier to type.
This is an optional pseudo-command to mark the end of assembly. It can be placed before the end of your source file to prevent the remaining portion of it from being assembled.
Example: org $2000 .proc tb,$1000 tmp lda #0 .endp lda .adr tb.tmp ; = $2000 lda tb.tmp ; = $1000
Example: label .array [255] .dword .enda dta a(.len label) ; = $400 .proc wait lda:cmp:req 20 rts .endp dta .len wait ; = 7
Example: ift .not(.def label) .def label eifNote: Defining a label after the use of a .DEF which references it can be dangerous, particularly if the .DEF is used in an .IF directive.
Example:
.ifdef label
jsr proc1
.else
jsr proc2
.endif
Example:
.ifndef label
clc
.else
sec
.endif
In the following example, the .IFNDEF (.IF) block will be processed and the label defined
only when the block is first encountered. If there are any errors associated with their definition,
this will only be reported when a reference to one of them is attempted, resulting in the
error message Undeclared label LABEL.
Example:
.ifndef label
.def label
lda #0 ; This will only be generated once and not be reassembled
temp = 100 ; Label TEMP is defined only once during assembly
.endif
Example:
.nowarn .proc temp ; Warning message Unreferenced procedure TEMP not generated
.endp
Example:
.local move
tmp lda #0
hlp sta $a000
.local move2
tmp2 ldx #0
hlp2 stx $b000
.endl
.endl
.local main
.use move.move2
lda tmp2 ; Will be move.move2.tmp2
.use move
lda tmp ; Will be move.tmp
.endl
The INDEX must be in the range [0..65535]. The values read by .GET are of type .BYTE.
Example: .get 'file' ; Load the file into a MADS array .get [5] 'file' ; Load the file into an array starting at index 5 .get 'file',0,3 ; Load the file into an array of size 3 lda #.get[7] ; Load the value of element 7 of the file array address = .get[2]+.get[3]<<8 ; Use bytes 2 and 3 of the file array as an addressWith the help of the directives .GET and .PUT you can transform binary files during the assmbley. For example sound modules can be relocated using macros and these directives. See MADS macro in the file "../EXAMPLES/MSX/TMC_PLAYER_RELOCATOR/TMC_RELOCATOR.MAC" for an example which relocates a module for Theta Music Composer (TMC).
The INDEX must be in the range [0..65535].
Example: .put [5] = 12 ; Store the value 12 into MADS memory at element 5
The INDEX must be in the range [0..65535].
Example: .sav ?length ; Insert elements [0..?LENGTH-1] to current assembly output .sav [200] 256 ; Insert elements [200..200+256-1] to current assembly output .sav [6] 'filename',32 ; Save elements [6..6+32-1] to file FILENAME
.if [ift] expression .else [els] .elseif [eli] expression .endif [eif]These directives and pseudo-commands can be used interchangeably to conditionally assemble portions of code.
Example:
.if .not .def label_name
label_name = 1
.endif
.if [.not .def label_name] .and [.not .def label_name2]
label_name = 1
label_name2 = 2
.endif
In these examples the parentheses or square brackets are required. Otherwise the parameter
of the first .DEF directive would be the label label_name.AND.NOT.DEFlabel_name2.
because spaces are ignored and periods are allowed in label names.
#IF type expression [.OR type expression] [.AND type expression] #ELSE #END #WHILE type expression [.OR type expression] [.AND type expression] #END #CYCLE #N
The #IF, #ELSE, and #END directives produce 6502 machine code for an IF conditional statement around the designated program block and can be nested. All types are acceptable (.BYTE, .WORD, .LONG, and .DWORD). It is possible to combine terms using the .AND and .OR directives, but it is impossible to control the order of evaluation with parentheses.
The implementation of the #IF directive begins with the calculation of the value that is a simple expression consisting of two operands and one operator (expression can be combined using the .OR or .AND directives).
If the expression is non-zero (TRUE), the program block within the #IF is executed, terminated by a JMP instruction to the next instruction after #END if there is an #ELSE block.
If the expression is zero (FALSE), the code following #ELSE is executed. If there is no #ELSE directive, then control is transferred to the next instruction after the #END directive.
Example:
#if .byte label>#10 .or .byte label<#5
#end
#if .byte label>#100
#if .byte label<#200
#end
#end
#if .byte label>#100 .and .byte label<#200 .or .word lab=temp
#end
#if .byte @
#end
The directives #WHILE and #END allow generation of 6502 machine code for a loop around the given program block and can be nested. The types .BYTE, .WORD, .LONG, and .DWORD are acceptable. Multiple terms can be connected with the .OR and .AND directives, but the order of evaluation cannot be controlled by parentheses.
The sequence of operations in the expansion of the #WHILE statement is as follows:Example: #while .byte label>#10 .or .byte label#5 #end #while .byte label>#100 #while .byte label2#200 #end #end #while .byte label>#100 .and .byte label#200 .or .word lab=temp #end
#cycle #17 ; pha 3 cycles
; pla 4 cycles
; pha 3 cycles
; pla 4 cycles
; cmp $00 3 cycles
---------
17 cycles
Example: detection of zero page addressing
org $00
lda tmp+1
tmp lda #$00
A two-pass assembler does not know the value of the label TMP.
It will assume that it is 16-bit (.WORD type) and generate LDA abs.
However, MADS nicely generates LDA zp because the actual size is known in the last pass.
This is the simplest benefit of multiple passes.
Now, suppose referring to zero page with LDA abs is explicitly desired.
No problem, just extend the mnemonic to force 16-bit addressing.
Example: force 16-bit addressing
org $00
lda.w tmp+1
tmp lda #$00
Three mnemonic extensions are allowed:
.b[.z] - .BYTE or zero page .w[.a][.q] - .WORD or address .l[.t] - .LONG or triple byteThe last one generates a 24-bit value for 65816 long addressing (seldom used). For more information 6502 and 65816 CPU mnemonics, see Mnemonics.
Another way to force zero page addressing is to use curly braces { } to generate opcodes with DTA pseudo-opcode.
Example:
dta {lda $00},$80 ; Generates $a5,$80, i.e. lda $80
Either will work with MADS as the last pass will do the trick.
The next problem is putting this code on your computer.
Loading directly into page zero will probably work if the target area is within [$80..$FF],
but when loading below that the OS is unlikely to survive.
Therefore, MADS provides the possibility to relocate code. The pseudo-command ORG address1,address2 assembles at address1, but loads to address address2. The pseudo-command ORG always creates a new block in the file, which adds an additional four bytes for the header of the new block.
Example: relocate zero page code
org $20,$3080
lda tmp+1
tmp lda #$00
This assembles at address $0020, but with a load address of $3080. Of course, moving
the code to the correct address ($0020 in our example) is now the responsibility of the programmer.
If it is OK for the new address of the data in memory to be the current address, then the property of .LOCAL and .PROC blocks can be used to avoid writing a new header.
Example: relocate inline blocks 1 2 org $2000 3 4 FFFF> 2000-200D> A9 00 lda #0 5 2002 EA nop 6 7 0060 .local temp, $60 8 9 0060 BD FF FF lda $ffff,x 10 0063 BE FF FF ldx $ffff,y 11 12 .endl 13 14 2009 A5 60 lda temp 15 200B AD 03 20 lda .adr temp 16In this example the block TEMP will be assembled with the new address $60 and be placed in memory at address $2003. After the block end directive (.ENDL, .ENDP, .END), assembly will resume at the previous assembly address plus the length of the block, which in this case is $2009.
The directives .ADR and .LEN can then be used to copy the block to the correct address.
Example:
ldy #0
copy mva .adr(temp),y temp,y+
cpy #.len temp
bne copy
For more information on the directives, see .ADR and .LEN.
The fields of the structure contain information about offsets from the beginning of the structure.
name .struct
.ends [.send] [.end]
.struct name
.ends [.send] [.end]
Example: structure declaration .struct name x .word ; lda #name.x = 0 y .word ; lda #name.y = 2 z .long ; lda #name.z = 4 v .dword ; lda #name.v = 7 q :3 .byte ; lda #name.q = 11 .ends ; lda #name = 14 (length)Each line defines a field by name and type (.BYTE, .WORD, .LONG, or .DWORD). The field name may be preceded by whitespace. Between the .STRUCT and .ENDS directives, CPU mnemonics may not be used. Attempting to do so or having other invalid characters will result in an Improper syntax or Illegal instruction error message.
In summary, the label name contains information about the total length of the structure (in bytes). The other labels describing the fields contain information about the offsets to each field from the beginning of the structure.
Structure declarations cannot be nested, but previously declared structures can be nested in other ones (declaration order does not matter). For example:.struct temp x .word y .word v .byte z .word .ends .struct test tmp temp .ends lda #temp.v lda #test.tmp.x lda #test.tmp.z
What are structures useful for?
Suppose you have a table of different types, where you can read each table field with a predetermined offset value. If a field is added to the table, or the table is modified in any other way, the program code will have to be updated to use new field offsets. Defining the table using a structure means that offsets can be determined by the structure definition, which are then automatically updated even if the structure of the table is changed.Another example of structure usage can be found in the section on External symbols, Using external symbols with structures (.STRUCT).
label DTA struct_name [count] (data1,data2,data3...) (data1,data2,data3...) ... label struct_name
COUNT specifies a number in the range [0..COUNT], which defines the maximum element index in a one-dimensional array and thus the amount of memory reserved.
Examples of structure and structured data declarations:;-----------------------; ; structure declaration ; ;-----------------------; .STRUCT temp x .word y .word v .byte z .word .ENDS ;---------------; ; defining data ; ;---------------; data dta temp [12] (1,20,200,32000) (19,2,122,42700) data2 dta temp [0] data3 temp // shorter equivalent to DATA2The value in square brackets must be a value between [0..2147483647], which defines the maximum value of a one-dimensional array index and thus the amount of memory reserved for the structured data.
After the square brackets an optional list of initializer values (in parentheses) may follow. Otherwise, the field values default to zero. However, if the initializer list is shorter than the number of declared fields, the remaining fields are initialized to the previous value given for those fields:
data dta temp [12] (1,20,200,32000)
If the list of initializers is longer than the number of elements, the error Constant expression violates subrange bounds will result.
To refer to fields in the structured data, use its name, followed by an index in square brackets and the field name after a dot:lda data[4].y ldx #data[0].vForgetting the brackets with an index in the syntax label[index] results in the error message Undeclared label.
name .enum
.ende [.eend] [.end]
Example:
.enum portb
rom_off = $fe
rom_on = $ff
.ende
.enum test
a ; a=0
b ; b=1
c = 5 ; c=5
d ; d=6
.ende
Enumerations are declared using the directives .ENUM and .ENDE. The name of the enumeration is required and an error will be generated otherwise. Enumeration names may not be the same as mnemonics or pseudo-commands, which will produce a Reserved word error.
The label values are automatically assigned starting with a default value of 0 and incrementing by 1. You can define the value of each label directly or have them be automatically set.Enumerated labels are referenced using this syntax:
enum_name (field)or directly like with .LOCAL and .PROC blocks, with a dot between the enumeration name and the field name:
lda #portb(rom_off) dta portb.rom_on, portb.rom_offEnumerations can be used for field declarations in structures (.STRUCT) and variable declarations (.VAR):
bank portb // allocate variable BANK of size 1 byte .var bank portb // allocate variable BANK of size 1 byte .struct test a portb b portb .endsThe size of an enumeration is dependent upon the maximum value of its labels:
.enum estate
done, directory_search=$ff, init_loading, loading
.ende
In this example, the enumeration "EState" will have a size of two bytes (WORD).
The size of an enumeration can be checked with the .LEN directive (equivalent to SIZEOF), where the result will be a value in the range [1..4] (1=BYTE, 2=WORD, 3=LONG, 4=DWORD):
.print .len estate
name .array index type [= default_value]
.array name count type [= default_value]
.enda [.aend] [.end]
Available types are .BYTE, .WORD, .LONG, and .DWORD.
INDEX specifies the maximum permitted value of the array index range [0..INDEX]. This value can be a constant or an expression in the range [0..65535]. If INDEX is omitted, the range is determined by the number of input values.
CPU mnemonics cannot be used between .ARRAY and .ENDA, and attempting to do so or having other illegal characters will result in the error Improper syntax. The array index used for initialization can be specified along with initializer values. A new array index is set by placing it in square brackets at the beginning of a new line, i.e. [expression]. Additional indices can be supplied, separated by a colon (':'). The array values then follow after a equals sign ('='):.array tab .byte ; Define array TAB with an unspecified number of elements 1,3 ; [0]=1, [1]=3 5 ; [2]=5 [12] = 1 ; [12]=1 [3]:[7]:[11] = 9,11 ; [3]=9, [4]=11, [7]=9, [8]=11, [11]=9, [12]=11 .endaThis facility may seem strange and of limited use, but it is occasionally useful, such as for declaring lookup tables for translating the scan code of a pressed key or between ATASCII and INTERNAL code.
.array TAB [255] .byte = $ff ; Allocate 256 bytes [0..255] with initial value $FF [63]:[127] = "A" ; Assign new values TAB[63]="A", TAB[127]="A" [21]:[85] = "B" [18]:[82] = "C" [58]:[122] = "D" [42]:[106] = "E" [56]:[120] = "F" [61]:[125] = "G" [57]:[121] = "H" [13]:[77] = "I" [1] :[65] = "J" [5] :[69] = "K" [0] :[64] = "L" [37]:[101] = "M" [35]:[99] = "N" [8] :[72] = "O" [10]:[74] = "P" [47]:[111] = "Q" [40]:[104] = "R" [62]:[126] = "S" [45]:[109] = "T" [11]:[75] = "U" [16]:[80] = "V" [46]:[110] = "W" [22]:[86] = "X" [43]:[107] = "Y" [23]:[87] = "Z" [33]:[97] = " " [52]:[180] = $7e [12]:[76] = $9b .enda
In this example, an array TAB is created with indices [0..255] and 256 .BYTEs in size, pre-initialized to $FF. The array elements are then set to translate keyboard scan codes (both upper and lowercase, ignoring case) to INTERNAL.
The colon (':') is used to separate array indices. Another example is to center a string:org $bc40 .array txt 39 .byte [17] = "ATARI" .enda
In summary, the .ARRAY directive allows creation of a one-dimensional array of values with a specified type.
To refer to an array:lda tab,y lda tab[23],x ldx tab[200]If the index given in square brackets exceeds the maximum index of the array, the error message Constant expression violates subrange bounds is reported.
name .macro [arg1, arg2 ...] ['separator'] ["separator"]
.macro name [(arg1, arg2 ...)] ['separator'] ["separator"]
.exitm [.exit]
.endm [.mend]
:[%%]parameter
:[%%]label
.macro SetColor val,reg lda :val sta :reg .endmAt the end of the macro declaration, the argument separator and the argument parsing mode can be supplied (unchanged for single quotes, split into parameters and addressing modes for double quotes).
The default separators for macro argument parsing are a comma (',') and a space (' ').
name .macro 'separator'Between the quotes '' we place the separator character used to separate parameters when invoking the macro (only with single quotes).
name .macro "separator"Double quotes ("") can also be used to set separators for the macro parameters, but this also indicates to MADS that the parameters should be split into two parts: addressing mode and argument.
test #12 200 <30
test .macro " "
.endm
This TEST macro is declared with a space as the separator using ",
which then causes the macro parameter to be divided into two parts, addressing mode and argument.
#12 -> addressing mode '#' argument 12 200 -> addressing mode ' ' argument 200 <30 -> addressing mode '#' argument 0 (calculated expression value of "<30") test '#' 12 ' ' 200 '#' 0NOTE #1: The sign operators '<', '>' are evaluated before parameters are passed to a macro, with the result substituted as the parameter.
NOTE #2: If the macro parameter is the loop counter '#' or '.R' (!!! the single character '#' or the directive '.R', and not an expression involving one of them !!!) the value of the loop counter is substituted as the macro parameter.
This property can be used to create iterated label names like "label0", "label1", "label2", "label3"... :
:32 find #
find .macro
ift .def label:1
dta a(label:1)
eif
.endm
In this example, the address of each numbered label is written (if it is defined).
:$2 nop :+2 nop :%10 nopParameter :0 (%%0) has special meaning and contains the number of parameters passed. This can be used to check if the required number of parameters was passed to a macro:
.if :0<2 || :0>5
.error "Wrong number of arguments"
.endif
ift %%0<2 .or :0>5
ert "Wrong number of arguments"
eif
Example: macro definition .macro load_word lda <:1 sta :2 lda >:1 sta :2+1 .endm test ne test eq .macro test b%%1 skip .endm
macro_name [Par1, Par2, Par3, 'Par4', "string1", "string2" ...]A parameter can be a value or a string delimited by either single quotes ('') or double quotes. ("").
All label definitions within a macro are local.
If the assembler does not find a label within the macro, it will then look in the local scope (if there was a .LOCAL directive), then in the procedure (if a procedure is currently being defined), and then finally in the main program.Example: macro call macro_name 'a',a,>$a000,cmp ; The default separator ',' macro_name 'a'_a_>$a000_cmp ; The declared separator '_' macro_name 'a' a >$a000 cmp ; The default separator ' 'Macros can be called from other macros as well as called recursively. In the latter case, care should be taken to avoid causing stack overflow in MADS. MADS is protected against infinite recursion and will stop with the error message Infinite recursion once the nesting depth reaches 4095.
Example: cause stack overflow in MADS stack
jump .macro
jump
.endm
Example program that passes parameters to pseudo-procedures, from the file "..\EXAMPLES\MACRO.ASM".
Example:
org $2000
proc PutChar,'a'-64 ; Call macro PROC, with as parameters
proc PutChar,'a'-64 ; The name of a procedure to call by JSR
proc PutChar,'r'-64 ; and a single argument (INTERNAL character code)
proc PutChar,'e'-64
proc PutChar,'a'-64
proc SetColor,$23 ; Call another procedure to change the background color
;---
loop jmp loop ; Endless loop to show the effect
;---
proc .macro ; Declare PROC macro
push =:1,:2,:3,:4 ; Call PUSH macro to push arguments onto the stack
; =:1 calculates the memory bank
jsr :1 ; Jump to procedure (procedure name is the first parameter)
lmb #0 ; Load memory bank, setting bank to 0
.endm ; End of PROC macro
;---
push .macro ; Declare PUSH macro
lmb #:1 ; Set up virtual memory bank
.if :2<=$FFFF ; If passed argument is less than or equal to $FFFF
lda <:2 ; put it on the stack
sta stack
lda >:2
sta stack+1
.endif
.if :3<=$FFFF
lda <:3
sta stack+2
lda >:3
sta stack+3
.endif
.if :4<=$FFFF
lda <:4
sta stack+4
lda >:4
sta stack+5
.endif
.endm
* --------------- * ; SetColor procedure
* PROC SetColor *
* --------------- *
lmb #1 ; Set virtual bank to 1
; Label definitions are now local to this bank
stack org *+256 ; Stack for SetColor procedure
color equ stack
SetColor ; Code for SetColor procedure
lda color
sta 712
rts
* -------------- * ; PutChar procedure
* PROC PutChar *
* -------------- *
lmb #2 ; Set virtual bank to 2
; Label definitions are now local to this bank
stack org *+256 ; Stack for PutChar procedure
char equ stack
PutChar ; Code for PutChar procedure
lda char
sta $bc40
scr equ *-2
inc scr
rts
Of course, this example uses a software stack, whereas with the 65816
the hardware stack could be used instead. Because the defined variables are each local to
a particular bank, procedure calls can be created with similar structure and function
to those of higher-level languages.
However, it is simpler and more efficient to use the procedures allowed by MADS, which are declared with .PROC. For more information on procedure declaration and operations, see Procedures.
The built-in MADS macros (@CALL.MAC, @PULL.MAC, @EXIT.MAC) provide a software stack of 256 bytes, the same size as the hardware stack, a way to pop from the software stack, and to save and restore parameters when calling other procedures. MADS supports recursive procedure calls.
The programmer is not involved in this mechanism and can focus on his program, only needing to define the appropriate labels and include the needed macros when assembling the program.
Also, the software stack can be omitted and arguments passed using a more classical method with CPU registers (.REG directive) or with variables (.VAR directive).
Another feature of .PROC procedures is that it is possible to omit them during assembly if they are not referenced. This generates the warning message Unreferenced procedure ????. They can be removed during assembly by specifying the -x 'Exclude unreferenced procedures' command-line parameter to MADS.
All labels defined in a .PROC procedure are local but can also be accessed globally, which is uncommon in other programming languages.
It is possible to define a global label from Within a .PROC procedure (see Global labels).
To access labels within a procedure from outside of it, address it using a dot ('.'):lda test.pole .proc test pole nop .endpIf a referenced label is not found within a .PROC procedure, MADS will then look for in enclosing scopes until the global scope is reached. To directly address a global label from within a .PROC procedure (or any other scope) prefix the label name with a colon (':'). For procedures that use a software stack, MADS requires three specific globally defined labels (the stack location, the stack pointer, and the address of the procedure variables):
@PROC_VARS_ADR @STACK_ADDRESS @STACK_POINTER
If these labels are undefined and a ..PROC procedure with a software stack is used, MADS assumes the following default values: @PROC_VARS_ADR = $0500, @STACK_ADDRESS = $0600, and @STACK_POINTER = $FE.
For procedures using a software stack, MADS also requires the declaration of macros with specific names. Declarations of these macros are included with MADS in the following files:@CALL ..\EXAMPLES\MACROS\@CALL.MAC @PUSH ..\EXAMPLES\MACROS\@CALL.MAC @PULL ..\EXAMPLES\MACROS\@PULL.MAC @EXIT ..\EXAMPLES\MACROS\@EXIT.MACThese macros implement the loading and pushing of parameters onto the software stack, the popping and saving of procedure parameters off the software stack, and calling other procedures using the software stack.
name .PROC [(.TYPE PAR1 .TYPE PAR2 ...)] [.REG] [.VAR] .PROC name [,address] [(.TYPE PAR1 .TYPE PAR2 ...)] [.REG] [.VAR] .ENDP [.PEND] [.END]
.BYTE (8-bit) relocatable .WORD (16-bit) relocatable .LONG (24-bit) non-relocatable .DWORD (32-bit) non-relocatableNote: In current versions of MADS, it is not possible to pass parameters of structure type (.STRUCT). The parameter type comes first, followed by at least one space, followed by the parameter name. Multiple parameters of the same type can be declared, separated by commas (',').
Example: procedure declaring using the software stack name .PROC ( .WORD par1 .BYTE par2 ) name .PROC ( .BYTE par1,par2 .LONG par3 ) name .PROC ( .DWORD p1,p2,p3,p4,p5,p6,p7,p8 )
Additionally, by using the .REG or .VAR directives, parameters can be passed to MADS procedures by CPU registers (.REG) or variables (.VAR). The directive specifying the parameter passing convention is placed at the end of the .PROC procedure declaration.
Example: procedure declarations using CPU registers name .PROC ( .BYTE x,y,a ) .REG name .PROC ( .WORD xa .BYTE y ) .REG name .PROC ( .LONG axy ) .REG
The .REG directive requires that the parameter names are made up of the letters 'A', 'X', 'Y', or a combination thereof. These refer to the names of the CPU registers and affect the order in which they are used. The number of parameters passed is limited by the number of CPU registers, so at most three bytes can be passed to the procedure. The advantage of this method is speed and lower memory usage.
Example: procedure declarations using variables name .PROC ( .BYTE x1,x2,y1,y2 ) .VAR name .PROC ( .WORD inputPointer, outputPointer ) .VAR name .PROC ( .WORD src+1, dst+1 ) .VARWith .VAR, the parameter names indicate the variable names which will be loaded with the passed parameters. This method is slower than .REG but still faster than software stack methods.
Procedures are exited in the usual way, using the RTS command. Adding the RTS instruction at the end of each code path is the responsibility of the programmer, not the assembler.
As with a .LOCAL block, a new address can be specified for assembling a .PROC block:.PROC label,$8000 .ENDP .PROC label2,$a000 (.word ax) .reg .ENDPFor procedures that use the software stack, MADS invokes the macro @EXIT at the end of the procedure, whose task is to modify the software stack pointer @STACK_POINTER. This is necessary for proper software stack operation. Users can write their own @EXIT macro, or use the one included with MADS in the file "..\EXAMPLES\MACROS\@EXIT.MAC", which currently looks like this:
.macro @EXIT ift :1<>0 ift :1=1 dec @stack_pointer eli :1=2 dec @stack_pointer dec @stack_pointer els pha lda @stack_pointer sub #:1 sta @stack_pointer pla eif eif .endm
The macro @EXIT should not alter the contents of CPU registers if you want to return a result from a .PROC procedure using CPU registers.
A procedure is called by name (the same as for a macro), followed by any provided parameters, separated by a commas (',') or spaces (' '). It is not possible to use other separators.
If the type of a parameter is different than the type used in the procedure declaration, an Incompatible types error is reported.If the number of parameters passed is different than the number of parameters in the procedure declaration, the result is an Improper number of actual parameters error. The exception is procedures which use parameters passed by CPU register (.REG) or variable (.VAR), in which case the extra parameters are assumed to be already loaded into the correct registers or variables.
There are three ways to pass parameters:Example: procedure call name @ , #$166 , $A400 ; with the software stack name , @ , #$3f ; with .REG or .VAR name "(hlp),y" "tab,y" ; with .VAR or the software stack (the software stack uses the X register)
When MADS encounters a procedure call that uses the software stack, it executes the macro @CALL. However, if the procedure does not use the software stack, a plain JSR PROCEDURE is used instead of the @CALL macro.
To the @CALL macro, MADS passes parameters computed based on the procedure declaration, breaking each parameter into three components: addressing mode, parameter type, and parameter value.@CALL_INIT 3\ @PUSH_INIT 3\ @CALL '@','B',0\ @CALL '#','W',358\ @CALL ' ',W,"$A400"\ @CALL_END PROC_NAMEHere, the @CALL macro pushes the contents of the accumulator, then the value $166 (358 dec), then the value at the address $A400. For more information on how parameters are passed to the macro (and the importance of '' and ""), see Calling macros.
Parameters passed using the accumulator ('@') should always be the first parameter passed to the procedure. If it is used elsewhere, the accumulator is already modified by that point (this restriction is imposed by the @CALL macro). Of course, this can be lifted with a custom version of the @CALL macro. With procedures that use .REG or .VAR, a '@' parameter can be used in any position.
The end of a .PROC procedure is marked by an RTS. After the procedure call, MADS invokes the @EXIT macro to modify the @STACK_POINTER, which is necessary for proper operation of the software stack. The number of bytes passed to the procedure is passed as a parameter to the macro, which then subtracts that number of bytes from the software stack.
(Editor's note: This is known as a caller-pops convention, since the caller both pushes and pops the parameters off the stack, and is common in C implementations.)
Adding an RTS instruction at the end of every code path in the procedure is the responsibility of the programmer, and not the assembler.
@stack_address equ $400 @stack_pointer equ $ff @proc_vars_adr equ $80 name .PROC (.WORD par1,par2) lda par1 clc adc par2 sta par1 lda par1+1 adc par2+1 sta par1+1 .endp icl '@call.mac' icl '@pull.mac' icl '@exit.mac'At the time of declaration, MADS automatically defines these parameters by assigning values based on @PROC_VARS_ADR. In the preceding example, MADS will define the parameters as PAR1 = @PROC_VARS_ADR and PAR2 = @PROC_VARS_ADR + 2.
The programmer uses these parameters by the names given in the procedure declaration, similarly as with higher-level languages. In MADS, it is possible to access procedure parameters externally, which is unusual in higher-level languages. For instance, PAR1 can be read as follows:
lda name.par1 sta $a000 lda name.par1+1 sta $a000+1This copies the two bytes at PAR1 to addresses $A000 and $A000+1. Of course, this can only be done after completion of this particular procedure. Remember that each procedure has parameters stored at the same area addressed by @PROC_VARS_ADR, so with each new procedure call the parameter area at <@PROC_VARS_ADR .. @PROC_VARS_ADR + $FF> changes.
If a procedure is declared using .REG type parameters, the programmer should remember to preserve or use parameters before they are modified by the procedure code. With .VAR type parameters, this is not an issue because the parameters are saved in specific memory locations where they can always be read.
Local scope directives:
[name] .LOCAL [,address] .LOCAL [name] [,address] .ENDL [.LEND] [.END]
Declares a local scope named name with the directive .LOCAL. The local scope name is not required and can be omitted. Local scope names cannot be the same as mnemonics and pseudo-commands, and attempting to use a reserved name will result in a Reserved word error.
After the name of the local scope (or the .LOCAL directive), a new assembly address can be provided. After the end of the block (.ENDL), the assembly address reverts to the previous address plus the length of the block.
label .local,$4000 .endl .local label2,$8000 .endl .local .endl .local label3 .endlAll definitions within a .LOCAL block are local. To refer to a global label with the same name as a local name, prefix the name with a colon (':'):
lab equ 1 .local lab equ 2 lda #lab ldx #:lab .endlIn this example, the A register is set to 2, while the X register is set to 1.
If the assembler cannot find a label in a .LOCAL scope, it will then check an enclosing macro (if one is being processed), then the enclosing procedure (if there is one), and finally the main program at global scope.
Within a local scope, all label definitions are qualified by the local scope's name. To reach labels defined in another local scope, both the name of the local scope and the label must be supplied:lda #name.lab1 ldx #name.lab2 .local name lab1 = 1 lab2 = 2 .endl
A dot ('.') is used to address labels within a .LOCAL block.
Local scopes can be nested, as well as placed within procedures declared using the .PROC directive. Local scopes are cumulative, i.e. there may be multiple scopes of the same name and all symbols in those scopes will belong to a common namespace.
The .ENDL directive ends a local scope.
Example: local scope declaration
org $2000
tmp ldx #0 <------------- label in global scope
|
lda obszar.pole <--- | reference to local scope
| |
.local obszar | | local scope declaration
| |
lda tmp <--- | |
| | |
lda :tmp | | <--- reference to global scope
| |
tmp nop <--- | definition in local scope
|
pole lda #0 <--- <--- definition in local scope
|
lda pole <----------------- reference within local scope
.endl end of local scope
asl -> asl @ lda # -> lda #0MADS will accept missing white spaces between the mnemonic and operand, provided that the operand does not start with a '@', which is used in the names of labels, or '%' and ':', which are used to denote numbered macro parameters (%%number, :number), e.g.:
lda$44 lda# lda(80),y
lda 20\ cmp 20\ beq *-2 lda 20 \ cmp 20 \ beq *-2 lda #0 \lop sta $a000,y \ iny \ bne lop ; Comments only at the end of this line
If there is no space after the '\' character, a mnemonic or other string can be interpreted as a label, so make sure to treat '\' as the beginning of a new line.
MADS stops processing the line once either at the end of the chain or when a comment is reached, so comments can be placed only at the end of the line.
WARNING: Putting a '\' at the end of a line tells MADS to combine the current line with the next line.Exmaple: Combining lines lda\ #\ 12In this case, we get the result 'LDA #12'.
lda:cmp:req 20 lda:iny:sta:iny $600,y
(Editor's note: All mnemonics in a combined statement are processed with the same argument. This is fine when some of the instructions only have implied addressing. However, it can lead to accidents with instructions that have both implied and memory forms. For instance, "STA:INC:RNE pixels,X+" will assemble as STA pixels,X / INX / INC pixels,X / INX / BNE *.)
Expressions are evaluated in the order determined by priority of each operator in the direction specified by the precedence of each operator.
MADS accepts numbers in decimal, hexadecimal, binary, ATASCII, and INTERNAL format.
decimal notation:-100 -2437325 1743hexadecimal notation:
$100 $e430 $000001 0x12 0xa000 0xaabbccddbinary notation:
%0001001010 %000000001 %001000ATASCII code:
'a' 'fds' 'W'*INTERNAL code:
"B" "FDSFSD" "."*Only the first character of ATASCII or INTERNAL coding is significant. A '*' after the closing quote specifies inverse characters. Additionally, there are two possible operations '+' and '-' for strings, which increase or decrease the values of the quoted characters.
"FDttrteSFSD"-12 'FDSFdsldksla'+2
Binary operators: + Addition - Subtraction * Multiplication / Division % Remainder & Bitwise and | Bitwise or ^ Bitwise xor << Arithmetic shift left >> Arithmetic shift right = Equal == Equal (same as =) <= Not equal != Not equal (same as <>) < Less than > Greater than <= Less or equal >= Greater or equal && Logical and || Logical or Unary operators: + Plus (does nothing) - Minus (changes sign) ~ Bitwise not (complements all bits) ! Logical not (changes true to false and vice versa) < Low (extracts low byte) > High (extracts high byte) ^ High 24bit (extracts high byte) = Extracts memory bank : Extracts global variable value Operator precedence: first [] (brackets) + - ~ < > (unary) * / % & << >> (binary) + - | ^ (binary) = == <> != < > <= >= (binary) ! (unary) && (binary) last || (binary)
Example: label definitions
?name EQU $A000 ; Defining a temporary global label
name = * ; Defining a global label
name2=12 ; Defining a global label
@?name EQU 'a'+32 ; Defining a global label
name: equ 12 ; Defining a non-global label that does not start at the beginning of the line
name: = 'v' ; Defining a non-global label that does not start at the beginning of the line
Unlike QA/XASM, a question mark ('?') and at-sign ('@') can be used in label names.
Using a dot ('.') in a label name is allowed but not recommended. The dot is reserved for extended mnemonics, assembler directives, and addressing new MADS structures.
A dot ('.') at the beginning of a label name suggests that it is an assembler directive, and a question mark ('?') at the beginning signifies a temporary label that can be changed several times during assembly.
The character '@' is reserved for anonymous labels, and there must be a sign character indicating either forward direction ('+') or backwards direction ('-'). Additionally, you can specify the number of anonymous labels in the range [1..9].
@+[1..9] ; Forward @-[1..9] ; Backward @ dex ---- ------- bne @+ | -- | stx $80 | | | @ lda #0 | -- | bne @- --- | bne @-1 ---------
Labels defined within a macro (.MACRO), procedure (.PROC), or local scope (.LOCAL) are local labels by default and do not need additional markup.
Local labels are defined using the following equivalent pseudo-commands:EQU =To access global labels, i.e. those defined outside of a macro (.MACRO), procedure (.PROC), or local scope (.LOCAL), use the ':' operator:
lp ldx #0 ; Define global label LP
test
test
test .macro
lda :lp ; The ':' prefix causes reference to the global label LP
sta lp+1 ; Reference to the local label LP in the macro
lp lda #0 ; Definition of local label LP in the macro
.endm
In this example, there are two definitions of labels with the same name (LP),
but each has a different value and a different scope.
Each definition outside of a macro (.MACRO), procedure (.PROC), or local scope (.LOCAL) is global.
Global labels are defined using the following equivalent pseudo-commands:EQU =Or with .DEF directive syntax:
.DEF :label [= expression]
The .DEF directive normally defines local labels, but the ':' at the beginning of the label indicates a global label instead. Using the syntax .DEF :label allows definition of global labels within a local scope.
The ':' at the beginning of the label has special meaning and indicates a global label, or label at mainline level, disgarding any current local scopes.For more information on use of the .DEF directive, see .DEF Directive.
Example: defining global labels
lab equ *
lab2 equ $4000
?tmp = 0
?tmp += 40
.proc name
.def :?nazwa = $A000
.def :nazwa=20
.local lok1
.def :@?nazw = 'a'+32
.endl
.endp
Examples of defining a global temporary labels include the macro @CALL
in the file "..\EXAMPLES\MACROS\@CALL.MAC", which defines the temporary label
?@STACK_OFFSET. This is later used by other macros called
called by the macro @CALL, and is used to optimize access to
parameters on the stack.
@CALL .macro .def ?@stack_offset = 0 ; Definition of temporary global lavel ?@stack_offset ... ... @CALL_@ .macro sta @stack_address+?@stack_offset,x .def ?@stack_offset = ?@stack_offset + 1 ; Modify ?@stack_offset label .endm
The lifetime of a temporary label depends on the scope in which it is defined. Temporary labels may have local scope (Local labels) or global scope (Global labels).
A temporary label is created by placing a '?' at the beginning:?labelTemporary labels should not be used to name procedures (.PROC), macros (.MACRO), local scopes (.LOCAL), structures (.STRUCT), or arrays (.ARRAY). A temporary label is defined using one of the following equivalent pseudo-commands:
EQU =Additionally, they can be modified using recognizable C operators:
-= expression += expression -- ++These assignment operators only work with temporary labels, and attempting to use them for other types of labels results in the error message Improper syntax.
Example: temporary labels ?loc = $567 ?loc2 = ?loc+$2000 lda ?loc sta ?loc2 ?loc = $123 lda ?loc
Example: MAE-style temporary labels
opt ?+
org $2000
local1 ldx #7
?lop sta $a000,x
dex
bpl ?lop
local2 ldx #7
?lop sta $b000,x
dex
bpl ?lop
A file itself is only a collection of bytes, lots of numbers that can mean both everything and nothing at the same time if you do not know how to interpret them. For this reason, most files are equipped with a variety of headers which store information about what the file contains and are necessary to read them. This includes binary executables loaded under DOS. After all, DOS is a program and like everyone else has a right to expect a certain structure in its data.
Traditional binary files, recognized by all DOSes for Atari XL/XEs, are built of blocks where each block has its own header. There are two types of headers:1. dta a($ffff),a(str_adr),a(end_adr) 2. dta a(str_adr),a(end_adr)
str_adr - address at which the first byte of data will be loaded
end_adr - address at which the last byte of data will be loaded
The first block in the file header must be $ffff, followed by other blocks. Each block header should of course be followed by this amount of data:
(end_adr-str_adr)+1
That's enough of a refresher. The developers of SpartaDOS X have kept the above standard, while adding several new types of headers. The file is still divided into blocks, except that now there are a lot more types of blocks. Here they are:
dta a($fffa),a(str_adr),a(end_adr)
This is the same as a $ffff block - it does not matter which you use. However, $fffa will clearly indicate that the program is designed for SDX - another DOS cannot read the file.
dta a($fffe),b(blk_num),b(blk_id)
dta a(blk_off),a(blk_len)
blk_num - block number in the file. Each relocatable block should have its own number. Because the load addresses of blocks are not known, blocks are just identified by number. They may be in the range [0..7], except that in practice they are usually numbered from 1 upwards.
blk_id - bits 1-5 are the memory type, indicating where the block is to be loaded. I have encountered two values:$00 - main memory $02 - extended memoryAlso, bit 7 indicates no data block if set. In this case, SDX loads nothing, but still reserves memory.
blk_off - block base address, which is simply the address at which the code was assembled. This is necessary when relocating addresses referring to the contents of the block.
blk_len - the length of the block. There should be as much data following as indicated by the header, unless the block is only a reserved area in which case there is no data.
When writing relocatable code, several limitations imposed by the idea of "relocatable code" must be kept in mind. All addresses referring to program areas must be updated during load, so sequences such as this cannot be used:
lda <something
ldx >something
...
something equ *
...
Instead, use something like this, for example:
lda _something
ldx _something+1
...
_something dta a(something)
...
something equ *
dta a($fffd),b(blk_num),a(blk_len)
blk_num - number of block containing the reference targets
blk_len - update block length (without the header). This is ignored.
Addresses are updated by adding the difference between the address at which the relocatable block was loaded and the value of blk_off (where the block was originally assembled). This can be illustrated as follows:
adr=adr+(blk_adr-blk_off)
The payload of an update block contains pointers to addresses and special commands. Numbers
between $00-$fb are offsets from the last updated location. This location is stored
as an address in an update pointer. This pointer can be updated through special functions
invoked by values greater than $fb:
$fc marks the end of the update block,
$fd,a(addr) directly updates the address ADDR. Thus, the update pointer is set to ADDR, which is used as the base for the next offset,
$fe,b(blk_num) sets the update pointer to the base address of the block specified by blk_num, which is then used for the next offset,
$ff increases the update pointer by $fa (without updating an address).
dta a($fffb),c'SMB_NAME',a(blk_len)
SMB_NAME - procedure symbol name (or array, system registry, etc.) Eight characters in ATASCII code,
blk_len - as in a $fffd block.
After the header, there is a sequence of offsets to locations of addresses to update - the same as in a $fffd block. Addresses are updated by adding the address of the procedure denoted by the symbol to the existing address. This allows use in programs of procedures whose addresses are unknown, such as procedures added by other applications running in the SDX environment. Also, system procedures must be used this way, as they have different addresses in different versions of SpartaDOS.
dta a($fffc),b(blk_num),a(smb_off)
dta c'SMB_NAME'
blk_num - number of the block in which the procedure is defined. This means that the procedure must be defined in a relocatable block.
smb_off - procedure offset in the block, which is an offset from the start of the block (the first byte is 0) plus the value of blk_off of the block. Basically, the address at which the procedure was assembled.
SMB_NAME - symbol name being defined for the procedure.
Block types $fffb, $fffc, and $fffd are not kept in memory. The system uses them only during program load.
The syntax for handling SpartaDOS X programs, was taken from FastAssemblera by the author Marka Goderskiego. Below is a quote from the manual that came with FA. MADS can now assemble source files in *.FAS format without any major problems. Relocatable commands always have two-byte arguments; it is impossible to relocate 3-byte arguments (65816).
The most important innovation in SDX is the ability for developers to write relocatable code. Since the MOS 6502 does not have relative addressing (except for short branches), the ICD developers created a way to do so using special program blocks. The process is based on loading blocks and then updating addresses within the blcok using special update blocks. It is enough to add the value of memlo to correct addresses, but what addresses to correct, and which ones to leave? That's the point of a special block that contains (specially coded) offsets to those addresses. Therefore, an UPDATE ADDRESS operation must be applied to each RELOC block before running the program. UPDATE ADDRESS must also be performed on any block which refers to SPARTA commands or vectors.
Another innovation is the introduction of symbols. Some of the SDX service procedures are defined by name! These names always have 8 letters (like filenames). Instead of using arrays of vectors or jumps (like in the OS), use symbols defined with SMB. After loading a block or blocks, SDX loads symbol blocks and updates symbol addresses the same way as with relocatable addresses in the program. Symbols can refer to items in either RELOC or SPARTA blocks.
Programmers can define custom symbols to replace the ones in SDX or completely new ones for use by other programs. This is done by the UPDATE NEW block. It should be kept in mind that new symbols must be stored in a RELOC block.
The number of RELOC and EMPTY blocks is limited to 7 by SDX.
Such blocks can be combined into chains:
blk sparta $600
...
blk reloc main
...
blk empty $100 main
...
blk reloc extended
...
blk empty $200 extended
This means that commands for these blocks can refer to all blocks in the chain.
The chain is not interrupted by updating addresses or symbols, but is ended by the definition of a new symbol and by other block types, e.g. DOS.
Note: The chain only makes sense if all blocks are loaded into the same memory, or when a program switches to the appropriate memory references.
Note: Commands and vectors in RELOC and EMPTY blocks should not refer to SPARTA blocks! This may cause an error when the user loads the program using the LOAD command and uses it after a long time. While RELOC and EMPTY are safe, you never know what is in memory where a SPARTA block was last loaded!
Equally dangerous is the references to RELOC and EMPTY blocks by SPARTA blocks (for the same reason as above), but during the installation of overlays (*.sys) using INSTALL this is sometimes necessary and therefore acceptable. You can also invoke a SPARTA block (through $2E2) to run immediate, then discard it.
Note: Addresses can collide between SPARTA blocks and RELOC/EMPTY addresses! FA recognizes references to other blocks by address, assuming a PC for RELOC and EMPTY blocks of $1000, so for mixed programs SPARTA blocks should be below $1000 (e.g. $600) or above the last relocatable block, $4000 using being enough. This error is not detected by the compiler!
A basic limitation of SDX relocatable code is that only WORD addresses are relocated and there is no support for 65816 code. MADS provides the ability to generate code both in SDX format and in a non-SDX format that removes the aforementioned limitations.
The MADS relocatable code format is similar to that of SDX, as there are main blocks and then blocks with additional address relocation information. MADS uses a simpler update block format, without the "compression" used by SDX.
Advantages of MADS relocatable code:.RELOC [.BYTE|.WORD]A block for updating a relocatable MADS block is created using the BLK pseudo-command:
BLK UPDATE ADDRESSAfter the .RELOC directive, it is possible to specify a relocation block type (.BYTE, .WORD), with the default being .WORD type. type .BYTE is for blocks that must be placed in zero page (instructions will use zero page), which MADS will assemble to address $0000. Type .WORD means that MADS will assemble the block starting at address $0100 and that it can be placed anywhere in memory (except for zero page).
The .RELOC block produces a header like the famous DOS header, further expanded by 10 bytes to a total of 16 bytes:
HEADER .WORD = $FFFF START_ADDRESS .WORD = $0000 END_ADDRESS .WORD = FILE_LENGTH-1 MADS_RELOC_HEADER .WORD = $524D UNUSED .BYTE = $00 CONFIG .BYTE (bit0) @STACK_POINTER .WORD @STACK_ADDRESS .WORD @PROC_VARS_ADR .WORD
| MADS_RELOC_HEADER | always $524D, corresponding to the characters 'MR' (M-ADS R-ELOC) |
| FILE_LENGTH | the length of the relocatable file without the 16-byte header |
| CONFIG | currently only bit 0 is used, where bit0=0 means relocatable blocks are assembled starting at $0000, and bit0=1 means blocks are assembled at $0100. |
The last 6 bytes contain information about the values of labels needed for the operation of the software stack, @STACK_POINTER, @STACK_ADDRESS, and @PROC_VARS_ADR, if used in relocatable blocks. If the individual .RELOC blocks were assembled with different values of these labels, linking them together will produce an Incompatible stack parameters warning. If the software stack labels are not used, the values are zero.
The pseudo-command .RELOC switches MADS to relocatable code generation mode, taking into account the operand sizes of 6502 and 65816 instructions. In this mode, it is impossible to use the ORG, LMB, NMB, and RMB pseudo-commands or the .DS directive. It is impossible to exit MADS relocatable code generation mode, but it is possible to have more than one .RELOC block.
Using the .RELOC directive will also increase the MADS virtual bank counter, and thus create a local scope not visible to other blocks. For more information on virtual banks, see the section Virtual memory banks (OPT B-).
At the end of a .RELOC block it is necessary to generate an update block, using the same BLK syntax for SDX relocatable blocks ("BLK UPDATE ADDRESS"). The encoding format for this block is different than for SDX and is as follows:
HEADER word ($FFEF) TYPE char (B-YTE, W-ORD, L-ONG, D-WORD, <, >) DATA_LENGTH word DATA word [byte]
| HEADER | always set to $FFEF |
| TYPE | data type is stored in bits 0..6 and specifies the type of addresses to update, where "<" means the lower address byte and ">" means the upper address byte. |
| DATA_LENGTH | number of 2-byte data items (addresses) to modify |
| DATA | offsets for updating the relocatable block, where at each address a value of type TYPE is read and then updated to the final address |
The exception is an update block for address high bytes ">", where for such a block an extra BYTE is stored for each address (low byte of address being modified). To adjust the high bytes, the high byte has to be read from the WORD address in the DATA, added to the current relocation address, and then added to the low byte in the BYTE field of the DATA. The newly calculated byte is then store back at the WORD address in the DATA.
External symbols indicate that the represented variables and procedures are stored somewhere outside, beyond the current program. The location need not be specified, only the names and the types. Since the translation of instructions to appropriate machine code depends on the type of data represented by a symbol, the assembler needs to know the size of the data used.
Currently there is no support for manipulating external symbols of type '^' (highest byte).External symbols may be used in both relocatable .RELOC blocks as well as usual DOS ones.
External symbols are declared using the pseudo-command EXT or the .EXTRN directive:label EXT type label .EXTRN type .EXTRN label1,label2,label3... typeAn update block for referencing external symbols is produced using the BLK pseudo-command:
BLK UPDATE EXTERNALNote: This will write symbol names used by the program.
External symbols of value type can also be defined (.BYTE, .WORD, .LONG , .DWORD):
name EXT .BYTE label_name EXT .WORD .EXTRN label_name .WORD wait EXT .PROC (.BYTE delay)External symbol declarations of procedure (.PROC) type default to .WORD, and attempts to reference the label will be interpreted by MADS as an attempt to call the procedure. For more about procedure calls, see Procedures.
During the assembly process, references to external symbols are replaced with the value zero.
External symbols can be useful when assembling part of a program separately from the rest of it. In this case, there are often references to other procedures and variables are defined elsewhere, and only the type and not value are known. With the help of external symbols, such a program can be assembled without the full procedure and variable definitions.
Another use for external symbols is for "plugins", or external programs connected to the main program to add additional functionality. These are types of libraries, using the procedures of the main program and expanding its capabilities. Creating such a plugin requires determining what procedures the main program provides (their name + parameters and type), and a procedure to read the file from the external symbols, which then attaches the plugins to the main program.
The following is the format of the file header produced by BLK UPDATE EXTERNAL for external symbols of type B-YTE, W-ORD, L-ONG, and D-WORD:
HEADER word ($FFEE) TYPE char (B-YTE, W-ORD, L-ONG, D-WORD, <, >) DATA_LENGTH word LABEL_LENGTH word LABEL_NAME ATASCII string DATA word .. .. ..
| HEADER | always set to $FFEE |
| TYPE | bits 0..6 contain the type of data to modify |
| DATA_LENGTH | number of 2-byte data items (addresses) to modify |
| LABEL_LENGTH | symbol name length in bytes |
| LABEL_NAME | the symbol name, coded in ATASCII |
| DATA | offset data for the relocation process. At each indicated address here, the value of type TYPE is read and then based on the actual value of the symbol. |
An applied example of external symbols and .STRUCT structures is the graphical primitives library in the directory "..\EXAMPLES\LIBRARIES\GRAPHICS\LIB". Individual modules use a fairly large number of zero page variables, and if we want to place these in relocatable code each individual variable would have to be declared as an external symbol by EXT (or .EXTRN). We can simplify this by using only one symbol and external data of structure (.STRUCT) type. These structures define a "map" of variables called ZP and one ZPAGE external symbol, of type .BYTE because we want it to be in zero page. Now, when referring to the variable, we need to do it in a way that forces relocation such as ZPAGE + ZP.DX. The result is a fully relocatable module with relocated variables in zero page.
Public symbols make variables and procedures available to other blocks in the rest of the relocatable assembly program. With public symbols, you can refer to variables and procedures from libraries.
Public symbols can be used in relocatable .RELOC blocks as well as usual DOS blocks.
MADS automatically detects whether a public label is a variable, constant, or procedure defined by .PROC, and does not need any additional information as for external symbols.
Public symbols are declared using the following directives:.PUBLIC label [,label2,...] .GLOBAL label [,label2,...] .GLOBL label [,label2,...]The directives .GLOBAL and .GLOBL have been added for compatibility with other assemblers. They are identical in meaning to .PUBLIC directive. Update blocks for public symbols are created using the BLK pseudo-command:
BLK UPDATE PUBLIC
Below is the header format produced by BLK UPDATE PUBLIC:
HEADER word ($FFED) LENGTH word TYPE byte (B-YTE, W-ORD, L-ONG, D-WORD) LABEL_TYPE char (C-ONSTANT, V-ARIABLE, P-ROCEDURE, A-RRAY, S-TRUCT) LABEL_LENGTH word LABEL_NAME ATASCII string ADDRESS wordMADS automatically selects the appropriate type for public labels:
> C-ONSTANT label is not subject to relocation V-ARIABLE label is subject to relocation P-ROCEDURE procedures defined by .PROC, subject to relocation A-RRAY array defined by .ARRAY, subject to relocation S-TRUCT structure defined by .STRUCT, not subject to relocationIf the symbol is of structure (.STRUCT) type, this additional information is saved (the type of the structure, name of the structure, and number of elements of the structure):
STRUCT_LABEL_TYPE char (B-YTE, W-ORD, L-ONG, D-WORD) STRUCT_LABEL_LENGTH word STRUCT_LABEL_NAME ATASCII string STRUCT_LABEL_REPEAT wordIf the symbol is of array (.ARRAY) type, this additional information is saved (maximum element index, declared element type):
ARRAY_MAX_INDEX WORD ARRAY_TYPE CHAR (B-YTE, W-ORD, L-ONG, D-WORD)If the symbol is of procedure (.PROC) type, this additional information is saved, regardless of whether the procedure does or does not declare parameters:
PROC_CPU_REG BYTE (bits 00 - regA, 01 - regX, 10 - regY) PROC_TYPE BYTE (D-EFAULT, R-EGISTRY, V-ARIABLE) PARAM_COUNT WORD
.REG symbols of the procedure are then included, of PARAM_COUNT count:
PARAM_TYPE CHAR (B-YTE, W-ORD, L-ONG, D-WORD) ...
.VAR symbols of the procedure are included next along with their names, where PARAM_COUNT specifies the total length of the data:
PARAM_TYPE char (B-YTE, W-ORD, L-ONG, D-WORD) PARAM_LENGTH word PARAM_NAME ATASCII string ...
| HEADER | always set to $FFED |
| LENGTH | number of symbols stored in the update block |
| TYPE | type of symbol data: B-YTE, W-ORD, L-ONG, D-WORD |
| LABEL_TYPE | symbol type: V-ARIABLE, C-ONSTANT, P-ROCEDURE, A-RRAY, S-TRUCT
For type P, additional stored information: PROC_CPU_REG, PROC_TYPE, PARAM_COUNT, PARAM_TYPE For type A, additional stored information: ARRAY_MAX_INDEX, ARRAY_TYPE For type S, additional stored information: STRUCT_LABEL_TYPE, STRUCT_LABEL_LENGTH, STRUCT_LABEL_NAME, STRUCT_LABEL_REPEAT |
| LABEL_LENGTH | public symbol name length in bytes |
| LABEL_NAME | public symbol name stored as ATASCII |
| ADDRESS | address assigned to the symbol in the relocatable .RELOC block. This value is relocated by adding its current assembly address. |
| PROC_CPU_REG | information about the .REG CPU register usage records for a procedure |
| PROC_TYPE | type of procedure:
|
| PARAM_COUNT | information about the number of register-passed parameters (.REGs) or total length of type and name data for variable-passed parameters (.VARs) |
| PARAM_TYPE | parameter types, recorded using the characters 'B', 'W', 'L', 'D' |
| PARAM_LENGTH | parameter name length (.VAR) |
| PARAM_NAME | parameter name coded as ATASCII (.VAR) |
.LINK 'filename'The directive .LINK requires as a parameter the filename of the file to link. Only Atari DOS files are accepted, not SDX files.
If the file load address is different than $0000, it means that the file does not contain relocatable code, but may include update blocks for external and public symbols. The .LINK directive accepts files at any address, but only those starting at $0000 are subject to relocation. More information on how to construct such a file is included in the section Relocatable blocks (.RELOC).
The .LINK directive allows linking of relocatable and non-relocatable code. MADS automatically relocates the file based on all three types of update blocks (ADDRESS, EXTERNAL, PUBLIC).
There is no limit to the address at which a file can be relocated.If a block being relocated requires the MADS software stack, the labels @STACK_POINTER, @STACK_ADDRESS, and @PROC_VARS_ADR are automatically updated based on the .RELOC block header. It is necessary that the main program and the .RELOC blocks operate on the same software stack.
LDA LDX LDY STA STX STY ADC AND ASL SBC JSR JMP LSR ORA CMP CPY CPX DEC INC EOR ROL ROR BRK CLC CLI CLV CLD PHP PLP PHA PLA RTI RTS SEC SEI SED INY INX DEY DEX TXA TYA TXS TAY TAX TSX NOP BPL BMI BNE BCC BCS BEQ BVC BVS BITA mnemonic extension can be placed after a dot '.' for LDA, LDX, LDY, STA, STX, and STY:
.b or .z BYTE .a or .w or .q WORD e.g.: lda.w $80 ; AD 80 00 lda $80 ; A5 80
ASO RLN LSE RRD SAX LAX DCP ISB ANC ALR ARR ANE ANX SBX LAS SHA SHS SHX SHY NPO CIM
STZ SEP REP TRB TSB BRA COP MVN MVP PEA PHB PHD PHK PHX PHY PLB PLD PLX PLY RTL STP TCD TCS TDC TSC TXY TYX WAI WDM XBA XCE INA DEA BRL JSL JMLIt is possible to use XASM-style a:, z:, and r: mnemonic extensions:
XASM MADS lda a:0 lda.a 0 ldx z:0 lda.z 0 org r:$40 org $40,*Mnemonic extensions can be placed after a dot ('.') for LDA, LDX, LDY, STA, STX, and STY:
.b or .z BYTE .a or .w or .q WORD .t or .l TRIPLE, LONG (24bit) e.g.: lda.w #$00 ; A9 00 00 lda #$80 ; A9 80
The following commands cannot have their operand given in absolute addressing form (some assemblers do not require the '#' character, but MADS requires it):
SEP REP COP
PEA
Another exception is long indirect addressing mode, which is represented by square brackets [ ]. These brackets are used to group subexpressions, but if the assembler encounters the character '[' first it is interpreted as the start of long indirect addressing, and if 65816 mode is not enabled a Illegal adressing mode results. To "trick" the assembler, place the character '+' before the '['.
lda [2+4] ; lda [6] lda +[2+4] ; lda 6
/* How to detect on which CPU the assembler code is running (This information is from Draco, the author of SYSINFO 2.0) You can test on plain 6502-Code if there is a 65c816 CPU, the 16-Bit processor avaible in some XLs as a turbo-board, avaible. Draco told me how to do this: First we make sure, whether we are running on NMOS-CPU (6502) or CMOS (65c02,65c816). I will just show the "official" way which doesn`t uses "illegal opcodes": */ org $2000 opt c+ DetectCPU lda #$99 clc sed adc #$01 cld beq DetectCPU_CMOS DetectCPU_02 ldx #<_6502 ldy #>_6502 jsr $c642 lda #0 rts DetectCPU_CMOS lda #0 rep #%00000010 ;reset Z bit bne DetectCPU_C816 DetectCPU_C02 ldx #<_65c02 ldy #>_65c02 jsr $c642 lda #1 rts DetectCPU_C816 ldx <_65816 ldy >_65816 jsr $c642 lda #$80 rts _6502 dta c'6502',$9b _65c02 dta c'65c02',$9b _65816 dta c'65816',$9bThe next CPU detection example is limited to distinguishing a 6502 from a 65816 microprocessor. The program instructions are read differently by a 6502 than by a 65816. A 6502 executes 'INC @' and a 'NOP' instead of 'XBA' followed by 'SBC #'. With this "transparency" we can be assured that the program does not perform illegal operations and correctly recognizes the correct CPU. The idea for this concise and very clever test comes from Ullrich von Bassewitz.
org $2000 opt c+ ; 65816 enabled lda #0 inc @ ; Increment accumulator cmp #1 bcc cpu6502 ; ultimate test for 65816 presence xba ; Put $01 in B accu dec @ ; A=$00 if 65C02 xba ; Get $01 back if 65816 inc @ ; Make $01/$02 cmp #2 bne cpu6502 cpu65816 ldx <text65816 ldy >text65816 jsr print_text rts cpu6502 ldx <text6502 ldy >text6502 jsr print_text rts text6502 dta c'6502',$9b text65816 dta c'65816',$9b
LMB #value NMB RMB
lmb #0 lmb #bank lmb #5 , $6500 ; only with OPT B+
nmb nmb $6500 ; only with OPT B+
rmb rmb $3500 ; only with OPT B+ rmb $8500 ; only with OPT B+During assembly, MADS assigns the current bank counter value to each newly defined label. The programmer can affect the value of the bank counter through pseudo-commands.
In MADS, the term "virtual memory bank" refers to any area designated by a newly defined label set to the current value of the bank counter (the default bank counter is zero). That is, a virtual memory bank is not necessary a memory area in the range [$4000..$7FFF], but each label presents a code area of the program which has been assigned a code (bank counter value) in the range [$00..$FF] with appropriate pseudo-commands for use by the programmer (NMB, RMB, LMB).
The exceptions are .RELOC blocks, where the bank counter cannot be changed manually and is automatically updated by MADS, incrementing it for each instance of the .RELOC directive. Bank counter values are in the range [$0001..$FFF7].
The programmer can read the bank counter value associated with a label using the equal operator '=':label ldx #=labelIn this example, the X CPU register is set to the bank counter value associated by MADS with the label LABEL. Another useful operator is the colon (':') placed at the beginning of the label name, which causes MADS to allow references to labels outside of the area specified by the current MADS bank counter. Sometimes this may cause problems, such as if there is more than one label with the same name in different local scopes or in areas with a different virtual bank number.
lmb #5 label5 nop lmb #6 label6 nop lda :label5In this example, without the operator ':' at the beginning of the label name in the instruction 'lda :label5' the error ERROR: Undeclared label LABEL5 (BANK=6) would result. Virtual memory banks can be used to index an array containing values for the PORTB register. This is the main use of the option OPT B+.
@BANK_ADD @BANK_JMPand requires label definitions with the names:
@TAB_MEM_BANKS @PROC_ADD_BANKThe @TAB_MEM_BANKS label specifies the address of an array with values to write to the PORTB register for switching extended memory banks. You can take advantage of pre-written expanded memory bank detection routines included with MADS, in the file "..\EXAMPLES\PROCEDURES\@MEM_DETECT.ASM". The label @PROC_ADD_BANK is used by the macro @BANK_ADD and defines the address of the code for switching extended memory banks. The programmer can read the bank counter value associated with a label using the '=' operator:
label ldy #=labelIn this example, the Y register is set to the memory bank value associated with the label LABEL by MADS.
If the MADS bank counter = 0:
If the MADS bank counter > 0:
An example of the use of MADS bank sensitive mode can be found in the file "..\EXAMPLES\XMS_BANKS.ASM". In this example, the program code is located in two different extended memory banks and runs as if it were a single program.
temp set 12
lda #temp
temp set 23
lda #temp
XASM MADS lda a:0 lda.a 0 ldx z:0 lda.z 0
XASM MADS org r:$40 org $40,*
line0 line1 line2 line3 ladr :4 dta l(line:1) hadr :4 dta h(line:1)
@ dex ---- ------- bne @+ | -- | stx $80 | | | @ lda #0 | -- | bne @- --- | bne @-1 ---------
.var temp .word #if temp>#2100 #end #if .word temp>#2100 #end
pla $00 -> ERROR: Extra characters on line pha:pla $00 -> OK
.macro SetColor val,reg lda :val sta :reg .endm .macro SetColor2 (arg1, arg2) lda #:arg1 sta :arg2 .endm
temp label = 100
.struct name .byte label0 .byte :5 label1 label2 .byte label3 :2 .word .ends
.extrn vbase .word org $2000 lda #$80 sta vbase+$5d blk update extrn
.struct LABEL x,y,z .word // wiele zmiennych tego samego typu w jednej linii .byte a,b .ends .enum type a=1,b=2 .ende .struct label2 x type type y .ends
.enum typ val0 = 1 val1 = 5 val2 = 9 .ende lda #typ(val0|val2) ; == "lda #typ.val0|typ.val2"
.sav 'filename',offset,length .sav 'filenema',length .sav [offset] 'filename',offset2,length .sav length .sav offset,length
.array temp .byte 1,4,6 ; [0..2] = 1,4,6 [12] = 9,3 ; [12..13] = 9,3 [5]:[8] = 10,16 ; [5..6] = 10,16 ; [8..9] = 10,16 0,0,\ ; [14..17] = 0,0,1,1 1,1 .enda ; 18 elementów, TEMP [0..17]
.struct Point x .byte y .byte .ends .var a,b,c Point .zpvar Point f,g,i
.enum Boolean false = 0 true = 1 .ende .var test Boolean .zpvar Boolean test
.enum EState
DONE, DIRECTORY_SEARCH, INIT_LOADING, LOADING
.ende
.struct SLoader
m_file_start .word
m_file_length .word
m_state EState
.ends
.VAR .TYPE lab1 lab2 lab3 .TYPE lab4 .TYPE lab5 lab6 ... .var .byte a,b,c .dword i j
.struct @point x .byte y .byte .ends pointA @point ; pointA dta @point [0] <=> pointA dta @point pointB @point ; pointB dta @point [0] <=> pointB dta @point points dta @point [100]
.ZPVAR TYPE label1, label2 label3 = $80 ; LABEL1=$80, LABEL2=LABEL1+TYPE, LABEL3=LABEL2+TYPE .ZPVAR label4, label5 TYPE ; LABEL4=LABEL3+TYPE, LABEL5=LABEL4+TYPE .print .zpvar
ERT *>$6000 , 'BUUU przekroczyliśmy zakres pamięci o ' , *-$6000 , ' bajtów'
.proc copySrc (.word src+1) .var .proc ToDst (.word src+1, dst+1) .var .endp ldy #0 src lda $ffff,y dst sta $ffff,y iny bne src rts .endp copySrc.ToDst #$a080 #$b000 copySrc #$a360
.enum dni_tygodnia poniedzialek = 1 wtorek, sroda = 5, czwartek = 7 piatek sobota niedziela .ende ift dzien==dni_tygodnia::wtorek .print 'wtorek' eif
lda #12+ /* komentarz */ 23
.reloc .def label=* lda label
#while .word ad+1<=#$bc40+39
{
ad sta $bc40
inw ad+1
}
.proc lab
{
.local temp2
{
}
.array tab [255] .long
{}
}
[label] .ALIGN N[,fill]
macro_temp \ _____________________________________parametr1_________________________________________________\ _____________________________________parametr2_________________________________________________\ _____________________________________parametr3_________________________________________________ lda\ #____________________________________label________________________________________\ +__________________________________expression___________________________________
.REPT 10, # label:1 ; LABEL0, LABEL1, LABEL2 ... LABEL9 .ENDR .REPT 5, $12,$33,$44,$55,$66 dta :1,:2,:3,:4,:5 ; $12,$33,$44,$55,$66 dta :5,:4,:3,:2,:1 ; $66,$55,$44,$33,$12 .ENDR
# if .byte i>#8 .and .byte i<#200
# else
#if .word j = #12
#end
# end
lda 20 -> lda 20
# while .byte @=20 -> wait cmp 20
# end -> sne
-> jmp wait
opt r- opt r+
mva #0 $80 -> lda #$00 -> lda #0
mva #0 $81 -> sta $80 -> sta $80
lda #$00 -> sta $81
sta $81 ->
.def label = 1
.def :label
.local namespace .proc proc1 .endp .endl .local namespace .proc proc2 .endp .endl
.test .byte k>#10+1 .or .word j>#100 .and .word j<#105 .or .byte k<=#5 ... ... .endt
.while .byte k>#4 .and .byte k<#39 ... ... .endw
pi .fl 3.1415926535897932384626433832795 ; 40 03 14 15 92 65 tb .fl 0.5 12.34 -2.30 0.00002 tb .fl 0.5, 12.34, -2.30, 0.00002
.struct test x :200 .byte y :999 .long .ends buf dta test [0]
.reloc lda temp temp .long $aabbcc
@point .struct
x .byte
y .byte
.ends
a @point
b @point
c @point
.struct @id id .word .ends .struct @mem @id adr .word .ends
mwa ($80),y $a000,x mwa $bc40,y ($f0),y mwa ($80),y ($82),y
.extrn a,b,c,d .byte x y z .word line .proc(.byte x,y) .reg
.var x y z .byte bit :2 .dword = $80
move .proc (.word src+1,dst+1) .var
src lda $ffff
dst sta $ffff
.endp
.nowarn PROCNAME
phr -> pha plr -> pla
txa tay
pha pla
tya tax
pha pla
ADB $80 #12 $b000 -> lda $80
clc
adc #12
sta $b000
SBB #200 $a000 -> lda #200
sec
sbc $a000
sta $a000
lda 0x2000 ldx #0x12 temp = 0x8000
adw hlp #40 ; hlp=hlp+40 adw hlp #20 pom ; pom=hlp+20
:12 makro #
.EXTRN a b c d .word .VAR i = 1 j = 2 .byte .VAR a b c d .byte
.var i = 10 j = 12 .byte .var a , b = 2 .byte
ldx #$ff
.while .word adr < #$bc40+40*24
stx $bc40
adr: equ *-2
inw adr
.endw
.test .byte (@>=#'a')
.test .byte (@<=#'z')
.endt
.endt
lda:cmp:req 20 ldx:ldy:lda:iny label
?label -- -> ?label=?label-1 ?lab ++ -> ?lab=?lab+1 ?temp += 3 -> ?temp=?temp+3 ?ofset -= 5 -> ?ofset=?ofset-5
.proc nazwa (.byte a,b,c .word d,e) .endp
Comments
Comment lines must start with a ';' or '*'. For a single-line comment, however, the safest way is to use a semicolon (';'), as the asterisk ('*') has other meanings and can mean multiplication or the current assembly address. On the other hand, the semicolon is dedicated only for comments. The characters '//' can also be used for a single-line comment, and '/* */' for a multi-line or inline comment.* this is a comment ; This is a comment lda #0 ; This is a comment dta 1 , 3 * BAD COMMENT, WILL BE MISINTERPRETED org $2000 + 1 BAD COMMENT, WILL BE MISINTERPRETED nop // this is a comment // this is a comment dta 1,2, /* comment */ 3,4 lda /* comment */ #0 /* ... this is a multi-line comment ... */ /************************************ this is also a multi-line comment *************************************/The multi-line comment signs '/* */' and the end-line comment sign '//' can be used without restrictions.