AMAC  Atari 6502 (by: Nick Kennedy)
(HTML conversion by SysOp Fox-1, June 2000)
Back to Index Back to Programming Info

TABLE OF CONTENTS

The AMAC Atari Macro Assembler

Introduction

Using AMAC
  Some Examples Some examples
  Macro Examples Macro examples
  More Example Source Code More example source code
  Some Operators Some operators

Some error-codes

Assembling with PC Xformer

How to get rid of the copy protection


Back to Programming Info Top of Page

THE AMAC ATARI MACRO ASSEMBLER

This file is intended to give a little bit of direction to anyone in possession of AMAC but not having documentation. It also tells how to remove the copy protection. It's thrown together and unstructured, but perhaps contains enough info to allow you to make use of AMAC.

Table of Contents Table of Contents


INTRODUCTION

AMAC came out in 1981, I believe. It wasn't very successful because it ran headlong into the famous MAC-65 cartridge. AMAC had a number of disadvantages. It was disk based (as opposed to keeping source code in memory), which tended to make it very slow on big files. It didn't have an integrated environment (editor was separate). It had a bug in its macro facility which caused it to blow up if macros over a certain length were written. (I wrote Atari about this bug, but they didn't respond. My guess is that they decided not to support AMAC since it was getting creamed by MAC-65 anyway.) It also had horrible copy protection of the type that causes the disk to grind and reset several times on each loading. Since programming sessions often include multiple toggles between assembler and editor, this was intolerable.

Why would I now recommend AMAC despite these flaws?
Simply because most of them can be eliminated, making it a fine assembler. The copy protection was easy to remove. The macro bug wasn't a big deal after I found that only long macros cause the problem. (Most macros are small, anyway.) The lack of a built in editor now looks like an advantage. AMAC will work with plain text source files from any editor. Integrated environment type cartridges like BASIC and MAC-65 now seem hokey with their forced line numbers and the requirement to hit Return on each edited line, etc. The speed problem went away with the advent of things like ramdisks, SIO2PC, and Darek's Xformer. AMAC doesn't flinch at large files. I now like to edit on a PC plain text editor, and use Xformer to run AMAC on the PC, accessing the file directly (after converting CR/LFs to ATASCII EOLs).

I've pointed out AMAC's flaws and how they are overcome. Outside of the flaws I mentioned, AMAC is a great 6502 assembler: robust, flexible, and full featured.

The AMAC Atari Macro Assembler The AMAC Atari Macro Assembler        Table of Contents Table of Contents


USING AMAC

The syntax used by AMAC is pretty standard. Knowing the command line functions is critical.

First, run AMAC. Then, you're prompted for a source file and options. The first entry must be the filespec you want to assemble. The other options are:

H=Dn         put output file on disk #n
H=<filespec> put output file to spec given
H=0          don't generate object code
L=P:         listing to printer
L=Dn:        listing to disk #n,
             (name.PRN is filename)
L=S:         listing to screen
L=0          default (no listing)
             (this can speed assembly)
O=n          make run address = n
             (same as END n statement)
S=<filespec> specify systext file
S            use default systext D:SYSTEXT.AST
S=0          specify no systext file for this
             assembly
R=F          full reference map
R=S          short reference map
R=0          no reference map

Here's a sample command line to put AMAC into operation:

D:KEYER.SOR S=D2:SYSTEXT L=S:

Assembles my file KEYER.SOR. Looks for a systext file on D2:. Puts a listing to the screen. The object file will be KEYER.OBJ by default and will go to D:.

Break stops assembly; CTRL-1 pauses assembly if listing to screen is in effect.

I'm not going to try to list everything in AMAC's syntax; just stuff I use a lot.

Remember, a label (if any) starts in the first column; then tab or spaces; then an opcode or pseudo op; then data; then a semicolon starts a comment field if used.

Labels end in a space. Ending in a colon is acceptable but not required and the colon isn't part of the label when you reference it.

Labels can be made from UC or LC letters, numbers, and the symbol @. A colon starts a local label. The underscore character can appear in a label for readability but isn't considered part of the label. So AB_CD is the same label as ABCD. Only the first 6 characters of a label are significant.

Introduction Introduction        Table of Contents Table of Contents

Here are some examples:

STORE: DS 1; define one byte of memory storage
EOL:   DB $9B ; define a variable called EOL

START: LDA #2 ; load accumulator with the number 2.

Note that the # meant load A with the immediate value 2. Without the #, the same line would load A with the value stored at memory location $0002.

Confusing memory labels with immediate values is one of the most vexing problems for beginners.

Usually, memory is referenced by a name (label) rather than absolute value.

  STA STORE ; store accumulator at memory location
  called STORE.
  LDX EOL ; load X with number stored at EOL

END START

An END statement must appear at the end of the file. If you follow it with an address (here, the label START), the address will be the run address of the program created.

Using AMAC Using AMAC

Here's an example of a macro:

; This MACRO puts a record. The
; parameters are IOCB # and
; address of RECORD.
;
PUTREC: MACRO I, A
  LDX #%1*$10
  LDA #0
  STA ICBLH,X
  LDA #$80
  STA ICBLL,X
  LDA #9
  STA ICCOM,X
  LDA #LOW %2
  STA ICBAL,X
  LDA #HIGH %2
  STA ICBAH,X
  JSR CIOV
  ENDM

The I, A arguments are dummies; you refer to them by %1 and %2 in the macro.

The ENDM statement ends the macro.

****You must precede it with an ATASCII tab.****

If your editor uses multiple spaces for tabs, hit ESC-TAB on your atari to put in the right pointing triangle TAB symbol.

I'd use this macro like this:

LABEL:  PUTREC 0, TEXT

0 is for IOCB 0 (the screen) and TEXT would be a label for the address where I'd stored a string, like this:

TEXT:  DB `whats going on???',$9b

As you can see, the macro has been used to make a kind of simple PRINT statement.

You will usually start your file with a bunch of `equates' which are labels for addresses or numbers you want to refer to by name. (Or you might put these in a "systext" file.) Here's an example of what might go in one:

WSYNC   EQU  $D40A
COLPF1  EQU  $D017
COLPF2  EQU  $D018
COLBK   EQU  $D01A
COLOR1  EQU  $2C5
COLOR2  EQU  $2C6
COLOR4  EQU  $2C8  ;Background
NMIEN   EQU  $D40E ;b7/DLI b8/VBI
VDSLST  EQU  $200  ;NMI OS vector
DOSVEC  EQU  $000A ;this program's start vector
DOSINI  EQU  $000C ;this program's init vector
POKMSK  EQU  $0010 ;mask for POKEY IRQ enable
RTCLOK  EQU  $0012 ;60 Hz clock
VIMIRQ  EQU  $0216 ;immediate IRQ vector
SSKCTL  EQU  $0232 ;serial port control
MEMLO   EQU  $02E7 ;start of user memory
CH      EQU  $02FC ;character buffer
ICCOM   EQU  $0342 ;CIO command block

Note that you can use the EQU pseudo-op or = interchangeably, as in:

ICCOM = $0342

Note that these equates don't actually put anything into memory or into the object file. They just associate a number with a symbol, so you can refer to it by symbol name in your program.

Using AMAC Using AMAC

Here's some more example source code:

; Clear the message buffer area:
  LDA #0; Set up FILLA routine to
  STA ZP1L; clear BMESS thru +FFF
  LDA #HIGH BMESS; to all 0's.
  STA ZP1L+1
  LDA #$FF; Low byte is abs $FF
  STA SIZEB
  LDA #HIGH [BMESS + $FFF]
  STA SIZEB+1
  LDA #0
  JSR FILLA

  JSR RINIT; `Real' Initialization
; Init ZBUF to point to TBUF:
  LDA #LOW TBUF
  STA ZBUF
  LDA #HIGH TBUF
  STA ZBUF+1

; Point vertical blank to my custom
; VBI routine:

DOMYV LDX #HIGH MYVBI
  LDY #LOW MYVBI
  LDA #6; Immediate VBI
  JSR $E45C

You can see some operators here. HIGH and LOW extract the high and low bytes of a word, for example. (Addresses are stored as words.) Note also that expressions can include math, such as ZBUF+1, which refers to address ZBUF plus 1.

Using AMAC Using AMAC

Here are some operators AMAC uses:

+, -, /, * ; add, subtract, divide, multiply
NOT - bit by bit complement
AND - logical and
& - same as AND
OR - logical OR
XOR - logical exclusive OR
= logical equal, also EQ
<> not equal, also NE
< less than, also LT
> greater than, also GT
<= less than or equal, also LE
>= greater or equal, also GE
SHR right shift n bits
SHL left shift n bits

***Brackets [] (not parenthesis) set precedence levels.***

As you'd expect, there are IF and ENDIF statements to use the logical tests and conditionally skip sections of code.

There's a PROC ... EPROC feature to allow defining procedures, which just means local symbol ranges. Local labels must start with a colon. I use it because on big files it gets hard to think of unique labels for jump targets, etc, after you've used a zillion of them. Within a PROC you can assign local labels accessible only within the PROC and reusuable elsewhere. Like this:

; Copy the character set down to
; $4000:

  PROC
  LDA #$40
  STA :MVCH+5; Init self altering
  LDA #0; code.
  STA :MVCH+4
  STA :MVCH+1
  LDA #$E0
  STA :MVCH+2
:LOOP   LDY #0
:MVCH   LDA $E000,Y; Get char from ROM
  STA $4000,Y; put it into RAM
  INY
  BNE :MVCH; Done with a page?
  INC :MVCH+2; Increment pages, ROM
  INC :MVCH+5; and RAM
  LDA :MVCH+2; Done all 4 pages?
  CMP #$E4
  BNE :MVCH; If not, loop...
  LDA #$40; Point CHBAS to new base:
  STA $2F4
  STA $D409; Hardware CHBASE

; Now make return and carat print
; as spaces:

  LDY #7
:LP2 LDA #0
     STA $41F0,Y; $41F0 starts ^
     LDA #$FF; Return prints inverse
     STA $42D8,Y; $42D8 starts ret.
     DEY
     BPL :LP2
     EPROC

The ORG statement is used to tell the assembler where in memory to put the code it generates. Therefore, there's got to be an ORG statement in the program before any code or data statements. You can also put in additional ORGs if you need to make the code discontinuous.

Also, the star * is the current value of the location counter: it's the value of the next memory address the assembler is going to use. See the ORG in the program starting below:

;             KEYER
; An electronic keyer program by
; Nick Kennedy. Begun: 5/17/87.
; r1.1: I moved the ORG up to $8000 to
; get out of the EMDE expansion window
; because MYVBI and the new DLIST have
; NMI's which may occur during RAMDISK
; I/O, causing lockup. Could just get
; the VBI AND DLIST out if space were
; a problem.

; POKEY TIMER AND AUDIO USAGE:
; TV sidetone source: Channel 3
; Sidetone via console spkr: Ch. 4

; Code element timer (16 bit res.):
; Channel 1 into Channel 2, IRQ from
; Channel 2. Note: Hi byte to 2; Low
; to 1.
VTIMR4    EQU $214
VTIMR2    EQU $212
FPREG     EQU $CB; Point to speed/ratio
KBAT EQU $4D; OS sets to 0 when key
;             pressed.
ATRCT     EQU $4F; My attract flag. Go
; to attract mode when b7 is set.
; BMESS is the start of the message
; storage area. Note there is a $1000
; byte limit (4K) due to ANTIC scroll
; counter limit.
BMESS     EQU $3000
; TBUF is the 256 byte transmit buffer
; for the keyboard sender...
  ORG $8000
TBUF EQU *-$100

;            INITIALIZE:

INIT:     JSR POKINI

You can see above, I ORG'd the program to $8000, then used the program counter to compute an address $100 bytes lower to use for a buffer. The program will start at $8000 which is the value of label INIT and contains the code, JSR POKINI.

Oh yeah, you can have INCLUDE files. This means you can break your source code down into multiple files and a statement within one file will cause the assembler to open another and resume assembly there:

  INCLUDE D2:FILE2.SOR

for example. At one time, I had a shell type executive source file which just had and ORG and a few equates, then half dozen INCLUDE statements to bring in other files, then an END statement. Now I use emulators and PC editors which aren't daunted by big files, so I don't split `em up anymore. There can be other reasons to split files up, though.

Getting back to pseudo-ops that declare memory space or data storage...

  DB is flexible in storing single or multiple bytes or
  character strings.
  DB 100 ; store single byte 100
  DB 1, 7, $25 ; store three bytes (separate in the DB
  statement by commas)
  DB `Howdy', $9B ; store a string, followed by a byte
MWEN DW BMESS + 320; End of M-window

   ; Above stores a word with calculated value of BMESS +
   320 and assignes the label MWEN to it.

   Note that labels don't have to end in colons, although
   they can if you are used to that style.

   DB HIGH MWEN ; store high byte of word MWEN

   Note again that EQU and = don't actually cause
   anything to be stored in memory, but DB and DW do.

Numbers of different bases:

Use % for binary, as in %11001010
Use $ for hex, as in $01FF
Use plain for decimal, as 300
Use @ for octal as in @212 (why use octal? don't know)

Symbols (labels) must be unique in first 6 chars (others are ignored). Must start with a letter or number, or colon for local. Can start with ? and be excluded from reference map.

Using AMAC Using AMAC        Table of Contents Table of Contents


Some Error Codes

A - Address error, addressing mode not supported by opcode

D - Duplicate label; using same label twice

E - Expression error; can't understand expression in the address field of the source line

I - Instruction field not recoginzed, three NOPs are generated

L - Label field not recognized, three NOPs are generated

N - Number error, doesn't match radix; > 16 bits, etc.

S - Syntax error in statement; too many or two few address subfields

U - Reference to an undefined symbol. (Very popular error! You mistyped a label name.)

V - Expression overflow; value truncated

Using AMAC Using AMAC        Table of Contents Table of Contents


Assembling with PC Xformer

Here's a file I wrote to remind myself of how I use AMAC to assemble a big project on the PC using Xformer:

TO_NICK.TXT

This file is intend to remind me of what form the KEYER program is in and how I currently assemble it:

I've copied all the individual files into one big file called BIGKEY.PCF. PCF means PC file. This file has the all the EOLs (155 or $9B) converted to CR/LF (13,10) and the ATASCII TABs converted to ASCII TABs (127 or $7F to 09).

I edit the PCF file on the PC. Then I use batch file BIG_2_ATR.BAT to convert the CR/LFs and TABs back to Atari style using the utility TT. I make TT create a separate file called BIGKEY.ATF to be accessed by AMAC. Next, I run XFORMER on the PC, loading a disk image with DOS and AMAC as D1: and BIGKEY.ATF as D2. AMAC WILL run on D1 and assemble the native PC file BIGKEY.ATF from D2. It takes a couple of minutes to assemble. I use this command line:

L AMAC [return], then AMAC runs, then

  D2:BIGKEY.ATF H=D:KEYER.OBJ

So the object code is put on the disk image D1.

Error-Codes Error-codes        Table of Contents Table of Contents


How to get rid of the copy protection
(Plus a little tutorial on Atari DOS file structure and function.)

You need a sector editor and you need to know how to look at the directory to locate the first sector number of the file AMAC.

My disassembly of the first sector of the file showed that the first thing it does is set up to read a bad sector. The way to fix it is to make the RUN (or maybe it's INIT?) address start right after the bad sector check, effectively bypassing it. If you fix it with a sector editor, you'd see that the first sector of the file starts with the six byte header FF FF 00 26 2B 26. That tells DOS to load the file starting with the 7th byte (meaning the 6 byte header isn't part of the file), putting it at memory $2600 thru $262B. (The FFs are a RUN file identifier, 00 26 means start loading at $2600 and 2B 26 means end loading at $262B.)

This first segment loaded ends with the 50th byte of the sector (which is the 44th byte of the file). Next comes the header (Note: no FF's, which are optional except for the ones in the first segment's header) for the next load segment. It's just two bytes, loaded at $02E2 thru $02E3. This has special meaning to DOS. In a multi-segment load, if an address is loaded into $02E2, DOS begins execution at that address. So you see that starting with byte 51 of the segment is this data:

E2 02 E3 02 00 26

Which can be translated as, "start loading at $02E2, end loading at $02E3, data to load: $00, $26"

By the way, you did know that 16 bit addresses are stored backwards (low byte first), right? This load segment stores address $2600 at address $2E2. That's the very start of the program where all that bad copy protection stuff takes place. Execution after the copy protection check succeeds continues at $2626, therefore we just want to change the header of the 2nd segment to look like this:

E2 02 E3 02 26 26

There, mission accomplished. Now the copy protection routine is still part of the program, but it no longer gets executed.

Assembling with PC Xformer Assembling with PC Xformer Top of Page