You are on page 1of 112

MACHINE LANGUAGE DATA STRUCTURES

MACHINE LANGUAGE DATA STRUCTURES


Programmers' Workshop Conference
April 5, 1990
by John Leeson

The design and use of code or program structures for data used in
programming is one of the most important considerations in early
software
development. This is particularly true for programs which process or use
large amounts of data, but can be important in nearly any programming
job.
Modern programming methods concentrate more emphasis on data structures,
and modern programming languages provide more code mechanisms for their
design, definition, processing and protection. Machine language,
however,
being the lowest level of program code, is far removed from the high
level
languages when it comes to data definition. This does not make the ML
programmers job any easier, and it is even more important to understand
the types of data structures that might be used and how to implement
them
in the lowest level form. This paper outlines several of the most common
data structures used and suggests how they can be implemented in 65xx
assembly code.

Data structure TYPES that this paper discusses are ARRAY, LIST, QUEUE,
and
RECORD. Stacks are generally similar to the queue or list structure
except
for how they are accessed. Sets, maps and graphs are beyond the scope of
this paper. Trees will be briefly discussed and related to list
structures
for the most common case of binary trees.

Array - The array can be single or multiple dimensions and is


characterized by the ability of it's associated processing to randomly
select, replace or swap the contents of the elements of the array. Each
element of the array is like every other element of the array and a
sub-division of an array has the same characteristics as it's parent
has,
except for size. Arrays are nearly always fixed in size rather than
dynamic sizes. The array may be at a location fixed at assembly time or
may be dynamically allocated memory at run time. The array, once it is
set
up, always exists. There is no such thing as an empty element. Each
element can contain data of the type or size defined for the element. If
an array element has not been assigned data by the program then the
contents of that element are undefined. But the element still exists.

There are various ways to set up an array data structure in assembly


coding. The method chosen depends on whether a generalized array
structure
is desired or you want an optimized setup for the particular array
characteristics. The general case can be setup similar to the most
complex
case so it will be shown first, followed by simplifications for special
cases that can be optimized. Array structures are important because most
other structures will be set up using the principles shown for arrays.

In the most general case, the array will contain multi-byte values, have
a
large order (number of elements), and be in dynamically allocated
memory.
That is, the array will not be at a location determined at assembly
time.
For this structure, each element will be set up to occupy consecutive
memory locations and have the index values increasing in memory starting
at a pointer value which addresses the start of the array in memory. The
zeroth element of the array using n bytes per element will thus start at
the address contained in a pointer "array'ptr" and occupy n consecutive
bytes. Element number 1 of this array will start at the address in
"array'ptr" + n bytes and occupy n consecutive bytes from there. The
total
size of the array will be n * k bytes where k is the maximum dimension
of
the array. Here is the assembly language code to set up an array like
this
with maximum size equal to the assembler constant max'size and number of
bytes equal to the assembler constant element'size.

zp'array'ptr = $20 ;zero page pointer (2 bytes) where a working


;pointer can be calculated and used
max'size = 300 ;assembler constant, max number of elements
element'size = 7 ;assembler constant, number of bytes per element
array'ptr .word 0 ;location where the address of the array will
;be placed at run time
;
;get'element : call with .X = element index low byte, .Y = element
; index high byte.
; returns with zp'array'ptr set to address of element
; in the array.
;
get'element = *
stx zp'array'ptr ;save the index for calculation
sty zp'array'ptr+1
lda #element'size ;get the size of each element
jsr multiply ;multiply the index in zp'array'ptr by the size
lda array'ptr ;and add the base address of the array
clc
adc zp'array'ptr
sta zp'array'ptr
lda array'ptr+1
adc zp'array'ptr+1
sta zp'array'ptr+1
rts
;
multiply = *
; this routine multiplies the value in the pointer at location
; zp'array'ptr by the contents of the accumulator and returns
; with the result left at zp'array'ptr.
sta mpy'multiplier ;set up work locations
lda #0
sta mpy'result
sta mpy'result+1
ldx #8 ;count of bits to multiply
- ror mpy'multiplier ;get bit of multiplier in carry
bcc + ;don't add if bit is %0
clc ;else add current value to result
lda zp'array'ptr
adc mpy'result
sta mpy'result
lda zp'array'ptr+1
adc mpy'result+1
sta mpy'result+1
+ dex
beq + ;finished if 8 bits counted
asl zp'array'ptr ;else multiply value * 2
rol zp'array'ptr+1
jmp - ;then go back and do next bit
+ lda mpy'result ;done. move result to zp'array'ptr
sta zp'array'ptr
lda mpy'result+1
sta zp'array'ptr+1
rts
mpy'multiplier .byte 0 ;temporary location to hold multiplier
mpy'result .word 0 ;working space to calculate result

Notice that it is necessary to have a general purpose multiply routine


that is used to multiply the element size by the array index. I have
included the general purpose multiply routine here for completeness.
Even
then, this is not a fully general purpose 16 bit multiply. It is a
multiply of a 16 bit value by an 8 bit value and is optimized to do
that.
In general you don't want to do extensively more than is required in the
assembly code. You could have set up for and used the BASIC interpreter
floating point multiply here instead, but that would take a couple
milliseconds to calculate where this special purpose integer multiply
gets
the job done in about 300 microseconds. For the best routine here you
should have also checked to make sure that the index value did not
exceed
max'size and returned with an error flag if it happened.

There are many smaller cases of arrays that allow simplifications. If


the
maximum index value is 255 or less then one byte is sufficient to hold
the
index instead of 2 bytes being required. If the product of the number of
elements and the element size in bytes is greater than 256 you will
still
need to do the 2 byte pointer and use a zero page location for it. The
use
of the zero page pointer allows you to use indirect-indexed addressing
mode to get the contents of the array element addressed. For example,
after executing the code above to set the element pointer then you could
read the 7 values from the array with...
ldy #0
lda (zp'array'ptr),y ;get the first byte

iny
lda (zp'array'ptr),y ;get the next byte

The operation becomes a bit simpler if the array is fixed in memory at


assembly time. That allows you to do some of the math with assembler
arithmetic instead of having to do the arithmetic at run time. For
example, to access element number 2 of the array at fixed memory address
"array" you could do the following:

ldx #0
lda array+7*2,x ;get the first byte

inx
lda array+7*2,x ;get the next byte

The array would be declared at a location in absolute memory with an


assembler label assignment. Or, if you wanted to imbed the array in your
code to make it be part of the loaded executable program then...

array = *
.buf max'size*element'size

The operation becomes trivial when the product of the element size and
max'size is less than 255. In that case you can simply use absolute
indexed addressing mode if the array is at a location fixed at assembly
time or use indirect indexed addressing mode if it is not. Simply
multiply
the index by the element size and put it in the .Y register, then do a
lda
array,y or, for a dynamic location array, lda (zp'array'ptr),y. You can
take advantage of this simplification for multi-byte elements if the max
size of the array is less than 255. To do this, instead of locating each
byte of an element in consecutive memory locations you can put the first
byte of each element together in consecutive memory locations, then put
the second byte in another set of consecutive locations, etc. For
example,
to declare the array with 2 bytes per element and the max index of 199
you
could do thus:

array0 = *
.buf 200
array1 = *
.buf 200

or the alternative for fixed memory outside of your code area...

array0 = $c000
array1 = $c000+200
Now to access element n of the array you can use a subroutine like
this...

;get'element : enter with .A containing the index value to access.


; return with .X containing the first byte, .A containing
; the next byte of the contents of the array element
get'element = *
tay ;index to element
lda array0,y ;get the first byte of the contents
tax ;put it in the return parameter location
lda array1,y ;get the next byte of the contents
rts ;done

The simplest form of all for an array using ML is the conversion table.
This is often used to convert 8 bit data bytes from one coding system to
another. For example, to convert ASCII to CBM ASCII. In this structure
the
table is an array that is normally assembled into your program. The
table
is 256 bytes long and the contents of each element of the array are the
CBM ASCII code for the equivalent ASCII character code used as an index
to
the array. A conversion routine to convert the contents of the
accumulator
as an ASCII coded character to a CBM ASCII coded character looks like
this:

ascii2pet = *
tax
lda ascii2pet'table,x
rts
ascii2pet'table = *
.byte 0, 0, 0, 0, 0, 0, 7, 157, 0, 0, 0, 12, 13, 0, 0, 0
.byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
.byte 32, 33, ...
.byte 48, 49, ...
.byte 64, 97, 98, ...

Notice that the ASCII code 8 is the backspace character, which gets
translated to the CBM ASCII code for cursor left. ASCII characters which
have no CBM ASCII equivalent are translated to a byte value of 0, which
can be printed with no effect. Also notice that ASCII code 65 is an
upper
case 'A' which is translated to character code 197, the CBM ASCII code
for
upper case A. If I had put in the complete table you would find that
ASCII
code 97 accesses the table with an index value of 97 and gets 65, the
CBM
ASCII code for a lower case 'a'. Also, using an index value of 127 for
the
ASCII DELETE character would get CBM ASCII code 20, the delete
character.
Values greater than 127 would have identical table entries to the values
for character codes 0 through 127 since standard ASCII does not define
characters greater than code 127. Thus the table can be shortened to 128
bytes by adding 'AND #$7F' before the 'TAX' in the above routine. That
trades the execution time of the one instruction for 128 bytes of
memory.
If you wanted the routine to run as fast as possible you might not do
that, however.

List - The list is a collection of similar items in any order. It can be


likened to a single dimensioned array in most respects. However, the
processing performed on a list structure is generally sequential rather
than random. The abstract 'list' is defined specifically as a sequential
structure rather than a random access structure. You can add or remove
things only at the bottom of the list. To remove an item from inside a
list you split the list into two lists at the place the item is to be
removed, then remove the item from the bottom, then rejoin the two
lists.
Lists are often dynamic size or memory location and have a pointer
linkage
from one item to another on the list. Unlike an array, it is possible
for
a list to not have an element. A list that is only 9 items long does not
have any 10th item, thus it is necessary for lists to define an empty
item. Usually this is called a NULL item and is the result of the last
item on the list having a NULL pointer.

Two forms of list are used, depending on what kind of access is desired
to
the list elements. The simplest form is a singly linked list where each
piece of data has a LINK to the next piece of data. This list can only
be
read from 'top' to 'bottom' since that is the way the links go. The top
item on the list points to the second item, which points to the third
item, etc. The last item on the list points to a NULL item using a NULL
pointer. An empty list has a NULL pointer to the list itself. The more
general form of a list is doubly linked. There is a pointer from each
element to the next item lower on the list and to the next item higher
on
the list. In this way you can traverse a list either 'top' to 'bottom'
or
vica-versa. The use of a linked list allows the items in the list to be
ordered differently than the order the list was created in without
moving
the data in memory. Thus this structure is ideal for sorting items or
for
adding and removing items. Only the pointers get changed as items move
around.

In the 6502 family of processors a LINK is a 2 byte absolute address, in


general. For special cases of small lists it could be a one byte value
that is added to the base address of the list in memory to get an
absolute
address. For a list of multi-byte values and a maximum size of less than
256 items, the link could be a single byte value that is multiplied by
the
size of the item in bytes and added to the base address of the list. Set
it up with the link bytes first followed by data bytes in consecutive
memory locations. Like with arrays, it is so convenient to have a power
of
2 for the size of an item that you often may want to set the size of an
item in bytes to be so, even if you don't need all the bytes. For
example,
if you need 12 bytes for a name string plus 2 bytes for pointers then
you
may set it up to use 16 bytes per item and leave the extra 2 bytes
unused.
Then a multiply of a pointer by the item size can be done by shifting
the
pointer value left 4 times instead of a general purpose multiply.

Working with a list is best accomplished in general by using a doubly


linked list. You will need 3 pointer variables to maintain a list in
memory. The first is a pointer to the memory address of the start of the
list. If the list is fixed in memory at assembly time then this can be
an
assembler label rather than a pointer storage place. The second is a
pointer to the HEAD of the list. This pointer addresses the first item
on
the list. Finally you need a pointer variable for the next available
memory address where data can be added. An optional fourth is a pointer
to
the TAIL of the list. If an item is to be added to the tail of the list
then this pointer gives direct access to it. You CAN get by without this
pointer variable if the list has to be searched before adding an item to
the list anyway. You know when you have reached the end of the list when
you find an item with a NULL link and this is by definition the TAIL of
the list.

This gives a structure of the list in memory as shown below:

back link <--- (ptr to start of list memory)


forward link

...
back link (null) <--- (ptr to head of list)
forward link

back link
forward link

...
back link <--- (ptr to tail of list)
forward link (null)

...
back link
forward link

start of available memory <--- (ptr to free memory)

The process of adding an item to a list is to put the data at the


location
pointed to by the next available memory address pointer. Then add the
item's size in bytes (including the pointers) to the next memory address
pointer. Set the new data item's forward pointer to NULL (usually a 0
value is used for NULL). Then set the back link of the new item to the
pointer value for the tail of the list, set the previously null pointer
in
the item that was at the tail of the list to the pointer value for the
item just put in memory, and update the pointer to the tail of the list
to
set it to the pointer value for the item just added.

To remove an item from the list you take the data of the last item in
memory and overwrite the data of the item being removed. Then subtract
the
size of the item from the next memory address pointer. Copy the backward
link of the deleted item to the backward link of the item that the
deleted
item pointed forwards to. Copy the forward link of the deleted item to
the
forward link of the item that the deleted item pointed backwards to.
Change the link of the items pointing to the moved item (one forward and
one backward) to point to it's new location. Finally, copy the old
forward
and backward pointers from the moved item's old location to its new
location. These operations require two temporary pointer variable
locations (usually zero page) plus one temporary storage location which
holds the pointer to the deleted item (and the new location of the item
moved during a delete).

To swap two items you simply swap the two item's pointers. A temporary
pointer variable space is needed but can usually be the stack, while you
use the accumulator for a pointer byte and the .Y index register to
access
the pointers using indirect indexed addressing. The code might look
like...

;swap with 2 byte pointers for doubly linked list


swap ldy #0
- lda (from'ptr),y : pha
lda (to'ptr),y : sta (from'ptr),y
pla : sta (to'ptr),y
iny : cpy #4 : bne -
rts

A binary TREE is similar to a LIST except there are TWO pointers to the
next data item. One points to the item referred to as the LEFT CHILD,
another to the item referred to as the RIGHT CHILD. If it is a doubly
linked tree then there is also a pointer backwards to the PARENT of the
item. A NODE which has no children is called a LEAF in the tree. Binary
trees are excellent for setting up and accessing large amounts of data
sorted in some specified order. It is common practice to have the left
child contain data that is less than the node value and the right child
contain data that is greater than the node value. Each node on the tree
contains both data and pointers and looks pretty much like an item in
the
LIST except that there are two forward pointers. A simpler form of tree
has only pointer nodes in the tree structure itself. The pointers point
to
either another pointer node or to a leaf node. Leaf nodes contain only
data. Pointer nodes in this tree structure sometimes contain some
'quality' of the data that is below them to be used for searching the
tree. For example, in a decision tree the pointer nodes contain a
question
which can be answered true or false (or yes or no). A false answer for
the
question might then mean to take the left child of that node and a true
answer to take the right child of that node. If the resulting child is a
leaf then it contains the data being searched for. If the resulting
child
is another pointer node (has another question) then the process is
repeated.

The storage of the tree in memory should, in most cases, be the same as
for a list except for including space for the two forward pointers. The
pointer to the HEAD of the list will instead be a pointer to the TOP of
the tree. You will need no pointer to the TAIL since there is no such
thing as the tail of a tree. You may need to have a flag byte reserved
in
the item storage space to determine if a node is a leaf node with data
in
it or a decision node.

The QUEUE is commonly used as a BUFFER in programming. A queue can be in


many forms: First-in/First-out (FIFO - which is the common form for a
buffer); Last-in/First-out (LIFO - which is nearly the same as a STACK);
Or other forms with priority, balking or random access. Whole books are
written on queues and queuing theory, so I obviously cannot cover much
on
the subject here. The FIFO form, however, is so common as a buffer that
it
really needs to be covered.

Depending on the data that is queued, the queue could take the internal
data structure of a linked list or a simple list. Two variables are
needed
to control the queue - the FRONT of the queue and either the END or the
LENGTH of the queue. In order to get to some examples and useful
processing I am only going to show forms which have single byte
elements.
This is likely to be just what you need anyway, and the ideas are easily
extended to larger data items.

Three forms of the queue are common for byte buffers; the static linear
buffer, dynamic linear buffer and circular buffer or queue. An example
of
the first is a disk buffer. It is first filled with bytes by an input
routine, then emptied by an output routine. For this structure, a flag
is
needed to allow the input routine to tell the output routine when there
is
no more data to be put into the queue (the EOF flag). The control flow
for
a disk buffer is generally that the input routine reads data into the
buffer until it is full or the end of file is reached. It then calls the
output routine, which removes data from the buffer until it is empty.
Then
the output routine checks the input EOF flag and if not set, calls the
input routine. If it is set, then the output routine returns to the
higher
level control program. Buffer empty occurs if the length of the queue is
zero or if the end of queue variable is equal to the start of queue
variable. Buffer full occurs if the length is equal to the maximum
length
or the end of queue variable is one greater than the maximum address
allowed for the queue storage space. The end of queue variable always
points to the next position where data can be written. If a queue length
variable is used then the next position for write is equal to the start
of
queue variable plus the length.
A dynamic linear buffer is used where the input routine and output
routine
are independent from each other. Data is put into the queue
asynchronously
with it's removal from the queue. An example of this type of queue is
the
keyboard buffer. Characters are put into the queue by the keyboard scan
routine executing during IRQ interrupts. Characters are removed from the
queue by an application program with calls to the Kernal GETIN routine
with the default input device active. It is usually only used where the
length of the queue is quite short. The keyboard buffer is only 10
characters long. It should never be used where the length of the queue
exceeds 255, so that indexed addressing can be used to access it. The
length of the queue then becomes the index register value used to write
to
the queue. The data is removed from the queue if the length is non-zero.
The next byte is removed from the start of queue position and all
remaining bytes are moved down by one in memory until queue'length-1
bytes
have been moved and the length is then decremented by one. If the buffer
is assembled into your code then the move is simplified. If the buffer
is
at a location determined at run time then indirect indexed addressing
must
be used. A comparison of the two move routines follows.

move'queue = * ;fixed location move'queue = * ;dynamic location


ldy #1 ldy #1
- cpy length - cpy length
bcs done bcs done
lda qstart,y lda (qstart'ptr),y
sta qstart-1,y dey
iny sta (qstart'ptr),y
bne - ;unconditional iny
done dec length iny
rts bne -
done dec length
rts

"qstart" is an assembled-in absolute location for the queue.


"qstart'ptr"
is a zero page pointer variable that is set to the start of the queue at
run time.
The third form of the buffer queue is also asynchronous. An example of
the
circular queue is the RS-232 buffer. Bytes are placed into the receive
queue by interrupt service routines for RS-232 input. They are removed
from the queue by calls to the Kernal GETIN routine with the RS-232
channel active as input. Bytes are placed into the output queue by calls
to the Kernal CHROUT routine with RS-232 channel active. They are
removed
from the queue by the RS-232 transmit routine which is interrupt
activated
as long as there is data in the queue. The output start is initiated by
writing a byte to the transmit queue when it is empty. The input start
is
initiated during setup of the channel. For the circular queue there are
3
variables needed. The start of queue, the Next Read Position (NRP) and
the
Next Write Position (NWP). The buffer is empty when the NRP and NWP are
equal. The buffer is full if the NWP is one less than the NRP. When the
variable NWP or NRP is incremented after writing a byte or reading a
byte
and it then exceeds the end address for the queue then it is reset to
the
start address for the queue. A queue length of 256 bytes makes the
circular queue work extremely well for the 65xx. It is only necessary to
increment the index and ignore overflow to make it be circular. The code
follows for a circular queue of 256 bytes.

qwrite = * ;enter with byte to write in .A


ldy nwp
iny ;wraps around from 255 to 0, overflow ignored
cpy nrp
beq queue'full
sta (qstart'ptr),y
sty nwp ;nwp updated AFTER the byte is stored, NEVER before.
rts
queue'full

qread = * ;return a byte in .A or return with carry set if empty


ldy nrp
cpy nwp
beq queue'empty ;equal condition also has carry set
lda (qstart'ptr),y
iny ;wraps around from 255 to 0, overflow ignored
sty nrp ;nrp updated AFTER the byte is read, NEVER before.
clc
queue'empty rts

For queue lengths less than 256 you can do a CPY MAX'LENGTH after the
INY
in these routines and reset it to 0 if .Y is greater than the desired
length. For queue lengths of greater than 256 then the NRP and NWP
become
zero page pointers which are incremented until they exceed the maximum
size then reset to the start of the buffer. Then the loads and stores
use
(NWP),Y with .Y set to zero. The compares for NWP = NRP need to be 16
bit
compares instead of simple register compares.

A RECORD contains several pieces of information which are not similar


but
all pertain to a single object. A person, for example, has a name,
address, phone number, and possibly other characteristics such as
bowling
average or amount that he owes you. A item of stock in a store might
have
an inventory number, manufacturers name, amount you pay for it, amount
you
sell it for, and how many you have in stock. The collection of
information
about the object is the RECORD. Each separate piece of information is
called a FIELD. A number of records can be put together into a DATA BASE
which might be structured like an array, a tree or a list. Each field in
the record has a name which can be used to access it. In high level
languages a specific field is completely determined by concatenating the
field name to the record identifier. For example, you might refer to
your
zip code as ME.ZIP where ME is the identifier of the record containing
info on yourself, and ZIP is the name for the field containing your zip
code.

For working with records in assembly language you generally will want to
keep the information in fixed length fields. This is the Pascal style
record structure. With fixed length fields, each field has a
predetermined
number of memory bytes reserved for it. The data in the field either
exactly fills the field or has a predetermined terminator byte marking
its
end. To access a specific field in a record you can then use the address
for the record and an index value for the offset to the field from the
start of the record. I prefer to define assembler constants for the
offsets using labels which are appropriate for field names. A structure
for storing a disk directory in memory as an array of records then might
look like this in assembly code:

directory = *
fileflag = *-directory
.byte 0 ;flag for file selected or not: multipurpose
blocks = *-directory
.byte 0,0 ;file size, low byte/high byte disk blocks
filename = *-directory
.asc "16 char filename" ;file name padded with bytes of zero
splat = *-directory
.asc "*" ;"*" if splat, else space
filetype = *-directory
.asc "p" ; p, s, u, r, c, d (1st letter of type)
lock = *-directory
.asc ">" ;">" if file locked, else space
spare = *-directory
.byte 0,0 ;unused
.buf 4680 ;space for 195 more. Total 196 for 1581 directory.
This structure is flexibly defined in assembler code such that it would
be
easy to change. If you wanted to add a second flag byte after 'fileflag'
you could take one away from the spare bytes and just stick in a new
label
for the field as the others were done at the place where you want the
byte
to appear. All the rest of the offset labels will be adjusted
accordingly.
Also, the total count of bytes in the record have been set up for 24.
This
is a fairly easy number to work with in assembly language math for
indexing. Here is an example program fragment to "process" each marked
directory entry:

do'em = *
inc an'entry ;present entry number
lda an'entry
cmp numb'files ;all done?
bcs exit
jsr get'new'fileflag ;get its flag
beq + ;no processing needed
jsr process ;whatever that is
+ jmp do'em ;note - structured block
exit = * ;continue

get'new'fileflag = * ;get directory(.A).fileflag for new entry


jsr set'dir'ptr ;set zp'dir'ptr to point to directory(.A)
get'fileflag = * ;get fileflag of present entry
ldy #fileflag ;offset of fileflag field in directory record
lda (zp'dir'ptr),y ;here it is
rts ;destroys .Y, .X is preserved, result in .A
;
set'dir'ptr = * ;set pointer to directory(.A)
ldy #0
sty zp'dir'ptr+1 ;directory pointer variable, high byte
asl : rol zp'dir'ptr+1 ;*2
asl : rol zp'dir'ptr+1 ;*4
asl : rol zp'dir'ptr+1 ;*8
sta zp'dir'ptr : ldx zp'dir'ptr+1 ;*8 in .A and .Y
asl : rol zp'dir'ptr+1 ;*16 in .A and memory
adc zp'dir'ptr : sta zp'dir'ptr ;*24 low byte (carry always clear)
tya : adc zp'dir'ptr+1 : sta zp'dir'ptr+1 ;*24 high byte
clc
lda zp'dir'ptr : adc #directory : sta zp'dir'ptr+1
rts ;Destroys .A and .Y, .X is preserved.

Data structures have been presented to help familiarize you with how to
set up and use ARRAY, LIST QUEUE, and RECORD type structures in 65xx
assembly language. Also, a small amount of information on TREE
structures
was included. These structures SHOULD cover nearly all the ones you will
need in programming as a home hobbiest. There are other exotic
structures,
maps, graphs and sets, which are beyond the scope of what I can cover in
just a short paper such as this. I also did not cover the STACK, but
these
structures are very similar to an array or a list structure, depending
on
whether it is open-ended or fixed. The main characteristics
differentiat-
ing them are the processing that is permitted and the way the structures
are accessed. I believe that sufficient information is included in what
IS
here so that the extension to these structures is relatively easy.

This set of data structures is sufficient to provide program


implementation of the data that is needed for any application. The
methods
to do so should be fairly obvious to a person that understands the
structures presented in this paper. It may require use of an array of
records as in the last example worked or some other combination of data
structures. I hope that this paper helps you in doing so.

PART TWO
Debugging Machine Language Programs
DEBUGGING MACHINE LANGUAGE PROGRAMS
by John L 2/11/88

This paper is presented in two parts. Part 1 covers basic ideas and
procedures used in debugging: Code reading; using a monitor program;
breakpoints; testing; and patching. Part 2 deals with special problems:
Debugging interrupt routines; emulation; symbolic debuggers; and debug
code.

PART 1. Programmers' Workshop Conference, Feb 11, 1988, SYSOP JL

Consider first what you will need. A monitor program. The C128 has one
built in or you may have a cartridge with one built in. Otherwise load
up a monitor program and initialize it before loading your M/L program
to work on. A listing of the program you are working on. If you don't
have a printer then have your handwritten code handy, or at least write
down starting addresses of routines and locations used for variables
and data. If you don't have an assembler listing (preferred) then
disassemble the code to a printer.

For working on M/L you will want a reset button on your computer. If
you don't have one then build one, use a cartridge with one or purchase
a plug in reset button. M/L programs often lock up the keyboard when
they crash. Re-loading programs, remaking patches and re-starting tests
is time consuming. Also if you have to power down to recover you have
lost important information you will need to find the problem.

Pencil and paper, a calculator, reference books, patience and a logical


mind are also helpful. Practice at having a part of your mind doing
*only* what the program tells you to do. In doing this don't make
assumptions - interpret instructions literally. For example, the code
sequence CMP #3 : BMI LOOP does NOT mean "if the accumulator is less
than 3 then loop". What it DOES mean is "set carry, subtract with carry
3 from the accumulator and branch if bit 7 of the result is set". So in
tracking the problem down if you used this code sequence you would find
that if the accumulator contains 135 ($87) then the branch would be
taken in addition to cases like accumulator equal to 2. (For what is
intended here, BCC LOOP would be correct instead of BMI LOOP)

Where to start... First find out what you see the program do when it
runs. You will then want to start following your code from its entry
point until you get to a point just past where it does what you see it
do correctly. Paying close attention to what the program does before it
crashes or does something wrong can give you clues to where to start
looking. Then you can start using one or more of the following methods
to isolate the problem.

Code reading -- Look over the code in the area where you suspect a
problem to be. Use the "dumb computer" thought process I mentioned
earlier. Most times if you KNOW a program section does something wrong
you can see the problem in your code. Besides possibly finding the
problem without a great deal of effort this exercise will thouroughly
familiarize you with the program logic in the area where there is a
problem.

Check variables/data areas -- Use a monitor program to check memory


locations used by your program. Don't forget to look at locations used
for temporary storage of registers or data. You may get a clue as to
what values it was using when it messed up. Check memory locations
used for inputs. You may get a clue but even if you don't, write the
value down. You may want to later try testing part of the routine with
special values you find. If an input is outside of the range which your
program expects then look back to where the input is passed to it. One
common bug found in programs is to expect a memory location to contain
an input value or temporary storage and find out that some other
routine or a system routine overwrites your saved value with something
different. More on this subject later involving interrupt routines.

Breakpoints -- If you still don't find anything then you will have to
go to active bug swatting. Some monitors have a feature that allows you
to set a BREAKPOINT. This is a handy feature of a monitor but it is not
necessary, only convenient. Make sure you pick a point in your program
with an executable instruction to set a breakpoint at. Make a note of
the location and put a zero byte (BRK instruction) at that location.
Put several in your program at first so that you can get a better idea
of where it is messing up. The best place to put a breakpoint is at a
branch instruction. Branch instructions are always 2 bytes long and you
don't need to keep the second byte if you use a BRK at that location.
Then when the program halts you can look at the registers and the
processor status for expected values, then continue the program either
at the instruction following the branch or at the instruction to which
the branch would go to if the branch condition is satisfied. All branch
instructions use the processor status register bits to determine if a
branch is to be taken or not. I keep the following written on the front
of my monitor shelf in large black letters... NV-BDIZC. This is the
meaning of the processor status register bits that are tested when a
branch instruction is executed. They have the following meaning when a
branch decision is made:

N (Negative) = '1' BMI will branch


= '0' BPL will branch
V (Overflow) = '1' BVS will branch
= '0' BVC will branch
Z (Zero) = '1' BEQ will branch
= '0' BNE will branch
C (Carry) = '1' BCS will branch
= '0' BCC will branch
D (Decimal mode) '1' decimal mode set
B (Break) '1' cause of an interrupt was BRK
I (Interrupt) '1' Interrupts are disabled

After setting the breakpoints then start the program in the normal way.
When one of the BRK instructions is executed the monitor will display
the instruction address counter and registers. Look at the address
displayed to see which of your breakpoints was executed. The C128
monitor displayes the location of the BRK plus 2 so remember to look 2
bytes earlier than the address shown. Now you can look at the registers
and memory locations to figure out what is going on at that point in
the program. Then continue the program with a G XXXX with the address
of the next instruction or the target instruction of the branch. To
reset a breakpoint use the monitor to change the byte you set to zero
back to the byte that is in the listing.

Patching -- There are two reasons for patching. One is to make


temporary changes correcting an error in your program. I say temporary
because you will want to go back and correct your source code and re-
assemble the program to make the change permanent. If you leave the
source alone and just make the patch then later when you add another
feature or change your source code for another reason you will still
have the bug in it. The second reason for patching is to insert
temporary code into the program to help you find a bug. The principles
are the same either way so I will just talk about the temporary
debugging patch here.

Put in a piece of code in some spare memory that does something


visable. I like to set the border color to different values at
different major areas of code. Then you can see the different sections
of code as they are executed. Also, if a program locks up the keyboard
you can see if it is in a loop that flashes the border colors or you
can tell what the last major section of code was that was executed
before the lockup by what color the border is. Some other things that
you can do in a patch are... increment a memory location each time the
patch is executed; save a register value at a spare memory location;
etc... This way you can leave tracks as your program runs. End your
patch code with a duplicate of the code you will replace in the next
step of the process and a RTS instruction.

After setting up the patch code then go into your program that has the
problem and put a JSR to your patch code in place of some instruction.
It is easiest to replace a 3 byte instruction with the JSR. Then you
will only have to duplicate the one instruction in your patch and the
RTS will return to the very next instruction in your listing. If you
replace a one or two byte instruction in your program with the JSR then
make sure you fix up the code following the JSR so that it is
executable (put enough NOP instructions in to pad out the remainder of
any messed up instructions). You should NEVER replace an RTS with the
JSR. It will mess up memory in other routines.
Testing -- Testing is a special variation on the above methods.
Typically what you will do is to set a breakpoint at the end of a
routine (replace an RTS with a BRK) first. Then from the monitor you
set up all expected input values (registers, pointers, absolute memory
locations, etc) to the values you want to test with. Pick values that
represent limit values as well as nominal values. If a register can
contain any value when the routine is entered then test it with 0, $ff,
$80, $1 and some other value or values as appropriate for what the
routine does. Do the same for any other memory locations used by the
routine. After setting all the variables used to what you want to test
with, do a G XXXX from the monitor to the start of the routine. When
the routine completes you will exit to the monitor and can check for
the expected values. You should have an idea ahead of time from the
program design what the expected values are that it will return so that
you can check to see if it worked correctly. Or if not, how the
results differ. If you are using a C128 monitor then you don't need to
replace the RTS with a BRK... use the monitor command J XXXX instead of
G XXXX and it will return to the monitor when the RTS instruction is
executed.

PART 2. Programmers' Workshop Conference, Feb 18, 1988, SYSOP JL

Debug Code -- Using debug code in your source program is similar to


patching methods except that it is preplanned and is easier to do in
source code then to do while debugging a program. There are three ways
that you can incorporate debug code into your source programs. Each is
discussed below.

First you can use conditional assembly if your assembler has options
that will allow it. Some assemblers have compiler directives (pseudo-
ops) that can identify a block of code to be assembled if a condition
is satisfied. For example, MAE-64 has pseudo-ops IFE, IFN, IFP, IFM
and *** for this purpose. A debug code block coded for MAE might look
like this...

debug .de $ff .


other statements.... .
ifn debug
php
pha
lda #2
sta border
pla
plp
***
program continues...

Now when you assemble the program you will have code built in that sets
the screen border color to red when this section of your code is
executed. When you are done debugging the program and are ready to try
it full speed and without the debug options then it is a simple matter
to change the equate "debug .de $ff" to be "debug .de 0" and
reassemble the program. When you do that the debug code will not be
assembled because the condition tested by the ifn is no longer true.

Similarly, Power Assembler from Spinnaker has conditional assembly


using pseudo ops .IF, .ELSE and .IFE. If the label expression on the
.IF statement is not zero then continue assembly, else skip to the
.ELSE or .IFE and continue assembly. For inserting debug code you
would probably not use the .ELSE statement. If your assembler does not
have a conditional assembly option you can still do the same things
except that you will have to do the operation of removing the debug
code manually. Just code the debug code inline with your program and
when you are done with the debugging you can simply comment out the
statements that you no longer want to run. I recommend not deleting
them from your source but just insert a ; as the first character on
the line so that they will be treated as comments by your assembler.
Then if you later have to re-insert the debug code you can just delete
the ;'s from these lines and reassemble. When you code debug code
inline this way, use comment lines to flag and highlight the debug
code so that it is easy to find later by looking at the listing.

A third method is to code the debug code inline except put a JMP just
ahead of the inline debug code that jumps around it. Then to turn on
the debug code while you are actively debugging you can simply NOP out
the JMP instruction and the debug code is activated. Again, in order to
later clean up your program for release you should remove the debug
code by commenting it out or deleting it. So highlight it in your
source code so you can easily find it later.

Debugging Interrupt routines -- Use debug code or patches. During the


interrupt you cannot just break out of a routine to the monitor. You
may get all kinds of problems. Patch code can be set up so that when
you exit through it it redirects the interrupts back to the normal
location and saves informaiton... registers, interrupt flags, important
memory locations, counters, etc. In setting up debug code for interrupt
routines, use counters or save values in spare memory locations. It is
better to allow the interrupt routines to run at near normal speed and
just provide the minimum necessary tracks on where it is with debug
code. This means that you will have to rely more on testing and code
reading to find program bugs, but that is about the best you can do
with interrupt routines. Code testing is probably the best method you
have of finding the problems in interrupt driven routines. This of
course depends on what the code is that you are debugging, but if it
does any amount of data manipulation you should check out the program
logic before trying it with live interrupts. For example, if you are
doing split screen interrupts make sure by testing the routine first
that it calculates the correct raster line to set up the next raster
interrupt.

With regard to interrupt routine problems, perhaps one of the most


common problems to watch out for is the read-modify-write problem. This
problem occurs when you have a memory location that is being read,
modified then re-written by the main part of your program and is also
being read, modified and re-written in an interrupt routine. If you
have a condition like this, then you will either have to disable
interrupts in the main part of the program during the read-modify-write
or you will have to set up a shadow location for the main program to
use. What happens is that if the main program reads the location and
then the interrupt hits, the main program will modify the original
value that it retrieved prior to the interrupt and rewrite it, while
the interrupt routine will have meanwhile changed the value. Another
problem is timing... you have to make sure that the condition which
causes the interrupt does not recur until you are completely done with
handling the first occurance of it and have returned to the main
program. You may either miss interrupts or you may just continually
operate in your interrupt routine and never get anything else done. On
the 64 and 128 the timer or VIC chip interrupt registers must be read
following an interrupt to reset them. If you don't reset the interrupt
your interrupt handler will just run continuously. Every time it re-
enables interrupts the next instruction will be interrupted,
immediately restarting your routine. This means that a code sequence of
CLI then RTI will result in the RTI being executed, but then
immediately jumping back into the interrupt routine. Also, watch out to
make sure you always restore the correct number of parameters off of
the stack and in the correct order. Again, this is something you can
check with testing by verifying that the SP register value is the
same at the end of a routine as it was at the start.

Emulation -- Use a machine language emulator to follow the program


along. Some monitors have a step-by-step execution mode or a trace
mode. This is very useful for following a program. Use it for assisting
your code reading step in debugging. If your monitor does not have this
feature then you can use a program like "6510 sim. rev", available in
the Programmers' Workshop, Assemblers and Monitors library, to follow
it along. A trace mode does a trace disassembly of the source code. Each
time you step the trace it disassembles the next instruction. When you
encounter a branch instruction you can select to follow the branch or
you can select to continue with the next instruction. When you
encounter a JSR you can select to continue with the next instruction
that will be executed after returning or you can follow the subroutine.
The 6510 sim. rev program is similar to a step-by-step execution with a
monitor but it does it by EMULATING the instructions rather than
executing them. It has its own simulated registers that it keeps track
of rather than using the actual 6510 registers.

Symbolic Debuggers -- Symbolic debuggers are the ultimate in


convenience and speed for debugging machine language programs. Two
examples are...

geoProgrammer -- $69.95 from Berkely Softworks


(415) 644-0980
PTD-6510 Symbolic Debugger -- $49.95 from Schnedler Systems
(704) 274-4646

Symbolic Debuggers are similar to the best monitors but have more
features. They also read in the symbol table from your assembler
source so that you can reference memory locations and code entry points
by your source code labels. Breakpoints, reverse disassembly, break
after N times, memory display in byte, word or ascii, and many other
useful features make the symbolic debugger a powerful tool if you are
going to be doing a lot of ML programming.

PART THREE
ML I/O ROUTINES
ML I/O ROUTINES
November 9, 1989
Programmers' Workshop Conference

BASIC programmers are spoiled with their OPEN, CLOSE, PRINT, INPUT and
GET instructions. In order to perform these functions, the micro-
processor must execute hundreds or even thousands of instructions. So,
in this conference paper I'm going to give some routines that are
hundreds of instructions long to help you get started doing I/O in
machine language. Okay? Well... not exactly. You see, Commodore machine
language programmers are just a bit pampered too. The C64 has built in
8K bytes of ROM (read only memory) routines dedicated to performing,
primarily, input and output from/to other portions of your computer
system. The C128, in native mode, has 12K bytes of ROM routines for
this purpose. The I/O routines in this "Kernal" ROM have been made
general purpose and simple to use so that ML programmers can quickly
and conveniently code up programs that need I/O. It takes only 10
instructions in machine language to set up for and perform an OPEN. Two
instructions for CLOSE, one for GET (three if performing a GET#
equivalent read from a device), 8 instructions to perform INPUT (10 to
do the equivalent of INPUT#) and only five to print a literal string
(7 for print# to a device or file).

The Kernal -- incidentally, Commodore official terminology for the


kernel of routines for I/O calls it the "Kernal". The misspelling isn't
mine. I don't know if it was intentional or a typical programmers'
misspelling that caused the Kernal to be "the Kernal" instead of "the
kernel". Anyway -- the Kernal is constructed with a set of high level
entry pointers in a jump table, all with documented, standardized
interfaces. The 'jump table' is standard for all Commodore 8 bit
computers - PET, VIC-20, C64, C128 - so this improves portability,
promotes use of a library of routines, and simplifies the task of
writing assembly language code. By using a jump table for the Kernal
routines, it is possible to maintain constant call addresses for the I/O
routines even though there may be changes to the routines' location
within the ROM. The familiar JSR $FFD2 performs a JSR to the instruction
at that address. This is the jump table entry point to the routine to
print a character to the current output device. At location $FFD2 there
is a JMP instruction that transfers control to the actual routine that
does the work. That routine is located at different places in a C64,
C128, VIC-20 or PET computer, but the JSR to $FFD2 will always take care
of the differences.

Just as call addresses are standard, so are the interfaces to these


routines. Obviously there are different requirements for parameters for
different functions, but for a given function the Kernal routine setup
is the same among different computers, different ROM versions or
different uses of the function. Any variations required for a particular
machine is handled by the actual function coding and the programmer is
relieved of that burden. There is a partial table at the end of this
paper giving call addresses and interfaces to a few of the common Kernal
routines for illustration and to get you started. For a complete table,
refer to a Programmers' Reference Guide. There are good ones for each of
the Commodore computers published by Commodore or Compute! Publications.
There are a few things that are generally common among all of the Kernal
routines, however. All parameters passed to a routine are either
implicit or contained in the processor registers. Where the actual
parameters wont fit in the three registers then a pointer to the
parameters is passed in the registers. Where a function returns a value
it is always returned in the processor registers. And if an error can
occur which prevents the function from performing its job, then the
function returns with the carry flag set and, if possible, an error code
in the accumulator. This makes error handling relatively simple using
the Kernal routines.

To be more specific, and use some examples for illustration, I'll show
two common problems in ML programming with some assembly language code.
For simplicity, this is shown in Power Assembler (Buddy) format using
+ and - as temporary labels for forward or backward branching. Labels
used for Kernal functions are those commonly given in literature and
shown in the table at the end of this paper. These would be assigned to
the actual addresses of the routine jump table entries in equate
statements in your assembler code. First, a simple text reader that
opens a disk file, prints the text to the screen, then closes the file.
This routine includes some user control of the output to pause the
display or abort it (see comments in the code).

shflag = $28d ;Address of byte containing flags for shift type


;keys. %001=shift, %010=control, %100=logo key
;Shflag is at location $d3 in the C128.
seqread = *
ldx #fname
lda #namend-fname ;length of file name calculated by assembler
jsr setnam ;Kernal routine
;
lda #8 ;Logical file #8
tax ;device #8
tay ;secondary address 8
jsr setlfs ;Kernal routine
;
jsr open ;Kernal routine
bcs error ;see OPEN for possible error returns
;
; The file is now open on the disk drive. Next read the data from it
; and print
;
ldx #8 ;Logical file #
jsr chkin ;Kernal routine
bcs endread ;see CHKIN for possible error returns
readloop = *
jsr chrin ;Kernal routine. Get character from serial bus
bcs endread ;see CHRIN for error returns
jsr chrout ;Kernal routine. Print the character
jsr readst ;Kernal routine. Get the status byte for last I/O
bne endread ;EOF or disk error causes end
jsr stop ;Kernal routine. Check the STOP key
beq endread ;and quit if it is pressed
- lda shflag ;check shift, control or logo key pressed
bne - ;pause while one of them is held down
beq readloop ;unconditional. Continue while not (EOF or STOP)
endread = *
jsr clrchn ;Kernal routine. Clear the bus
lda #8 ;logical file #
jsr close ;Kernal routine.
error = *
nop ;change to BRK for debugging
rts
fname .byte 'testfile' ;name of file to read
namend = * ;for calculating name length

For a second example, I will show how to set up an OPEN to a modem


channel. This is a commonly asked question in the Programmers' Workshop
among those just getting started with machine language programming. The
confusion here is because of the interpretation of what a "name" is for
a file being opened. The Kernal OPEN routine uses the "name" to contain
the bytes that are used to set up the RS-232 port if the device number
is 2, the RS-232 channel. Here is the example.

RS-232 Open is done by setting the "name" of the file to open to be the
bytes which set the command and control registers. Then use the normal
SETNAM, SETLFS and OPEN Kernal calls. For example, to open the file for
300 baud, full duplex, no handshake, 8 data bits and no parity the
control register byte is a 6 and the command register byte is 0. Thus
there is a two character "name" and SETNAM would be called as follows:

; OPEN RS-232 channel for I/O


;
rsreg .byte 6,0 ;the RS-232 control and command register values
rsopen = * ;jsr rsopen returns with LFN 2 open to RS-232
port
;
lda #2 ;Length = 2 bytes
ldx #rsreg
jsr SETNAM ;Kernal call
;
;The RS-232 device number is 2 and there is no significance to the
;secondary address that is used in the setup, so the call to SETLFS
;would look like:
lda #2 ;Logical file #2
tax ;Device #2
tay ;secondary address don't care
jsr SETLFS ;Kernal call

Then a JSR OPEN would complete the process.

SPECIAL CONSIDERATIONS
----------------------

CHRIN vs GETIN for Reading : Use GETIN to get single characters from the
keyboard with no cursor and for RS-232 input. Use CHRIN for serial bus
devices (disk) and to perform the equivalent of BASIC's INPUT from the
keyboard (with flashing cursor and edit keys active). Never use CHRIN
for RS-232 input. It can lock up your program waiting for a carriage
return from the RS-232 input and if garbage data is coming in, it can
overflow the INPUT buffer (88 characters on the C64, 161 characters on
the C128). Using GETIN on disk I/O just wastes time. For serial bus or
cassette input the Kernal routine GETIN calls CHRIN, so you might just
as well use CHRIN. A JSR to CHRIN or GETIN returns one character for
each call. If you are inputing a string or continuous data you will have
to store the data away as you read it. Neither of these routines
performs any buffering, except that CHRIN buffers a logical screen line
starting at $201 in either the C64 or C128 if the default input device
(keyboard) is being used.

Serial bus I/O : You can only use the serial bus for one action at a
time. So you cannot set up one device for input and another for output.
For example, to copy a SEQ file from disk to printer it is necessary to
read input data, clear the channel, set up the output channel, send the
data, clear the channel then repeat at get data. In ML, with logical
file 2 for input from disk and logical file 4 for the printer :

getprint = * ;DO
ldx #2 ;Disk drive LFN
jsr CHKIN ;Make disk a talker on the bus
jsr CHRIN ;Get a character
pha ;save character for the moment
jsr READST ;Get disk status
tay ;save status temporarily in .Y
jsr CLRCHN ;Turn off talker (disk drive)
ldx #4 ;Printer LFN
jsr CHKOUT ;Make printer listen
pla ;the character we saved
jsr CHROUT ;print it
jsr CLRCHN ;turn off listener (printer)
cpy #0 ;where we put the disk status earlier
beq getprint ;LOOP until ST 0

A routine such as this will tend to be a bit slow because of the serial
bus turn-around on every character. It is more efficient if you BUFFER
the transfer. To do that, repeat a CHRIN loop that stores characters in
a buffer until EOF is reached or the buffer is full. Then perform the
serial bus turnaround and send all the data in the buffer to the printer
with a CHROUT loop until the buffer is empty. Then, if the last buffer
full sent to the printer was not cut off because of an EOF when the data
was read from the disk, go back to the CHRIN loop again until EOF is
found. The buffer is simply an area of memory that you set aside to hold
the data temporarily and thus may be used for several different things
as long as the purpose is temporary storage. A convenient buffer size to
use is 256 bytes because you can use indexed addressing mode to access
the buffer. For special cases you may use a different buffer size,
though. For example, using BURST read on a C128 you may use a buffer
size of 254 bytes instead as is done in the program "burst read.sda"
uploaded by John L.

"Low level" Kernal routines : There are a number of Kernal routines


available to manipulate the serial bus directly. It is best to avoid the
use of the low level routines unless you specifically want to manipulate
the serial bus directly. Using the low level routines can, and usually
does, create compatibility problems between your program and such
commonly used devices as fast load cartridges, JiffyDOS, hard disk
drives, IEEE disk interfaces, and RAMDOS when used with a REU for a
virtual disk (RAMDISK). Sticking to the high level routines discussed in
this paper will provide the best compatibility with devices and software
wedges that many Commodore computer owners like to use. These are often
used routinely by ML programmers thinking that the time savings will be
appreciated by the program users. It is a mistake though, because the
time savings are negligible at ML speeds and the inconvenience of
compatibility problems is death to many otherwise useful programs.

Stopping : In most of these routines I have not mentioned any way to


stop what is happening. It is a simple matter to stick a JSR STOP in
occasionally at places where it is convenient to code it so that a
program user can press the STOP key to halt the program. After a
JSR STOP then a BEQ ABORT can be used to clean up the I/O and go back
to a higher level control program.

Reading Nuls : Another special consideration is reading nuls which are


either a byte with a value of 0 or no byte, depending on where it is
read from. When doing a CHRIN from a disk file you will ALWAYS get a
data byte. So a data byte value of 0 means that there is actually a byte
value 0 at that place in the file. For keyboard input, a GETIN will
return a 0 value if no key is pressed. You can check for a 0 in the
accumulator and not do anything with the byte if it is 0. For RS-232,
however, it is possible to receive no byte in response to a GETIN, or it
is possible to receive a 0 value byte. Both cases have the accumulator
set to 0 on return from the JSR GETIN. So with RS-232 I/O you should
check the RS-232 status register at location 663 ($297) in a C64 or
location 2580 ($a14) in a C128 after a GETIN. If bit 3 (AND #$08) is a
%1 then there was no data returned from the last RS-232 GETIN call. If
this bit is a %0 then the you have valid data. Thus this is a better way
to check for valid data than to check for a zero byte after the GETIN
call.

RS-232 Transmit : A special consideration in sending data to RS-232 is


that the Kernal RS-232 send routines buffer up to 256 bytes. In ML you
can easily fill the transmit buffer faster than it can be sent. You
cannot overflow the buffer, but you can run into problems due to the
time delay. For example, in Punter protocol you send a block of data 254
bytes long then wait for the receiving end to acknowledge receiving it.
If you fill the RS-232 transmit buffer then start a timeout for the
receiving end to acknowledge it you can get into trouble. At 300 baud it
takes about 8 seconds to send the full buffer while at 1200 baud it
takes only about 2 seconds. So a time delay that is right for one wont
be correct for the other. To avoid this, check bit 0 of the RS-232
interrupt flag register, location 673 ($2a1) in a C64, 2575 ($a0f) in a
C128. If it is %1 then data is being sent yet. When the RS-232 transmit
buffer is empty this bit will change to a %0. So you could avoid the
time delay problem by starting your timeout delay when bit 0 of the
interrupt flag register is %0. This bit may also be used to pace output
of data sent to another computer screen. This allows the receiving end
to pause or abort the printout without having to wait for a full RS-232
transmit buffer of data to get sent. You may have experienced this on a
BBS that was not set up for transmit pacing and it is annoying, but
easily corrected by proper program design.

Hopefully this paper has taken some of the mystery out of doing I/O from
machine language. It looks like a lot of code compared to a simple
OPEN 8,8,8,"testfile" like you would use from BASIC, and it is. But once
you have done it one time and have an assembler source file with it then
it is a simple matter to re-use that code in another program. After
writing just a few small programs in assembly language you can build a
library of routines that you will use over and over again, and thus make
the job easier as you gain experience.
Here is a table of some of the commonly used Kernal routines.

Parameter SETNAM SETLFS OPEN CHKIN CHKOUT CHRIN GETIN CHROUT


--------------- ------ ------ ------ ------ ------ ------ ------ ------
Call address $ffbd $ffba $ffc0 $ffc6 $ffc9 $ffcf $ffe4 $ffd2
Parameters A,X,Y A,X,Y none X X A A A
Reg. affected none none A,X,Y A,X A,X A,X A,X,Y A
Prep routines none none SETNAM OPEN OPEN (OPEN) (OPEN) (OPEN)
SETLFS
Error rtns none none 1,2,4, 3,5,6 3,5,7 none none none
5,6,240

Parameter CLRCHN CLOSE READST STOP PLOT LOAD SAVE


--------------- ------ ------ ------ ------ ------ ------ ------
Call address $ffcc $ffc3 $ffb7 $ffe1 $fff0 $ffd5 $ffd8
Parameters none A none none %c,X,Y A,X,Y A,X,Y
Reg. affected A,X A,X,Y A A,X A,X,Y A,X,Y A,X,Y
Prep routines none none none none none SETLFS SETLFS
SETNAM SETNAM
Error rtns none 0,240 none %z=1 none 0,4,5, 5,8,9
8,9

ERROR RETURNS : ERROR NUMBER IN .A IF CARRY IS SET ON RETURN


0 Terminated by STOP key : 5 Device not present error
1 Too many open files error : 6 Not an input file
2 File open error : 7 Not an output file
3 File not open error : 8 File name error
4 File not found error : 9 Illegal device error
240 RS-232 buffer allocated/de-allocated (top of BASIC memory moved)

In addition to these errors, most I/O operations require reading the I/O
status value to determine the I/O condition after the routine is called.
Basically, if the routine transfers data or results in serial bus
activity, then you should execute a JSR READST to check for the results
of the transfer. A non-zero result indicates an error. Refer to BASIC
contents of the reserved variable ST to determine the effect of a
READST. For serial bus I/O you can simply look at the contents of
location $90. This location is used by the Kernal to store the ST value.
Here are some brief descriptions of the parameter setup for the Kernal
routines listed above:

SETNAM : .A=name length, .X=low byte of name location, .Y=high byte


SETLFS : .A=logical file #, .X=device, .Y=secondary address
CHKIN : .X=logical file #
CHKOUT : .X=logical file #
CHRIN : Returns character in .A (If input device is the keyboard then
operation of CHRIN is similar to the BASIC INPUT statement.)
GETIN : Returns character in .A
CHROUT : .A=character to send
CLOSE : .A=logical file #
READST : Returns .A = ST value
STOP : Returns with the processor Z flag set if STOP is pressed
PLOT : Set carry, call with .Y = column, .X = row to set cursor
location
Clear carry and call to return current cursor location in .X, .Y
LOAD : .A = 0 for LOAD. To load at the saved address, use any non-zero
secondary address. If the secondary address is set to 0 then the
routine will LOAD the program at the address contained in .X, .Y
SAVE : .A = ZP ptr to start of SAVE. .X, .Y = end address + 1 of SAVE.

PART FOUR
Working with PowerC
POWER C SHELL ENVIRONMENT ASSEMBLERS
PROGRAMMERS' WORKSHOP CONFERENCE 5/25/89
by SYSOP JL

There are two 65xx symbolic assemblers which operate in the Power C
Shell
environment. In order to use these assemblers you must have Power C from
Spinnaker Software. Then, using one of these assemblers, you can
assemble
your own machine language functions callable from C or even write stand
alone ML programs with the features afforded by the assemblers and the
Power C linker.

The two assemblers are ASSM.SH and ASM.SH. The ASSM.SH program is
downloadable from the Programmers' Workshop / C Language / Source
library. ASM.SH is part of the Power Assembler package from Spinnaker or
the update to it, Buddy V10 available from the program author, Chris
Miller. ASSM.SH runs under the C64 version of Power C while ASM.SH runs
only under the C128 version of Power C.

FEATURES

CASSM is a fairly basic symbolic assembler. Source code is SEQ PETASCII


files on disk, typically created with the Power C editor. Its main
feature is that it produces relocatable .obj output files on disk which
are then linked using the Power C Linker. The linker is used to
optionally specify a load address and to link other .obj files together,
producing a loadable or executable file.

ASM is a full featured symbolic assembler. Source code is SEQ PETASCII


on
disk or RAMDISK, typically created by the Power C editor or by the EBUD
editor (ED128) which is part of the "Buddy" system (Power Assembler).
Assembler features are essentially the same as the family of Buddy
assemblers including temporary labels, long labels, conditional
assembly,
offset assembly and user definable macros for Buddy V10. Like ASSM it
creates relocatable .obj files that are combined using the Power C
Linker
to create loadable or executable disk files.

USES

There are three typical uses for these 65xx assemblers. 1) Modifying
Power C library functions; 2) Creating ML functions for use in C
programs
written for Power C; and 3) Creating stand alone ML programs in a
modular
fashion. The procedures to follow for each of these purposes are covered
below.

MODIFYING POWER C LIBRARY FUNCTIONS

You may wish to modify Power C library functions for special purposes or
to create new functions closely related to existing library functions.
The steps to do this are as follows...

1. Use RA.SH (from downloaded file RA.ARC) to disassemble the .obj


library module from one of the Power C disks. The result will be a
ASSM.SH compatible source file on the work disk. This is done in
C64
mode using the C64 Power C version, but a C128 library function can
be disassembled in this fashion also.

2. Use a SEQ file editor (such as ED.SH in Power C) to make the


desired
changes. If you are using Buddy's ASM.SH to assemble the file on a
C128 then you also need to make the (minor) source format changes
for
compatibility with this assembler. Include notes on the changes
made
and reasons for the changes.

3. Use ASSM.SH or ASM.SH to assemble the modified function. This file


should be written to a work disk other than the Power C library
disk
along with the edited source. Now do either of two things to update
the Power C library. Give the new file a different name than the
old
file, copy it to the library disk, and update the .l library file
which references it using the LIB.SH program on the Power C disk;
or
Rename the old .obj function to something else (to keep it), then
copy the new function to the library disk giving it the same name
as
the original function.

4. Test the revised function thoroughly.

CREATING A NEW ML FUNCTION FOR POWER C

The steps to create a new ML function for use with your Power C programs
are as follows :

NOTE: THROUGHOUT THE FOLLOWING EXAMPLES THE CHARACTER ' (SEMI-QUOTE) IS


USED IN PLACE OF THE UNDERLINE CHARACTER. The Power C editor allows for
the use of this character as part of a label and C functions use it
extensively. However, not all word processors, printers and SEQ file
readers will allow for it printing. Thus the substitution in this text.
This is particularly important to note with regard to explicit
definitions of labels of Power C library functions such as C$FUNCT¤INIT
including the underline character, which are shown here in as
C$FUNCT'INIT.

1. Write the source code for your ML function using a SEQ file editor
such as the Power C editor or Buddy's EBUD. Documentation for the
ASSM.SH assembler is in the C-ASSM.ARC file. The Buddy ASM.SH
assembler is basically the same as BUD or EBUD except that .BAS,
.ORG, .OFS, and .OBJ pseudo-ops are not allowed. There is no
specific
documentation on ASM.SH in the Power Assembler package, but Buddy
V10
from Chris Miller includes enough info to use it. The following
machine specific info may be helpful...

- C64 Power C : The C64 Power C parameter stack starts at $033c and
moves upwards. Zero page function work area is available at $4b-
$74.
Upon entering your function, to get access to parameters passed
from
the C function call, include the following code...

jsr c$funct'init ; get parameter stack index


stx temp ; in case you need it again
lda param'stack,x ; $033c indexed by the parameter stack
pointer

Parameters will be passed in the internal format defined in the


Power
C manual; INT parameters and pointers as 2 byte parameters in
low-byte / high-byte order; long int, short and char are converted
to
INT; float and double passed as float with 5 bytes per value; etc.

Using ASSM.SH, any label, including the callable function name,


that
is to be accessed from C or other ML functions must be defined in a
.def statement somewhere in the source code. These identify the
labels in your ML for which the linker needs your .obj file to
resolve.

.def internal'label,internal'label,...

ASSM.SH also requires that you define labels that you use which are
external to your function. This is done with the .ref pseudo-op.

.ref external'label,external'label,...

For example, if your C function is called from C with BELLS(ch);


and
returns with a status byte saved at the static location
BELLS'STATUS
then the following code elements would be needed for ASSM.SH...

.ref c$funct'init
.ref c$chkout,c$chrout
.def BELLS, BELLS'STATUS
;
parms = $033c ; Power C C64 Parameter stack location
bells = * ; called from external procedure
jsr c$funct'init ; get parameter stack index
stx temp ; save it for return params
lda parms,x ; get parameter ch in .A

sta bells'status ; accessible externally


rts
temp .byte 0 ; local index save
bells'status .byte 0 ; globally accessible static variable

You can return a function value to C by storing it on the parameter


stack at the original X register index offset. In the above example
you could have returned the char value bells'status as a return
parameter to the C call with the following code in place of the sta
bells'status...

ldx temp ; get the original offset


sta parms,x ; save the byte (char) return value
lda #0 ; dummy high byte set to zero
sta parms+1,x ; save it as the high byte of the return

In the C64, BASIC ROM is banked out but I/O and Kernal ROM are
visible. Save your source file with a .A or .ASM extension (for
example, bells.a) to indicate it is an assembler source code file.

- C128 Power C : The C128 parameter stack is at $0400 and moves


upward to $05ff. C programs start at $0600. Zero page temporary
work
area is available at $a0. If you need extensive zero page you
should
relocate page 0. As with the C64 configuration, a jsr c$funct'init
will return the parameter stack pointer in the X register. Buddy's
ASM.SH assembler requires that labels in your routine that are
intended to be externally referenced be defined with a .ext pseudo-
op. For the example in the C64 info above...

.ext bells,bells'status

This pseudo-op takes the place of the .def pseudo-op in the ASSM.SH
assembler. ASM.SH does not require you to explicitly identify
external references (the .ref pseudo-op in ASSM.SH).

C128 C programs are linked to run in C128 RAM bank 1 with only RAM
in
context. To get access to I/O you must save the REU configuration
register ($ff00), set it to I/O in context ($7e), and restore it to
bank 1 RAM ($7f) before returning. Access to the Kernal routines is
through common RAM long JSR's generally called by a jsr to a C
library function with a label c$ plus the Kernal function name.
Disassemble c$kernal.obj for a complete list.

2. Assemble your new ML function using ASSM.SH (C64) or ASM.SH (C128).


For example, the shell call...

ASSM BELLS.A or ASM d:BELLS.A d:BELLS (d: is the drive)


will load the assembler from the system disk, assemble BELLS.A from
the work disk and save the relocatable object file (linker file) as
BELLS.OBJ to the work disk. ASSM.SH replaces the .A extension of
the
source file name with the .OBJ extension when it saves the file.
ASM.SH requires you to give a file name to save the output file to
and it automatically adds a .OBJ to the end of the file name. With
ASM.SH, if you do not specify a output file name the assembler will
run and produce error reports, output listings requested, etc but
will not save the assembled file.

3. Write a small driver program in C or ML and test your routine.

4. To link your new routine with a C program, give the relocatable


object file name to the linker after your C compiled object file(s)
have been linked and before the library files. For example...

link
> bellstest.o
> bells.obj
> ^

STAND ALONE ML PROGRAM DEVELOPMENT

The Power C shell environment assemblers may also be used in developing


stand alone ML programs for the C64 or C128. This way you can create
separately assembled, relocatable modules that can be linked with the
Power C linker, LINK.SH. When used in this way you do not have to be
concerned about the C memory configuration or usage. You have complete
control of memory use just as you would with any assembled ML program.
Each assembly module that is separately assembled would have to include
the appropriate .ext, .ref and .def pseudo-ops as discussed above.

One additional thing that is needed by the linker that is not obvious...
a label MUST be defined in one of the program modules as an entry point
when the file is loaded. This beginning execution address must have a
label MAIN and that label must be defined as external by a .ext or .def
pseudo-op. When the linker combines the relocatable object modules
including the one with the MAIN label it will produce a JMP to MAIN at
the load address supplied to the linker. Use the -S option on the LINK
call line to produce the stand alone executable file. This linker
command
is documented in the Power C manual. If -S and an address is specified
then that address will be the program load address. Otherwise the
program
will be linked to load at the start of BASIC as a load and run program
with a SYS to the start address.

REFERENCE FILES

Here is a list of files in the Programmers' Workshop library which will


be of interest in connection with this topic. All files are located in
the Programmers' Workshop / Software Libraries / C Language library in
either the Source or Utility sections as noted.
File name Uploader Date Sublib Description
---------------- ---------- -------- ------ -------------------------
-
cassm64stuff.arc Mark MM 9/12/88 Source Example assm src files
cassm.arc Mark MM 4/14/88 Source The assm.sh files
asm.sh-info Mark MM 4/14/88 Source Text info on using ASM.SH
newshell.arc Dan B15 2/4/89 Utility C64 C Shell assm src code
ra.arc Mark MM 9/12/88 Utility C64 C symbolic
disassembler

PART FIVE
BETTER WORKING

From Spinnaker

P O W E R A S S E M B L E R

Contains Documents for Both C=64 and C=128 Versions

S P I N N A K E R

Copyright 1986 by Spinnaker Software Corporation. All rights


reserved.
The distribution and sale of this are intended for the use of
the
original purchaser only and for use only on the computer system
specified. Lawful users of this program are hereby licensed only
to read
the program from its medium into memory of a computer for the
purpose of
executing this program. Copying, duplicating, selling or
otherwise
distributing this product is hereby expressly forbidden.

SPECIFICATIONS

Your POWER ASSEMBLER actually encompasses two stand-alone


machine
language development systems for the Commodore 64 and three for
the
Commodore 128. One uses the Basic editor (enhanced by string
SEARCH &
REPLACE commands) for writing its memory based source and the
other its
own powerful ASCII editor. For the Commodore 128, there is also
a
complete Z/80 (CP/M's micro processor) cross assembler which has
all the
powerful commands and features of the other two programs.

Here is a brief rundown of the programs you will find on your


system
disk.

BUD is the boot for the basic source compatible version of the
assembler.

EBUD is the boot for the ASCII editor and its version of the
assembler.

ZBUD (C=128 only) is the boot for the Z/80 cross assembler.

BUDDY.64 is the body of the Basic source compatible version of


the
assembler which is loaded into memory when "BUD" is run.

BUDDY.ML (C=128 only) is the body of the Basic source compatible


version
of the assembler which is loaded into memory when "BUD" is run.

BUDDYSYMS is the symbol table for "BUDDY.64" or "BUDDY.ML" as


generated
by its assembly. You can use it to explore the code and to
create your
own commands.

CREATE-BOOT (C=128 Only) can be used to generate an autoboot for


"BUDDY.ML" that will not disturb source already in memory.

ED-BUDDY.64 or ED-BUDDY.ML is the body of the ASCII editor


compatible
version of the assembler. It is loaded into memory along with
"EDITOR.64" or "EDITOR.128" when "EBUD" is run.

ED-BUDDYSYMS is the symbol table for the "ED-BUDDY.64" or


"EBUDDY.ML" as
generated by its assembly. You can use it to explore the code
and to
create your own commands.

EDITOR.64 or EDITOR.128 is a multi-featured ASCII text editor,


one that
does not clash with the Basic operating system.
MAKE-ASCII can be used to convert Basic style source to
EDITOR.64 or
EDITOR.128 compatible ASCII source.

TEST.MNE is a complete source listing of all standard and non-


standard
8500 mnemonics. Use these to familiarize yourself with the 8500
command
syntax and to test the assembler.

TEST.ZMNE (C=128 Only) is a complete source listing of Z/80


instructions.
Use it to test ZBUD as well as to examine Z/80 assembly language
syntax.

INVOKE-Z80.BAS (C=128 Only) is a short example of a Basic


program calling
Z/80 subroutines.

SWITCHER-SOURCE (C=128 only) is a POWER ASSEMBLER source program


to
generate the 8500 "pivot" code used by the above. These
demonstrate
dual processing techniques in the C=128 and will help you make
full use
of the ZBUD Z/.80 cross assembler.

UNASM-SOURCE is the complete, documented source program for our


powerful
unassembler that will convert raw code to source that you can
LIST, SAVE,
LOAD and, best of all, re-assemble using BUD.

ZBUDDY.ML (C=128 only) is the body of the Z/80 cross assembler


which is
installed wench "ZBUD" is run.

ASM.SH (C=128 only) is the version of POWER ASSEMBLER that was


designed
to run under the SHELL program from POWER C 128 for C language
programmers. It is fully compatible with the Shell, Editor,
RAMDISK, and
Linker, and can be used both as a stand-alone assembler in this
environment or to write custom C language functions.

COMPATIBILITY
Your POWER ASSEMBLER is completely compatible with the Basic 2.0
source
format used in the Commodore 64 and with the Basic 7.0 source
format used
in the Commodore 128, and with the Commodore disk operating
system used
in both.

Assembly language programs can be written on the much enhanced


C=128
editor. In addition to using this new editor's ability to
renumber and
auto-number lines, delete line ranges, pause scroll and much
more, with
BUD in memory users will be able to execute powerful string
(label)
search and replace commands.

Pure ASCII SEQ. or PRG files can also be assembled from disk or
memory,
allowing source to be written on virtually any text editor or
word
processor. POWER ASSEMBLER's own EDITOR.64 and EDITOR.128
provided
supports 4-WAY, bi-directional scrolling and paging as well as
CUT &
PASTE, SEARCH & REPLACE and much more.

POWER ASSEMBLER for the Commodore 64 is fully compatible with


FASTLOAD/SAVE disk utilities such as BETTER WORKING's TURBO LOAD
AND
SAVE.

SPEED

On the Commodore 64, symbols are organized as a binary tree for


very fast
access of even gigantic tables. 6510 Mnemonics are divided into
numerous
short lists which are selected via a quickly generated hash
value for
virtually instantaneous command lookups. BUD LINK assembles its
own ten
source files creating to ML programs containing over 8K of code
in about
two minutes with Turbo Load and Save installed.
On the Commodore 128, source files can be linked or disk
assembled using
1571 burst mode access through a quasi RAM DISK maintained by
the
assembler for very fast chaining. A binary structured symbol
table and
hash code access to multiple, very short mnemonic lists insure
near
instantaneous memory based operations.

INPUT

Single, large source programs can be assembled directly from


memory, or
many source files can be assembled as one, either by load
linking or
direct disk based assembly. Many combinations of memory and
disk based
assembly are also possible.

Multiple device handling, allows for the application of any


number of
disk drives.

OUTPUT

Code can be sent directly to memory allowing for fast, diskless


testing of fairly large programs.

Any number of LOADable, machine language programs, all sharing a


common symbol
table, can be created in a single operation.

Symbol tables can be automatically saved in part or in full and


used by other
source programs. New modules for very large ML projects can be
designed,
tested and retested in memory without having to re-assemble the
whole system
each time.

POWER ASSEMBLER can be instructed to direct all output through


custom user
routine for special handling.

DISPLAY
Show full assembly process including source lines, object code
and symbol table
listing for all or any portion of an assembly.

Paginated output may also be directed to a printer.

Error checking is complete and error messages are full and


descriptive. Where
many errors are anticipated a Display-Only-To-Printer mode is
supported.

LANGUAGE FEATURES

If/else conditional assembly is supported

Temporarily offset program counter assembling generates patches


of code that
will be relocated before execution.

Setup internal buffers as well as passive external variable


tables
effortlessly.

Automatically merge Basic as assembler source programs. BUD


allows Basic to
SYS, POKE, and PEEK assembled table values by name.

Work with non-standard opcode using all of their unofficially


yet generally
agreed on mnemonic forms.

Macro-ops to move memory (up or down any distance), fill memory


and test
pointers make short work of these common and often tedious
procedures.

Data can be in the form of word tables, byte tables, ASCII text,
and even
screen-code text.

Multiplication, division, addition and subtraction of any


combination of hex,
binary, decimal, ASCII, screen code or symbolic values is
supported.

Symbols may be of any length and remain unique.


Temporary (reusable), character ( + - / ) symbols allow for
easier coding of
routine short branches and result in smaller symbol tables and
even faster
assemblies.

GETTING STARTED

If you are like most people you will want to try something right
away just to
feel the program out and get on the right track.

For Commodore 64 For Commodore 128

LOAD"BUD",8 <RETURN> DLOAD"BUD <RETURN>


RUN <RETURN> RUN <RETURN>

This will cause the body of the program to be loaded into memory
and executed.
Upon completion you should see a line of copyright information
alongwith a pair
of meaningless (at this time), hex range numbers.

MEMORY USAGE

POWER ASSEMBLER resides completely in the "hidden" RAM beneath


Basic and the
Kernal ROM. This maximizes the amount of free memory for
source, symboltable
and utilities such as SUPERMON and POWER which lower the top of
Basic.

For the Commodore 64, a few bytes of memory starting at 999 are
reserved for
POWER ASSEMBLER's entry code.

A SYS 999 will probably, though not necessairly, be the only


Basic statement
executed before POWER ASSEMBLER takes over. In other words, a
SYS 999 invokes
the assembler. POWER ASSEMBLER will act on all source
following. Basic will
interpret and try to execute everything up to and including the
SYS 999 line.
For the Commodore 128, the page of memory between $f00 and $1000
in BANK 15 is
reserved for POWER ASSEMBLER activities. If you interfere in
this area you
may have to re-boot the program. If you need to press the reset
button for
other reasons, POWER ASSEMBLER should not be affected. The
entry routine sits
at 4000. A SYS 4000 will probably, though not necessairly, be
the only Basic
statement executed before POWER ASSEMBLER takes over. In other
words, a SYS
4000 invokes the assembler. POWER ASSEMBLER will act on all
source following.
Basic will interpret and try to execute everything up to and
including the SYS
4000 line.

WARM-UP EXERCISE

Enter the following short program just as if you were writing in


Basic. The
sequence of the line numbers is important but the actual line
numbers
themselves are not. You don't have to bother typing in the
comments. The
colons are used to introduce white space to the source in order
to make it more
readable. They could be replaced with UP ARROW's ( ) or left
out altogether.

1 SYS 999 ;calls Power Assembler


2 .ORG 10000 ; put code at 10000
3 .MEM ; output to memory
10 PRINT =$FFD2 ;kernal routine
20 LDX #0 ;initilize X
30 - LDA MESSAGE,X ;get next character
40: JSR PRINT ;print it
50: INX ;increment x
60: CPX #MESSAGELEN ;see it done
70: BNE - ;if not loop back
90 RTS
100:
110 MESSAGE =*
120 .ASC "HELLO WORLD"
130 MESSAGELEN =*-MESSAGE
Look it over to see if you got it right, then RUN it. (Now
type RUN to
compile the program to memory and SYS 10000 to run your compiled
program.)
If this was your first ever coding in assembler you undoubtedly
are facing a
number of error messages. Examine lines with errors closely to
see how they
differ from the above source. When you can assemble with no
error messages
try executing the code with a SYS 10000.

If all went well HELLO WORLD will be printed on the screen. If


not, you should
know that you are the first person ever whose assembly language
program failed
to work perfectly the first time ( ha ha, only kidding). Try
again.

Notice the .ASC command. Commands with periods in front of them


are called
pseudo-ops. They do not represent any particular ML opcode.
They are
instructions to POWER ASSEMBLER. Familiarity with them will
allow you to take
full advantage of POWER ASSEMBLER's many abilities.

Notice the use of "-" as a label. This is an example of the use


of a POWER
ASSEMBLER temporary label. Some name like LOOP or BACK or HOWDY
could have
been used in place of the "-" characters; but why bother? The
BNE - codes a
conditional branch back to the last line labeled with a "-"
character. The "+"
is a forward referencing temporary symbol. These can be used
again and again.

Notice also how the statements are laid out. Each statement
consists of up to
four distinct parts:

A FLAG or LABEL when used will come first. POWER ASSEMBLER will
place it in the symbol table (unless temporary) along with the
address of the program counter at the beginning of its line.
Throughout your source you may refer to that particular line
(i.e.
address) using the symbol name.

Next comes the OPERATOR which is the instruction portion. It


will be a PSEUDO-
OP or MACRO command to POWER ASSEMBLER or a mnemonic
representing a specific
opcode.

Many operators will require an OPERAND address or value to


complete their
instruction. The OPERAND of the assembly language statement
follows the
operator.

Last will be your comment. A semi-colon must precede it. These


are
of no use to POWER ASSEMBLER who knows exactly what is going on
all
the time, but can be of tremendous benefit to you who may
someday
forget.

SYMBOL OPERATOR OPERAND


COMMENT
10 MEANINGFUL LDA #0
;EXPLAIN

There must be at least one space separating each of the first


three parts.
Extra spaces will be ignored.

SYMBOLS

Symbols may be of any length so you can and should use very
meaningful name.
The apostrophe has no special meaning to POWER ASSEMBLER; multi-
word symbols
should probably be broken up with these for clarity. Notice how
much more
readable WRITE'TO'TAPE is than WRITETOTAPE, or COLOUR'MEMORY is
than
COLOURMEMORY.

Permanent symbol names may not begin with any of the following
characters:
0 1 2 3 4 5 6 7 8 9 ! # < > " @ $ % ( ) : ;
,
. / * + - =

or contain any of the following arithmetic operators.

/ * + - =

These would cause POWER ASSEMBLER to mistakenly assume the


symbol was a numeric
or character value, or expression, probably resulting in a
delightful and
poignant error message.

Symbols may not contain blanks. Again, use the apostrophe to


break them up.

Also, a symbol may not be the same as one of the standard


mnemonics like LDA or
DEX or BNE. POWER ASSEMBLER is great but it can't read your
mind.

EQUAL ASSIGNMENTS

In addition to flagging, symbols may be given values using an


assignment
statement. The equal sign is used just as in Basic assignments.
One space
must follow the symbol name. Extra spaces are optional. Here
are some
examples:

1 SCREEN'START = $400
2 SCREEN'END = SCREEN'MEMORY+999
3 CHROUT = $FFD2
4 PROGRAM'COUNTER = *

SET ASSIGNMENTS

You cannot use the equal sign or flagging to reassign a new


value to an existing

symbol. To change a symbol value you should use the LEFT ARROW
( ) in place of

an equal sign. Symbols to be reassigned should be assigned


values exclusively
with the LEFT ARROW assignment operator so that they maintain
parallel values on

both phases of the assembly.

LEFT-ARROW, set re-assignments can lead to confusing programs


and hard-to
diagnose errors. POWER ASSEMBLER's support of temporary
symbols, large symbol
names and numerous program counter pseudo-ops reduce the
possibility that symbol

value re-assignments will be necessary.

ASSIGNMENTS TO PROGRAM COUNTER

Symbols may be set to the value of the program counter in two


ways: (1) by
assignment to the program counter " * " variable (see line 4
above) and (2) by
flagging. Flagging involves simply putting the name first on
any line:

10 ANYNAME DEX ;decrement x register


20 BNE ANYNAME ;loops until x = 0

The same program counter value can be assigned to a number of


symbols via the
"* " assignment. Only one flag may be used per line. Tabled
flag values are
compared with the program counter on pass two of the assembly in
checking for
deadly out-of-phase condition.

OPERATORS

The operator is also referred to as the instruction. In English


or source form
it is called a mnemonic. Once converted into a machine language
byte it is
known as an opcode.

The operator is the command portion of the assembly language


statement. The
commands which begin with a period are called pseudo-ops. These
are not
converted into any specific opcodes but tell POWER ASSEMBLER to
do something
special.

Many, though not all operators, will require that some


information follow.
This may be an address or numeric value or a string of comma
delimited values
or even quoted text as in a filename .ASC string. Absence of
this information
will lead to an OPERAND EXPECTED error message.

POWER ASSEMBLER of course recognizes all of the standard


mnemonics used to
represent machine language opcodes. These three letter terms
are converted by
the assembler directly into the appropriate one byte opcodes.
You will be
informed if the value was expected but didn't follow an
instruction, or if an
unexpected value or illegal value followed.

OPERANDS

These are the values which are required by many operators to


complete their
instruction. If you start a line with an STX instruction it is
assumed that
you wish to STore the information in the X register somewhere.
Therefore,
following this must be an OPERAND value relating to where in
memory this value
is to be put.

An operand may be elsewhere defined symbol; a hexadecimal,


binary or decimal
value; or a screen code or ASCII character. Any combination of
these may be
used in an expression to produce an operand value. Binary
numbers must begin
with a percent sign (%11110000). Hexadecimal numbers must begin
with a dollar
sign ($f0). Decimal numbers are otherwise assumed (240).

The greater than $ffff (65536 decimal) or values less than 0


will lead to an
error message.
10 LDX #END-START ;length of whatever
20 STA 12*4096 ;store at $c000
30 LDA POINTER+1 ;high byte of pointer
40 LDA #$100-15 ;is 241 (negative 15)
50 LDA #"&"+128 ;ascii for reversed &

Here is how an RTS jump might be coded using expressions as


operands:

10 LDA >PICTURESHOW-1:PHA
20 LDA <PICTURESHOW-1:PHA
30 RTS ;is the same as jmp
pictureshow

Notice that two or more statements can be put on one source line
if they are
separated by a colon. The colon always signals a new line with
two exceptions:
(1) when the colon occurs between quotes as in a filename, (2)
when the colon
occurs in a comment ... that is after a semi-colon. A command
cannot follow a
comment on the same line.

The " < " and " > " (low byte -- high byte) operators are always
applied after
the entire expression following has been evaluated. They also
indicate that
the value represents a numeric constant and not an address; in
other words, an
immediate value.

EXPRESSIONS

Multiplication, division, addition and subtraction are supported


expressions.
Fractions are truncated in division. The expression 8/3 would
equal 2.

Expressions are evaluated strictly from left to right. Any


combination of
hexadecimal, decimal, binary, ASCII, screen code, or symbolic
integer values
may be involved. Nowhere in the course of evaluation an
expression will a
negative value be tolerated. Neither will a value greater than
$FFFF be
acceptable.

Expressions which generate negative values are often accidental.


Calculating
the length of a table by subtracting the address of its end from
the address of
its beginning (instead of the other way around) could result in
a frustrating
and hard-to-diagnose mis-performance of the code.

If one must work with negatives they can be easily, just not
accidentally,
expressed.

100 .BYTE $100-VAL ;= -val


200 .WORD $FFFF-VAL+1 ;= -two byte val

Parenthesis are not supported in POWER ASSEMBLER expressions.


These signal
indirect addressing mode only. If ordering cannot be properly
established in
simple left to right layout then an expression should be divided
into two or
more parts.

When the " * " is used as a variable in an expression it always


holds the value
of the program counter. This is the address at which the code
for the " * "
line will originate in memory.

Here " * " is used to point to the address operand portion of a


self modifying
JSR instruction.

10 LDA <DESTINATION
20 STA TARGET
30 LDA >DESTINATION
40 STA TARGET+1
50 JSR $0000: TARGET =*-2

When the "<" character precedes an expression it acts on the


entire value.
That is, the expression is completely evaluated first, and then
the low byte
only of this value is returned.

The ">" returns only the high byte.

10 LDA <$ABCD ;same a lda #$cd


10 LDA >$ABCD ;same as lda #$ab
10 LDA <$1100-1 ;same as lda #$ff
10 LDA >1100-1 ;same as lda #$10

Notice again how the ">" and "<" always force immediate mode.
In other
assemblers only the "#" can do this. Indeed you could use LDA
#<OPERAND with
POWER ASSEMBLER, but the "#" would be superfluous.

The same is true when using screen code and ASCII values:

10 LDX "A" ;same as ldx #"A" or ldx


#65
20 LDY @"A" ; screen code i.e. ldy
#1

The immediate mode is automatic when using ASCII codes, screen


codes, and
low or high bytes. A situation where you would wish it
otherwise is
inconceivable. Using immediate mode where its intention is
obvious should help
you avoid often puzzling "#" omission errors where zero page
addresses are
accessed instead of one byte immediate values.

ADDRESSING MODES

Syntax for describing addressing modes is highly standardized.


POWER ASSEMBLER
adheres strongly to this standard. The value 0 is used in the
following to
represent any one byte operand. The value 1000 is used where
any two byte
operand would do.

1 LDA #0 ;immediate value


2 LDA 0 ;zero page address
3 STX 0,y ;zero page y indexed
4 LDA 0,x ;zero page x indexed
5 LDA 1000 ;absolute address
6 LDA 1000,x ;absolute x indexed
7 LDA 1000,y ;absolute y indexed
8 LDA (0,x) ;pre-indexed indirect x
9 LDA (0),y ;indirect post-indexed y
10 BNE 1000 ;relative branching
11 JMP (1000) ;indirect jump
12 LSR ;accumulator implied
13 INX ;implied

Some assemblers allow or require that the accumulator mode be


expressed LSR A,
ASL A, ROR A or ROL A. POWER ASSEMBLER, however, would try to
look up the "A"
in the symbol table. So leave it off.

POWER ASSEMBLER will use zero page addressing whenever possible.


You may force
absolute addressing with the "!" character.

1 LDA $FF,X ;codes b5 ff


2 LDA !FF,X ;codes bd ff 00

ERROR MESSAGES

Most of us never make mistakes and have no use for error


messages. Still there
are always a few to go and spoil it for everyone. So for these
few people we
have included comprehensive error handling and checking. The
rest of us
perfect programmers can just skip over this section.

Seriously however, I use 'em myself ... a lot. Unless an error


is fatal, POWER
ASSEMBLER will place NOPs into your code where it is
encountered. The number
of bytes which would ordinarily have been generated by the
instruction
determines the number of NOPs output. If you were to include
the line 1000
YIPPIE DIPPIE in a program you would get an error message but no
bytes would be
output. Your code would not be affected.

If you had written 1000 BNE *+500 you would get a BRANCH OUT
OF RANGE error
and two NOPs would be output. You might be able to do a certain
amount of
testing in spite of it.

Other errors, knows affectionately as fatal errors, will


terminate the assembly
after closing all files.

This is the case with phase errors, I/O errors or symbol table
overflow, If
you press the RUN/STOP key assembly is stopped. Open files are
always closed.
If you are .FAST assembling with a blank screen an error will
turn it back on
instantly.

The messages are fairly self-explanatory and, in most


situations, should make
it easy to diagnose the problem. Error messages are always
listed above the
offending source line which is displayed following a >>>.

Here is a rundown of each of POWER ASSEMBLERS error messages:

QUOTE EXPECTED following .ASC or .SCR or there is more than one


character
between quotes where only one is permitted.

UNKNOWN PSEUDO-OP means you've used the "." as the first


character of a
symbol or mis-spelled a pseudo-op (.BITE).

TOO MANY STRINGS if more than three space separated "words"


appear in one
statement outside quotes.

COMMAND EXPECTED means a mnemonic or pseudo-op was expected.

OPERAND EXPECTED means a value or parameter is needed to


complete some
instruction.

INVALID MODE OF OPERATION if you try to code some addressing


mode not allowed
with the command.
RE-DEFINITION OF A SYMBOL if you used the same flag twice or
otherwise
tried to reassign a value to a symbol with "=".

ONE BYTE VALUE EXPECTED if you try to use a two byte operand
where
unacceptable.

IMMEDIATE VALUE INDEXING is a special case of the invalid mode


in which one
tries to index a number instead of an address i.e. lda #100,x

KEYBOARD ERROR probably indicates a typo or perhaps some illegal


character
making its way into a symbol.

VALUE TOO HIGH OR NEGATIVE when a negative value or a value


higher than $ffff
is
arrived at during an expression evaluation.

NON-NUMERIC CHARACTER a symbol begins with a number 0-9, or a


non-digit has ma
de
its way into a number.

UNDEFINED SYMBOL means that the symbol was not found (on pass
two only) in the

symbol table; perhaps it's mis-spelled or a "$" has been left


off a hex value.

BRANCH OUT OF RANGE if you attempt to relative branch to far


i.e. more than +1
27
or -128 from *+2.

UNEXPECTED OPERAND if some inherent command is followed by a


value.

SYMBOL TABLE OVERFLOW there is no longer room in memory for your


source and
generated symbol table. You'll have to .FILE assemble it from
disk or split it
into two or more pieces and .LINK them.
FILE NAME EXPECTED a special case of operand expected; a file
name is needed t
o
complete one of the file handling pseudo-ops.

PHASE ERROR For some reason the symbol table as created on pass
one is out of
sync with the code on pass two; this could be caused by a late
zero page
assignment or leaving characters outside quotes in a .SCR or
.ASC text string.
POWER ASSEMBLER checks for phase errors by looking up all labels
on pass two to
see if their value in the symbol table matches the program
counter. If not
something has gone very wrong. You don't want to continue in
this condition.

BUS CRASH!!! there's a problem on the IEEE bus; the disk command
channel is re
ad
for your enlightenment.

If you are assembling a very large, perhaps newly converted,


program for the
first time and anticipate more errors than will fit on the
screen then you might

want to direct errors-only to your printer via the .DIS E


command.

COMMENTS ON STYLE

POWER ASSEMBLER allows you to use colons to link source


statements just as in
Basic. Never abuse this! Your source may become unreadable.

WHITE SPACE

Use a few blank lines to separate the various modules and ideas
of your program.

Indent everything but your symbols a few spaces to the right so


they stand out.

There are two ways to do these things: Obviously you just cant
enter a blank
line; Basic editor would ignore or erase it. Put a colon, or
better yet, an UP
ERROR ( ) by itself on the line. The UP ARROW is ignored by
POWER ASSEMBLER
as the first line character. Use it to keep the BASIC editor
from removing
leading spaces.

COMMENTS AND MEANINGFUL SYMBOLS

Use meaningful symbols and comment liberally. I have always


been impressed by
this in the source programs of experts.

Use the temporary symbols "-", "+", and "/" to code short
branches and avoid
having to generate meaningless symbol names for them. This
should free up your
imagination for those names that do matter. It will also allow
crucial,
thoughtful labels to better stand out.

Use POWER ASSEMBLER's built in string handling editor, Labelgun,


to keep your
symbols meaningful and up-to-date without typing or hunting
around.

MAKE THE ASSEMBLER DO IT

A common mistake of beginners is to calculate by hand the


lengths of strings
and tables in their programs. Use symbols and expressions to do
this;
then, when you change the length, you wont have to recalculate.

10 TABLEBEGIN =*
20 .ASC "******PRINT MESSAGES"*****"
30 .SCR "/////SCREENCODE VALUES/////"
40 .WOR 1000,2000,ADDRESS,256*12
50 .BYT 0,1,2,4,8,16,32,64,128
60 ;or whatever else goes
in tables
70 TABLEEND =*
80 TABLELENGTH = TABLEEND-TABLEBEGIN

In short, make the assembler do the work. Assembly language, in


addition to
producing very fast and compact code can be flexible, versatile
and easy to
modify and understand.

PSEUDO OPS

Here are the pseudo-ops which POWER ASSEMBLER recognizes. Where


a [word]
operand is required any valid POWER ASSEMBLER expression
involving any
combination of $Hex, Decimal, %Binary, "ASCII", @"SCREENCODE" or
symbolic
values can be used. If a [byte] is expected the expression
value cannot exceed
255.

When quote enclosed names or text strings are expected those


provided are only
examples. Make up your own, okay.

When the operand is a pointer [...PTR] a one byte value is


needed. It will
represent a zero page address. The square brackets are not part
of the command
syntax. They do not go in your source.

PSEUDO-OPS **quick reference table**

*= [word] ;set program counter


.ORG [word] ;set program counter
.BUF [word] ;create internal buffer
.OFF [word] ;offset code destination
.OFE ;end of offset coding
.MEM ;output to memory
.DIS ;display assembly
(on/off)
.DIS P ;display assembly to
printer
.DIS E ;display only errors to
printer
.OUT [word] ;output through user
routine
.DVI [#] ;define input device
(default 8)
.DVO [#] ;define output device
(default 8)
.OBJ "MY-PROGRAM" ;create object file
.BAS "COMBO-PRG" ;merge Basic with ML
.LINK "NEXT-SRC" ;chain next source file
.LOOP "BACK-FIRST" ;end of chain
.FILE "ANY-SOURCE" ;assemble from disk
.SEQ "ASCII-FILE" ;assembles ASCII format
source file
.LST "SYMS-TO-USE" ;load symbol table
.TOP ; top of .SSTwhen not
all symbols
.SST "SYMBOL-TAB" ;save symbol table
.BYTE [byte,....] ;table one byte value(s)
.WORD [word,....] ;table two byte value(s)
.ASC "characters..." ;table ASCII value(s)
.SCR "character(s) ;table screen code
value(s)
.PSU ;for non-standard opcode
mnemonics
.FAS ;turns screen off during
assembly
.END ;force end of source
.IF [word] ;if word <> 0 then
continue
.ELSE ;otherwise skip here and
then begin
.IFE ;conditional assembly
ends
.TEST [THIDPTR,THATPTR] ;compare indirect
addresses.
.DUMP [BEGINPTR ENDPTR ;dump acc.to range of
memory.
.MOVE;[BEGINPTR,ENDPTR,DESTPTR] ;move memory

Any pseudo-op mneumonic can be extended or truncated. If you


would rather use
.WOR or .WO or even .W instead of .WORD, POWER ASSEMBLER will
still accept it.
Conversely .BUFFER, .DISPLAY or .MEMORY would work the same as
.BUF, .DIS or
.MEM.

Programmers, being the lazy lot they are, are more apt to
truncate than extend
POWER ASSEMBLER's pseudo-ops. Don't get to carried away with
this. The
command .) $C000 might ORIGinate program counter to $C000; it
also might cause
the power assembler to jsr OUT through a user routine or do some
OFFset coding,
or even try to open up an OBJect file.

PSEUDO OPS, DEFINITIONS

.ORG [address]

The .ORG pseudo-op must be followed by an address value. This


tells the POWER
ASSEMBLER where the machine language output will reside in
memory. If it
is not used the output will ORIGinate at $C000 hex.

10 .ORG $2000 ;code at $2000 hex


10 .ORG 50000 ;code at 50000
decimal
10 .ORG *+2 ;bump program counter by
2

>ORG *+2 above will not result in actual output. If you had
been sending bytes
to disk such a statement would lead to trouble. When loaded
into memory the
code would be out of sync with the symbol table used to create
it.

Instead, use .ORG to set up flexible variable tables before


output has begun
and especially after output has ended.

10 .ORG $200 ;system input buffers


20 FLAG1 .ORG *+1
30 FLAG2 .ORG *+1
40 VECTOR1 .ORG *+2
50 VECTOR2 .ORG *+2 ;and so on...

.ORG replaces the *= assignment found in this and other


assemblers. Again, do
not use it to create space within your code. To create internal
buffers use
the .BUF [#bytes] or *= [new pc] command.

.BUF {# of zeroes to send]

The value following the .BUF determines the number of zeros to


be output This
can be used to create buffers for I/O or space for variables
within your code.
the command .BUF 6 would do the same thing as .BYTE
0,0,0,0,0,0,0.

Situations may arise where you do not know exactly how many
zeros you want to
write, only the destination address to which you wish to write
them. The
command .BUF DESTINATION-* would do the job as would
*=DESTINATION. In either
case the value of DESTINATION must be previously defined or
immediately
calculable.

Usually variable tables sit on top or lie at the bottom of a


program or are off
somewhere else completely in memory and do not contribute to the
size of the
object code. If for some reason an internal variable table is
needed, the .BUF
or *= commands should be used. Following they are used
interchangeably
although you might want to do so purely for aesthetic reasons.

. ;code being sent to


disk
. ;more code
10 JMP ENDOFTABLE ;jump over internal
table
20 STARTOFTABLE =*
50 VAR1 .BUF 1 ;internal vars
60 VAR2 *=*+1
70 FLG1 .BUF 1
80 FLG2 *=*+1
90 PTR1 .BUF 2
100 PTR2 *=*+2
. ;etc.
. ;etc.
400 ENDOFTABLE =*
410 ;code continues

Note: any symbol used in an operand to either .BUF, .ORG or =*


must have
already been defined. Forward references will not work since
the number of
bytes generated must be calculated on the first pass.
To recap: .ORG [EXPR] may be used to set any value to the
program counter "*"
variable at anytime and will never result in output. It is
most useful in
defining load (header) addresses prior to creating of .OBJect
files and for
creating un-initialized variable tables at the end of object
files.

.BUF {EXPR] will always result in the output of the expressed


number of zeros.

*= [EXPR] will generate zero filler bytes to the new "*" value
only after you
have begun sending bytes to an object file; it may not then be
used to reverse
the program counter.

.OFF [address]

The .OFF command is used to write code designed to execute at a


different
location than where it originates. The operand portion tells
POWER ASSEMBLER
where the code will finally execute. This should prove
invaluable in
programming for more than one micro-processor at a time or in
situations where
you are writing code that will be moved before it is run. The
.ORG command
could be used to do the same thing by resetting the program
counter after
output has begun then back to the proper in-stream value (by
using some
symbolic expression) after the offset coding has finished. This
is not as
convenient as or as clear as using .OFF DESTINATION to create
another temporary
program counter.

.OFE

The .OFE command simply ends offset coding and resumes with the
original
program counter at its new address. If assembly is DISplayed
for OFFset coding
the program counter, at first glance, may not appear to have
been affected;
however, symbol values, JSR's and other absolute references to
it will
correspond to the defined .OFF, not as originally set with .ORG
and as
displayed.

The following program moves a short "POKEHELLO" routine into the


C64 cassette
buffer, calls it, then continues.

5 SCREEN =1024 ;(C-128 in 40 column


mode)
10 *= 2000 (C-128 *= 3000) ;or where ever
20 LDX #LENGTH'TO'MOVE-1
30 - LDA CODE'TO'MOVE,X
40 STA POKE'HELLO,X
50 DEX
60 BPL - ;use temporary sym "-"
70 JSR POKE'HELLO
80 JMP CONTINUE
85:
90 CODE'TO'MOVE =*
100 .OFF 832 ;cassette buffer
110 POKE'HELLO =*
120 LDX #0
130 - LDA MSG,X
140 STA SCREEN,X
150 INX
160 CPX #MSGLEN
170 BNE - ;temp sym used again
180 RTS
190 MSG .SCR "HELLO":MSGLEN =*-MSG
200 LENGTH'TO'MOVE = *-POKE'HELLO
210 .OFE ;back to normal
215:
220 CONTINUE =* ;and on we go

.OFF would be useful in creating code destined to execute in the


`1541 disk
drive after being loaded into the C-64 as part of a larger
program.

For the Commodore 128 only, moves like the above are quite
useful in
programming the C-128. POWER ASSEMBLER, for example, before
assembling, moves
"relay" code into Basic's input buffer ($200) where it can see
and be seen by
all other banks. This allows POWER ASSEMBLER to access Kernal
ROM, registers
and user defined routines and memory which would be otherwise
invisible. .OFF
would also be useful in creating code destined to execute in the
disk drive
after being loaded into the C-128 as part of a larger program.

.MEM; output to memory

This command takes no operand. It simply instructs POWER


ASSEMBLER to output
code directly into C-64 memory. The code will be "poked" into
memory at the
.ORG address

.MEM is a toggled command. The first occurrence initiates


memory output, a
second turns it off, a third turns it back on again, and so on.
This allows
selected portions of a program to be output to memory. Fairly
large programs
can be worked on without going to disk.

.BANK [0-15] (C-128 only)

This selects an output bank for in memory operations. Bank 15


is the default.
In bank 15 all Basic and Kernal ROM is visible which means these
routines
can be called directly and that special interrupt handling or
squelching will
not be required. However, there is not all that much RAM.
Chances are that
large ML programs will not finally execute in bank 15. The
.BANK command
can be used in conjunction with .MEM to direct object code to
memory in any
bank.

.DIS
When this is used the complete assembly process will be shown on
screen.
Included in this will be the following from left to right:

1. The Basic line number of the source line being assembled, or


if it is an un-
numbered ASCII file being assembled from disk, then the
sequence number of the
source line in the file.

2. The current program counter

3. One, two or three hex values representing the object code,


if any is
generated.

4. The actual source line.

.DIS is an on/off toggle command. This allows for display of


selected portions
only of the assembly. In the display mode, when assembly is
finished, the
symbol names and values, as defined in the program, will be
listed. If this is
not wanted then use .DIS to turn the display off at or near the
end of your
source. If only the symbol listing is wanted then turn the
display mode on by
using .DIS for the first time as the last source command.

.DIS P

This will direct full display to a printer as well as to the


screen. Use the
.DIS P command to generate detailed source/assembly listings.
Paging is
controlled by POWER ASSEMBLER. Three things are assumed.

1. The paper is positioned at the top of a page. Only four


blank lines are
allowed per page so don't start down too far or the
perforated edgeswill
be printed over

2. Paper is of the standard size (i.e. 66 lines per page)


3. Continuous form feed is acceptable. It is doubtful that
anyone
will want source listings on separate pieces of paper. Be
sure enough
paper is at hand. Once printing has begun the only way to
stop is to
abort the assembly via RUN/STOP.

The symbol table will be displayed to the printer in two column


format.

.DIS E

The E option sends only error messages to the printer. It is


really no display
except that messages normally only sent to the screen with
display off are also
sent to the printer. These include (1) the name of any disk
files accessed
during assembly, (2) error messages and (3) the hex object range
at the end.

When disk assembling a large source file or a number of them


together there is
an ever-so-remote possibility that more errors will occur than
can fit on the
screen. Rather than frantically scribbling down filenames and
line numbers as
mistakes go whizzing by, use .DIS E to send everything to the
printer and go
have a coffee.

Again, error messages are sent to the screen even when no


display mode is
used. The only way to avoid seeing them is to either not make
any, or to
not look at your monitor.

.OUT [operand] (C-64 only)

If you are burning an EPROM, outputting to tape or modem, or


perhaps encrypting
your code you might need to use the .OUT command. Beginning on
pass two, POWER
ASSEMBLER JSRs to the address following the .OUT with each byte
of code. This
byte will be in the accumulator. Do what you like with it then
RTS back to
POWER ASSEMBLER and wait for the next.

RAM from 820 to 998 is not used by POWER ASSEMBLER, neither is


the free memory
from 679--767. Memory from $C000 to $CFFF is available and all
of Basic RAM is
at your disposal. If you must use zero page in your routine you
should
probably save and replace any values.

.OUT [operand] (C-128 only)

If you are burning an EPROM, outputting to tape or modem, or


perhaps encrypting
your code you might need to use the .OUT command. Beginning on
pass two, POWER
ASSEMBLER JSRs to the address following the .OUT with each byte
of code. This
byte will be in the accumulator. Do what you like with it then
RTS back to
POWER ASSEMBLER and wait for the next. You should use the .BANK
# command to
tell POWER ASSEMBLER which bank your routine is in if it is not
in bank 15.

The tape buffer ($b00-$bff) is not used by POWER ASSEMBLER. The


RS232 buffers
($c00-$eff) are unused during assembly. Zero page, however, is
used
extensively. If you must use zero page in your routine you can
use the C-128's
re-locatable zero page feature to point to your own while
executing your code.
POWER ASSEMBLER's zero page is actually situated at $fe00.

10 .ORG $B00 ;tape buffer


20 .MEM ;output to memory
30 LDA #$0C ;put zero page at $c00
40 STA $D507 ;z pg pointer register
50 ;do your thing
. ;in here
. ;using your z page
100 LDA #$FE ;point z page to POWER
ASEMBLER's
110 STA $D507 ;at $fe00
129 RTS

It is assumed that the above code executes in a BANK where I/O


registers are
visible. POWER ASSEMBLER will have already SEI disabled
interrupts. Do not
enable them in your routine. Writing to $D509 will reposition
page 1 (the
system stack). If you make use of this, set it back to 1 when
you are done.

.OBJ "FILENAME"

Quotes are optional in enclosing any disk filenames defined


within POWER
ASSEMBLER source unless a drive# is specified (i.e. the name
contains
a colon). They are used here for clarity only.

Use the .OBJ command to "save" ML programs to disk. Files are


not opened until
the output buffer (beneath Kernal ROM) is full on second pass.
Object files
will be closed except during actual output from buffer. Large
file will be
reopened to append. Having files open only when necessary makes
POWER
ASSEMBLER compatible with C-64 FASTLOAD cartridges (which must
kill open
files).

If a fatal error occurs on pass one or execution is halted via


RUN/STOP there
will be no empty or unclosed file to have to deal with as is the
case with some
assemblers. If execution is aborted during pass two after
output has begun,
due to some fatal error or user intervention, the file is always
first closed.

The current program counter is sent as the file header;


therefore, an .ORG
[address] command will usually directly precede an .OBJ "OBJECT-
FILE" program
marker. The header address composes the first two bytes of the
actual disk
file and tells the Basic operating system where to put the code
when it is
LOAD'ed ,8,1 into memory (C-128 only, use BLOAD'ed).

Any number of OBJect files may be created during a single


assembly. Each time
POWER ASSEMBLER encounters a new .OBJ "MYPROGRAM" the last is
closed before the
new one is opened. Of course it will have a different name.

If the output device is not to be device 8 then the .DVO #


command should be
used to select the device number to use. If the drive is not
drive zero use
the filename to set the drive number.

10 .OBJ "0:ZIP" ;create on


drive 0
.
500 .OBJ "1:ZANG" ;create on drive 1
.
1000 .DVO 9: .OBJ "0:ZOWIE" ;use device 9,
drive 0

Again, multiple object files which will later be LOAD'ed all


over memory, but
assembled as one job and sharing a common symbol table, are
possible.

.BAS "0:FILENAME"

The command allows for the automatic merging of Basic and


assembler source.
These programs can be LOAD'ed, SAV'ed and RUN just like Basic
ones.

After the .BAS command. write ordinary Basic program source with
one major
enhancement. In this Basic the SYS, PEEK and POKE command will
be able to
refer to symbol tables values, as defined in the assembler
portion which will
follow, by name. These symbol names must appear in quotes. The
Basic part may
be quite short:

100 .BAS "0:YOU-NAME-IT"


110 SYS"MYCODE"
120 END
130 MYCODE =*
140 ;brilliant assembler
source....

An END on a line by itself must follow the Basic, telling POWER


ASSEMBLER that
the source has changed. The END line will not appear as part of
the final
object program. If the above source was assembled and the
created program
"MYCODE" was LOAD'ed and listed, it would look like this:

110 SYS 2063 (C-64 only) 110 SYS 7183 (C-


128 only)

And that is it! On top of this SYS 2063 (or SYS 7183 for C-128)
invisible to
the listing, would be the ML code. Trying to modify the above
program without
re-assembling is not advisable. For instance, adding a line;

100 PRINT "MY NAME IS FRED, I HAVE NO HEAD"

... would list okay, but crash when run. The code which had
been at 2063 or
7183 would now been further up.

.BAS "NAME" is somewhat like .OBJ "NAME" in that it causes a


program
file to be written to disk. There are, however, two
differences.

1. Do not use the .ORG command to initialize the program


counter for .BAS
created files. POWER ASSEMBLER will automatically set it to
$801 on the
C-64 and $C01 on the C-128, which is where Basic programs
begin.
( ^ something wrong here, leading 1 is missing, Wile E.
Coyote)
2. Do not try to use .BAS more than once in your source. Only
one hybrid
program can be created at a time.
Here is another exceedingly example of an ML -- Basic source
program. Notice
how completely Basic is able to access the ML symbol table.

10 SYS 999 ;calls POWER ASSEMBLER


20 .BAS "0:SIMPLE" ;name of Basic prg
30 POKE"CHARACTER",ASC("X")
40 SYS"PRINT'X'ROUTINE"
50 END
60 ;****now the assembler part****
70 CHARACTER =*: .ORG *=1

- Page 24 -

80 PRINT'X'ROUTINE LDA CHARACTER


90 JMP $FFD2

If you use POWER ASSEMBLER to assemble this, then DLOAD "SIMPLE"


and RUN it,
you would see an X printed on your screen (be still my heart).

Basic may even use the symbol table names in expressions.


Anywhere the actual
value is needed the quoted symbol may be used. Lines like;

100 FOR N=0 TO PEEK("TABLE'LENGTH")


110 POKE"TABLE"+N,PEEK("DATA"+N)
120 NEXT:REM MOVE DATA TO TABLE

... could be used. If any of the symbol names referenced were


not defined in
the assembler source an UNDEFINED SYMBOL error would ensue.

.LINK "0:NEXTSOURCEFILE"

This is one way of chaining a number of source files together.


The .LINK
command will appear at the end of each but the last source file
in the chain.
It causes POWER ASSEMBLER to LOAD the source file specified into
memory before
continuing the assembly. The last program in your chain will
end with a .LOOP
"0:FIRSTSOURCEFILE" line. The names used will be of course be
the names you
have DSAVE'd your files to disk under.
If you make use of the "+" forward referencing temporary label
then the
largest source file should be the first in the chain. The
address stack for
these labels builds up from the end of the program in memory
when assembly
begins. If a longer source file is .LINKed in it will overwrite
these
addresses spoiling everything.

.LOOP "0:FIRST-FILE"

This tells POWER ASSEMBLER that there are no more files in the
LINKed chain.
The file name specified by .LOOP will be the first file in the
chain. On pass
one this file will be loaded into memory and pass two begun. On
pass two the
.LOOP command signals the end. Any output files are closed and
control is
returned to Basic. The source program ending with the .LOOP
instruction will
be sitting in Basic's program buffer.

For the Commodore 64 only, .LINK....LOOP memory chaining allows


you to take
full advantage of FASTDISK utilities which intercept Basic's
LOAD vector.
Again though, make sure that the largest source file comes first
if you are
using the forward "+" temporary symbols.

.FILE "0:SAVED-SOURCEFILE"

This is a very convenient way of chaining source files together.


The .FILE
command tells POWER ASSEMBLER to assemble the specified source
file directly
from disk then to return to the next line of the in memory
source and
continue. A very short program containing nothing but .FILE
statements can be
used to assemble multiple giant source programs as one. It
might look like
this:
SYS 999 (SYS 4000 for C-128) ;call POWER
ASSEMBLER
10 .FILE "0:INITIALIZE"
20 .FILE "0:PROCESS"
30 .FILE "0:THESEROUTINES"
40 .FILE "0:THOSEROUTINES"
50 .FILE "0:MOREROUTINES"
60 .FILE "0:MESSAGES"

With this type of setup the assembly process and file chain can
be very
easily modified. To add a source file called "PROTECTION" to
the chain would
be as simple as adding a line 70 .FILE "0:PROTECTION" to the
rest before
running (assembling). Changing the order in which the files are
assembled
would involve merely switching a few line numbers. To save the
symbol table
part way through would entail only inserting the line 15 .SST
"0:INIT-SYMS"
for example. Alternating display options, I/O device numbers
and assembly
modes (e.g. .FAS or .MEM) would also not involve loading,
modifying and
resaving large source files.

It is not even necessary to save the changes made to the memory-


based file
chaining program before assembly; it will still be there
afterwards.

The relative sizes of .FILEd source programs are unimportant


because they are
read directly from disk. The temporary label address stack will
always be
completely safe. The amount of memory available for .MEM output
and symbol
tables is also maximized by this method of source file chaining.

Large source files and even .LINKed source files may contain
.FILE statements.
Control will always return to the next line after the specified
source has
been assembled in from disk. .FILE assembled source, however,
may not contain
its own .FILE or .LINK commands. This type of nesting would
lead to great
unhappiness were POWER ASSEMBLER to attempt it. The .LINK and
.LOOP commands
are ignored in .FILE assembled source.

.SEQ "0:ASCIISRCFILE"

This works exactly like .FILE except that the source is expected
in ASCII
format, not Basic. This makes POWER ASSEMBLER highly compatible
with almost
any editor or word processor.

With POWER ASSEMBLER you can combine types to produce a single,


ML object
program using (1) in-memory Basic type source created on the C-
64 or C-128
Basic editor, (2) .FILE'ing in SAVE'd source programs and (3)
.SEQ'ing in
source created on the ASCII editor of your choice.

Source files specified in the .SEQ instruction must have the


following
attributes:

1. They will be in pure ASCII form. No screen code or


tokenization.

2. Lines will not be numbered. POWER ASSEMBLER will


attach a sequence number to each line in a file for
display purposes.

3. A carriage return, i.e. CHR$(13), will be the last


character of each line, and at least two of these will
be at the end of each source file.

4. Colons may still be used to link statements on a


line, but no line should be longer than 255 characters

A large source program in this format might possibly assemble


slightly faster
than if it were in Basic source format. It would not be
necessary for POWER
ASSEMBLER to un-crunch tokens or to read in the four bytes of
overhead
associated with link and line number.
.TOP

In some situations it may be desirable to save only a portion of


the symbols
defined or used in a program. The .TOP command lowers the
symbol table top in
so far as any future .SST is concerned, permitting the saving of
intermittent
symbols only. Symbols defined prior to .TOP, although
accessible to the
program in every other way, will not be saved. Unless one has a
penchant for
empty files one should not attempt to .SST immediately following
.TOP. Here
is probably the more practical application of .TOP:

100 .LST "0:HUGE-SYMTAB"


120 .TOP ;will not effect coding
130 ;now a whole bunch
. ;of neat stuff using the
. ;loaded symbol table
500 .SST "0:NEW-SYMS" ;saves only the newly
defined symbols

Numerous, completely exclusive symbol tables can be saved from


within one
assembly list just as numerous separate object files can be
created.

With .TOP it is possible for two programs to access each other's


symbol tables
without re-definition problems or phase errors caused by late
zero page
assignments. If .TOP is not used then every symbol defined
prior to the .SST
command will be saved.

.SST "0:SYMBOL-TABLE-NAME"; save symbol table

This can be used to save all or portions of a symbol table. If


the above were
the last line of your source program all of its symbols might be
saved to a
file under the name you used.
Use .SST to create a file of Kernal routines, important register
addresses and
memory locations for use in all your programs. There are clear
advantages to
this.

1. You don't have to type them all in every time you


start something new.

2. Your source files will be shorter without the


numerous assignment statements.

3. Certain consistency and uniformity will be lent to


your source programs. The names of key symbols will
not change from one project to the next.

.SST and .LST provide an excellent way of modifying large ML


programs without
having to re-assemble the entire system each time changes are to
be tested.

Imagine that you have developed a sophisticated word processor


or game or
assembler or something and you now wish to add to it a fancy new
feature. You
know perfectly well you're not going to get it right the first,
second, third
or maybe even the twentieth time. We're talking tricky here.
The thought of
re-assembling the fifteen of so chained files involved with each
new try is
not the most fun thing that you could possibly ever imagine.
you'd probably
spend more time waiting than working. Try this:

1. Put a call to the new routine in the main source and


also assign therein an address to it. This will not be
the final destination, just a free, safe place to work
on it. So somewhere in the main source will be a line
like 500 JSR NEW'FEATURE, and a line like 50
NEW'FEATURE = 50000.

2. Now assemble the whole thing. Be sure to create an


object file via an .OBJ "GREAT-BIG-ML-PGM" and to save
its symbol table at the end via .SST "ITS-SYMBOLS".

3. You should have then a BLOAD'able version of your


program and a copy of its symbol table, i.e. the
addresses and values of all of the routines, and
variables contained in or used by it.

4. Write the new routine. You don't have to get it


perfect right off. It should .ORG originate at the
address you told the main program it would. The first
thing this source will do is load in the symbol table
of the main program with .LST "ITS-SYMBOLS" line.

.LST "0:ITS-SYMBOLS"

This will load in the specified symbol table for use by your
program. ...
carrying on with our example:

5. BLOAD the main program in then assemble the new


module (routine) right into memory using .MEM. This
new module will have complete access to the main one as
if they had been assembled together. Any routine in
the large one will be made call-able by name from the
new one. Any flags, registers or variables in the main
one are also at the disposal of the new part.

6. So try the whole thing out. Run it. Crash-boom, or


yuk, or whatever. It didn't work but that's okay
because you planned it that way. At worst you'll have
to re-boot POWER ASSEMBLER, LOAD you ML code and the
source for your test program before you can try it
again. At best you won't have to do any of that before
you begin making corrections.

7. Sooner or later you'll get it perfect. Believe. Now


remove the line from the main source which assigned the
test address to the routine and either .FILE or .LINK
assemble them together the way you would have like to do
in the first place if life wasn't so full of mistakes.

If you .LST symbols in before you define any of your own (i.e.
first), re-
definitions will trigger error messages when they occur.
Duplicates will not
be loaded in. In the case of labels this is usually convenient
since it is
the last occurrence of a label that you are probably interested
in anyway.
.BYTE [onebytevalues,...,...]

This is used to place one byte value(s) into your code. Here
are a few
examples of .BYTE:

10 .BYTE 0,2,4,8,16,32,64,128 ;powers of 2


20 .BYTE <1000,2000,3000 ;low bytes only
30 .BYTE >SUB1,SUB2,SUB3 ;high bytes only
40 .BYTE "a","b","c"+128 ;ASCII values

Notice that commas separate the operands and that no spaces are
included.
Also notice how the < and > work: they affect the entire
string of values
and should not be repeated. This will make setting up high and
low byte
address tables more convenient.

.WORD [twobytevalues,...,...]

Use .WORD to set up address tables. All values following will


be treated as
two byte values. This means that 10 .WORD $FF,$FF would have
the same
effect as 10 .BYTE 0,$FF,0,$FF.

Here are some examples of .WORD:

10 .WORD DESTINATION-1 ;setup rts jmp


20 .WORD 12*4096,$c000+OFFSET ;expressions

It would be pointless to use > or < in conjunction with word


data since the
resulting values would never exceed one byte.

.ASC "***ASCII TEXT***"

Use the .ASC command followed by any quote-mode-typeable string


of ASCII
characters you wish to be placed in your code.

The opening quote is not optional. Omitting it will result in a


"QUOTE
EXPECTED" error message.
A closing quote is optional unless of course you wish to include
some banks at
the end of your text entry.

For the Commodore 64, the following is a staple routine for


printing messages
in ML. It is almost always used in conjunction with the .ASC
pseudo-op.

50 SYS 999 ;again


70 .ORG 820 ;sys 820 after
80 .MEM
90 PTR =251
95 PRINT =$FFD2 ;Kernal ROM
100 JSR WRITE ;print message routine
110 .ASC "***HI MOM***":.BYTE 13,0
120 RTS
130 WRITE =*
140 LDY #0
150 PLA:STA PTR+1 ;message address-1 on stack
160 PLA:STA PTR
170 - INC PTR
180 BNE + ;to line 200
190 INC PTR+1
200 + LDA (PTR),Y
210 BEQ + ;to line 240
220 JSR PRINT
230 BNE - ;jmp to line 170
240 + LDA PTR+1:PHA ;restore the rts address past
zero
250 LDA PTR:PHA
260 RTS

The preceding WRITE routine works much the way Basic's PRINT
command does in
that the following text is printed. A zero marks the end of
WRITE text. If
you examine this routine you will see how the 6510 stack works
during JSR and
RTS executions.

The C-128 only, has a new Kernal routine to print out strings of
text. This
text cannot be longer that 255 characters and must be terminated
by a null
(zero).
Here is as example of this routine used in conjunction with the
.ASC pseudo-
op:

50 SYS 4000 ;again


70 .ORG $B00 ;sys 2816 after
80 .MEM
90 FOREVER =*
100 JSR $FF7D ;kernal primm
routine
120 .ASC "HI MOM":.BYTE 13,0
130 - JSR $FFE4 ;kernal get keystroke]
140 BEQ - ;loop if no key
150 JSR $FF7D ;primm routine
again
160 .ASC "HI MOM":.BYTE 13,0
170 JMP FOREVER

NOTE: don't try JMPing to $FF7D

.SCR "***SCREEN CODE VALUES***"

.SCReen works the same as .ASC except the following text is


converted to its
screen code equivalent. That is the value you would use to poke
the character
directly to the screen.

The line 100 .SCR "A" would code the value 1 whereas the
line 100 .ASC
"A" would code the value 65. This should make like a little
easier for
programmers who maintain menu lines and display by "poking"
character values
directly to the screen.

.FAS

For the Commodore 64, .FASt switches off the screen. This
should increase in-
memory assembly speed approximately by about 20 percent.

For the Commodore 128, .FASt switches the micro processor into
the 2mhz mode
and turns off the video. This should at least double in-memory
assembly
speed.
There is no danger of missing any important messages by doing
this. If any
errors are encountered the screen is turned back on for you. It
would be
pointless, a waste of time to use .FASt and .DISplay together.

.BURST (C-128 only)

the .BURST command is for disk based (i.e. .SEQ and .FILE)
assembly using the
1571. When .BURST is used source files, instead of being read
via kernal
routines a line at a time from disk, will be burst loaded into
memory at the
bottom of bank 1. From here they will be accessed RAM DISK
fashion by the
assembler. This more than doubles the speed of disk based
operation making
this almost as fast as .LINK/>LOOP load chaining which is always
burst driven.

If you are using the .FILE or .SEQ commands have a 1571 and can
spare low
memory in bank 1 during assembly then .BURST is highly
recommended. It need
only be used once at the beginning of your source. If you are
using more than
one drive and only one is a 1571 the others will not be
affected.

.PSU

.PSeUdo allows for the use of mnemonics like LAX, DCM, INS, SKB,
AXS, .etc. to
code non-standard opcode. The reliability of some of these are
somewhat moot.
I would suggest you execute them with interrupts disabled. Some
very widely
distributed commercial programs make extensive use of non-
standard opcode both
to conserve space and to confuse disassembly.

Like most inherent (operand-less) pseudos it is a toggle


command. Using it
for a second time will turn the feature off. You will probably
want it on
only for those portions of code which make use of non-standard
opcode. as
with standard mnemonics like LDA and INX you will have to avoid
giving symbols
in your program the same names as non-standard mnemonics when
.PSU is enabled.

See the table appended to this manual for a full listing and
brief
descriptions of the pseudo mnemonics which POWER ASSEMBLER
recognizes.

.IF [operand]; conditional assembly

When the expression following an .IF is not equal to zero then


assembly will
proceed until an .ELSE is encountered, then skip to an .IFE line
marking the
end of conditional assembly or another .ELSE.

When the value following .IF equals zero then POWER ASSEMBLER
will ignore
everything until an .ELSE or an .IFE is found. Assembly will
resume there.

.ELSE

This is where assembly will pick up when the value following the
previous .IF
was zero. If a second (third, fourth...) .ELSE follows,
assembly will
alternate between them.

20 .IF FLAG
30 : LDA "A":JSR $FFD2 ;Kernal print
40 .ELSE
50 : LDA "1":JSR $FFD2
60 .ELSE
70 : LDA "B":JSR $FFD2
80 .ELSE
90 : LDA "2":JSR $FFD2
100 .ELSE
110 : LDA "C":JSR $FFD2
120 .ELSE
130 : LDA "3":JSR $FFD2
140 .IFE ;end of conditional assembly
110 : LDA "!":JMP $FFD2
If flag = 0 in the above then the assembled code would print
"123",
otherwise the code would print "ABC!"

Another more useful application of .IFE .ELSE conditional


assembly would be to
protect your indirect jumps from accidentally falling on page
boundaries.

10 JMP (INDIRECT) ;to destination


.
.
500 .IF <*=1 ;check page boundary
510 INDIRECT =* ;not page boundary
520 .WORD DESTINATION
530 .ELSE
540 NOP ;pass page boundary
550 INDIRECT =*
560 .WORD DESTINATION
570 .IFE ;end of conditional assembly

No re-definition of a symbol error would occur during the above


assembly.
Only the .IFE or .ELSE portion of the actual source would be
assembled.
This would depend on whether or not <*+1 (the low byte of the
program counter
was +1) was zero.

If you are using a number of .ELSEs you might want to take


advantage of the
fact that pseudo-ops can be extended and tack some alternating
character on
telling you which condition belongs to, i.e.

.ELSE1,...ELSE0,...ELSE1,...Else0, etc.

Note: Don't try JMPing to $FF7D. Never stick a label in front


of an .ELSE
or an .IFE. POWER ASSEMBLER will look no further and miss the
switch.

.IFE

This ends conditional assembly. Everything following is


assembled.
MACRO-OPS

Three of the most common activities in machine language involve


(1) comparing
pointers, (2) filling, i.e. erasing, ranges of memory, and (3)
moving ranges
of memory. POWER ASSEMBLER has provided macro-ops to make short
work of these
traditionals while enhancing the readability and reducing the
size of your
source.

All require operands which are expected to be in the form of


zero page
pointers. While this may seem a trifle inconvenient at first
glance it makes
the resultant code much more flexible.

For instance, you do not want to have to use the .MOVE macro
every time you
want to relocate some range of memory. It would be much more
efficient to use
it once as a subroutine (i.e. preceded by a label and followed
by a RTS) and a
JSR to it whit its three pointers set to your specific needs on
each
particular occasion. This would not of course be possible if
this macro-op
took constraints as operands.

Another advantage to taking pointers is that you can choose


precisely what
addresses will be used by the generated code. Only the pointers
you specify
and the processor's registers are manipulated. Back in the
joyous awareness
that your data and variables will always be safe when macro
coding; trip on to
the absolute power you exercise over memory usage when employing
POWER
ASSEMBLER's macros.

I have come into contact with a number of very proficient and


talented,
professional assembly language programmers over the last several
years and not
one has confessed to having ever used macros. I believe this is
because by
their very nature ML programmers enjoy the exquisite control
they have over
their machines and do not wish to relinquish this to something
"standard".
Perfection is in order. Custom subroutines seem to hold more
appeal than
built-in, space-wasting, other-people's macros.

However, the few that have been selected for POWER ASSEMBLER are
universally
applicable. To overcome your apprehensions about using them I
would suggest
that you UNASM to disassemble the code generated by each. You
will find it
totally re-locatable and non-self-modifying as well as fast,
efficient and
correct.

.TEST ZEROPTR1,ZEROPTR2

In situations where you wish to compare two addresses designated


indirectly by
zero page pointers you could use the .TEST macro-op. The carry
returns clear
if the first was pointing to a lower address, otherwise it will
be set. The Z
flag is set if they both point to the same address.

.DUMP BEGINPTR,ENDPTR

This dumps the contents of the accumulator to a range of memory.


It might be
used quite effectively to clear buffers or hi-res screen areas.
The first
pointer must designate the first address to be filled and the
second pointer
the last. Make sure that they are properly set and that the A
register has
been loaded with the desired value before you use (or call the
subroutine
using) the .DUMP command. In the following exciting
demonstration of it the
40 column screen is filled with "B"s

10 SYS 999 (C-64) SYS 4000 (C-128)


20 .ORG 820:.MEM
30 SCREEN =1024 ;in 40 column mode for C-128
40 LDA <SCREEN:STA TOPPTR
50 LDA >SCREEN:STA TOPPTR+1
60 LDA <SCREEN+999:STA BOTPTR
70 LDA >SCREEN+999:STA BOTPTR+1
80 LDA "B" ;screen code for "B"
90 .DUMP TOPPTR+BOTPTR
100 RTS

.MOVE BEGINPTR,ENDPTR,DESTINATIONPTR

This will generate the code to move the range of memory


specified by the first
two pointers to begin at the address pointed to by the third
pointer. The
range can be moved in either direction any distance without
overwriting
itself. In other words, it does not matter whether the
destination is above
or below the beginning range to be moved or if the distance is
very small.
Memory will still be intact. This macro is used in EDITOR.64 or
LABELGUN (C-
128 only) to shift ranges of source up or down when inserting or
deleting text
and replacing strings with others that are longer or shorter.
Of course the
memory being moved (your source) cannot be corrupted in any way.

Write the following short program to locate in the cassette


buffer.

10 SYS 820 SYS4000 for C-128


20 .ORG 820:.MEM FOR C-64 .ORG $B00:.MEM for C-
128
30 FROMPTR =251 ;safe Basic zero page
40 TOPTR =253
50 DESTPTR =65
60 .MOVE FROMPTR,TOPTR,DESTPTR

Now use UNASMbler to disassemble and examine it. Notice that


only the
pointers you defined and the micro processor's registers are
used. Try
moving some memory around. Convince yourself that .MOVE works
and is safe.
Almost every ML program ever written uses memory moves. Getting
comfortable
with this POWER ASSEMBLER macro can save you time and trouble.

WRITING YOUR OWN COMMANDS

There is space in POWER ASSEMBLER's pseudo-op stack for up to


five new
commands. Each one takes up five bytes of memory. The first
three, which are
currently spaces will be replaced by your own

three-letter command which you will make up all by yourself; the


next two will
be the address-1 of the routine you want to execute when the
assembler comes
across this command.

A symbol table for each version of your assembler is on the


system disk. To
display one use the following technique:

10 SYS 999 (C-64) SYS 4000 (C-128)


20 .DIS ;to display to screen
30 .LST BUDDYSYMS

The symbol you will use to get your commands into the code is
called
"PUT'YOUR'CMDS'HERE"; and nothing could be easier that putting
your command
there. Let us create a new feature for POWER ASSEMBLER called
"fun"; every
time the pseudo-op .FUN is encountered in your source POWER
ASSEMBLER will
inform you that fun is being had; what could be nicer?

10 SYS 999 (C-64) SYS 4000 (C-128)


20 .LST BUDDYSYMS ;so you can use them
30 .ORG PUT'YOUR'CMDS'HERE
40 .MEM ;now we put fun on the stack
50 .ASC "FUN" ;no period here
60 .WOR FUNROUTINE-1 ;address of new useful
routine less one
70 .ORG 832 ($B00 on C-128) ;we'll put it in
the cassette
buffer
80 FUNROUTINE =* ;powerful new command
90 JSR MESSAGE ;POWER ASSEMBLER's print
message subroutine
100 .ASC "WHEEEE! THIS IS FUN."
110 BYTE 13,0 ;must end with zero
120 JMP NEWLINE ;POWER ASSEMBLER takes over

after running this, run the following:

10 SYS 999 (C-64) SYS 4000 (C-128)


20 .FUN

Your "fun" message should have been printed twice: once on each
pass. If it
wasn't then it's your fault. Fix whatever you did wrong, try
again, and be
more careful this time (GRIN).

IMPORTANT ROUTINES AND LOCATIONS

Every source line is de-tokenized into memory at the address of


the BUFFER
symbol. A zero byte marks the end of that line.

If you generate output you should call POWER ASSEMBLER's NEWPC


routine. First
set BYTES to the appropriate value, not greater than three. Put
code
generated at OUTPUT, OUTPUT+1, and OUTPUT+2 as necessary. You
may call NEWPC
more than once (i.e. in a loop) When you are done, a JMP
NEWLINE; passes
control back to POWER ASSEMBLER.

If your command takes and operand you can immediately JSR the
EVALOPERAND
routine. Any valid POWER ASSEMBLER expression will be evaluated
and the value
returned in SUM and SUM+1.

PASSNUM will be 0 on pass 1 and 255 on pass 2.

Try changing the previous .FUN command so you can use .FUN 100
to print the
"fun" message 100 times, but only on pass 1.

Of course there are many, many more routines and flags and
variables that you
will want to become familiar with if you plan to really get
intimate with the
inner workings of your assembler. You have symbol tables. You
have a
powerful unassembler. You have fun.

TEMPORARY SYMBOLS

TEMPORARY LABELS: - / +

The multiplication, division, addition and subtraction


characters each have two possible uses. In expressions, if
"*" is an arithmetic operator then values on either side
are multiplied (e.g. 12*4096); whereas, if it is used as a
symbol it will represent the program counter (e.g. LABEL =*
or *=*+4). This is standard use of "*" and is mentioned
only to illustrate dual functioning of one special
character.

In POWER ASSEMBLER source the "+", the "-" and the "/" also
serve two purposes. In addition to their standard
application in arithmetic, they may be used as temporary
labels. Many ML programmers don't like having to think up
symbol names for numerous, routine, short branches. This is
especially so in very long programs after all variations of
the labels SKIP and LOOP and BACK and AHEAD and OVER and so
on.... and so on.... have been exhausted. Objections to
using these often random symbols are based on the following:

1. Time and effort are wasted in deciding on their names


and typing them in, each at least twice.

2. They have a tendency to camouflage more meaningful


symbols, making it harder to visualize what is happening.

3. Symbol tables become unnecessarily large, wasting memory


and slowing things down.

Judicious use of POWER ASSEMBLER's three temporary flags


smartly overcome all of these difficulties.

TEMPORARY BACKWARD REFERENCING

When the "-" is used as a symbolic operand, the last


occurrence of it as a label is referred to. The command BNE
- will code a conditional branch back to the last line
flagged with a "-" character. Here is how it might be used
in a simple time delay routine:

100 WAIT =* ;name of subroutine


110 LDX #0 ;initialize x and y
120 LDY #0
130 - DEX
140 BNE - ;loop back until x=0
150 DEY
160 BNE - ;same for y
170 RTS

Up to three minus signs may be used together as a symbol (e.g.


BCC - - -) to
refer back as far as the third last "-" flagged line; only the
last three are
remembered. The minus sign may be used as a label again and
again in your
source without re-definition errors. You must be careful that
when you use "-
" characters symbolically that the line on which the referenced
one has
occurred as a label is the one you will want to access (e.g.
branch to). Any
"-" markers prior to the third one are inaccessible.

TEMPORARY FORWARD REFERENCING

The plus sign, as you may have guessed already, works in just
the opposite
way. That is, BNE + would code a conditional branch to the very
next
occurrence of the "+" flag. Here is how one might use it to
increment a
pointer.

10 INC PTR ;the low byte


20 BNE +
30 INC PTR+1 ;the high byte
40 + RTS

A symbol could have been used instead of "+", but what a bother,
a mess and a
waste of space.

There is no limit to how far forward the next "+" flag may be or
how far back
to the last "-" flagged lines may be. JMP - - or JMP ++ are
valid too.
Within there scope of three, these temporary flags may be dealt
with just like
any other symbol. Still, all subroutines and data should be
given meaningful
labels even if you could get away with a "+" or "-" temp.

The next three "+" flagged lines may be referenced at any point
by using 1 to
3 "+"'s (e.g. BEQ +, BEQ ++, or BEQ +++) as a symbol just as any
of the last
three "-" flagged lines may be accessed by using 1 to 3 "-"'s.

Don't let temporary labels permit you to become un-imaginative.


Restrict
their use to short, redundant branches.

FORWARD OR BACKWARD

When the "/" character is used as a label it serves as both "+"


and "-",
either of which can be used to reference it. In effect it is as
though the
"/" flagged line had both "+" and "-" as a label on it. The JMP
- statement
would actually code a jump back to either the very last "-" or
"/" flagged
line. A JMP + would code a jump forward to the very next "/" or
"+" label
position. In the next example both conditional branches target
the RTS in the
middle.

10 BEQ +
20 LDA #0 ;or whatever
30 / RTS ;destination of both
branches
40 DEX ;or whatever
50 BEQ -

TEMPORARY SYMBOL MANAGEMENT

The backward referenced "-" label is handled only on pass two.


Only three
addresses need ever be "remembered" by the assembler with regard
to it. The
forward referenced "+" can not be dealt with so easily. A table
of all of its
occurrences as a flag is created on pass one which is then
accessed on pass
two. This table is separate from the normal symbol table and
contains only
addresses. It builds up from the end of your source.

If you are using the memory based

.LINK "NEXTFILE". . . .
.LOOP "FIRSTFILE"

system to chain source files together and you have made use of
any temporary,
forward "+" references you should make sure that the largest
file in the chain
comes first; otherwise, a larger file when loaded into memory
will clash with
the "+" address table. Consider disk based .FILE "ANYFILE"
chaining as an
excellent alternative to memory based chaining in this
situation.

LABELGUN (for C=128 only)

The C=128 screen editor is an excellent one. With it you can


redefine keys,
freeze scrolling, delete ranges, renumber, auto line number and
much more.

About the only thing missing when it comes to developing a large


program is
sophisticated string handling. To be able to seek our
occurrences of and
possibly modify a given symbol (e.g. string of characters)
instantly
throughout an entire source program is so useful as to be almost
essential.

With Bud installed you have this ability. So never strain your
eyes scrolling
through screen after screen of source looking for that elusive
BUG subroutine.
Just enter the following command:

L,BUG
Every line in your program with the word BUG on it will be
listed for you.
Change every occurrence of BUG to CRITTER like this:

C,BUG,CRITTER

In the above case words like DEBUG, BUGEYES and BUGGY would also
be changed.
This may not be what you had in mind.

To have only whole words considered you would have to use a


period in place of
the first comma.

C.X,EXITROUTINE

This would not ruin all your words containing X's. Only if X
occurred as a
whole symbol would it be changed to EXITROUTINE. All those LDX,
INX, STX and
TXA commands would go unmolested.

Sometimes the string you seek will contain a Basic keyword but
not have been
tokenized by the Basic editor. This may be due to its following
a DATA or REM
string on a line or because it exists between quotes. In this
situation it is
possible that the string you target, even though it looks the
same as in your
program, will not be found by Labelgun.

If you have doubts or if you are after a string you know is in


quotes, do
this:

L,"ENDING
or
C"STOPTHIS,STOPTHAT

You may put a period at the end of any Labelgun command to add
extra spaces to
the end of a string;

L,MODULE .
... would find any subroutines whose names ended in MODULE, but
probably not
calls to them.

You will find these string handling commands virtually


indispensable. Use
them to update label names that have changed their meaning.
Quickly locate
routines by name. If you have source for the C=64 around that
you would like
to convert to the C=128, Labelgun can help.

Source written on the C=64 editor can be assembled by BUD, but


source written
on the C=128 might not work with a C=64 basic environment
assembler because of
the much larger set of tokens used on the C=128.

TWO ENVIRONMENT EDITOR

Buddy-System 64 actually encompasses two machine language


development
environments. It is the POWER ASSEMBLER half which has been
discussed so far.
Although POWER ASSEMBLER is able to assemble ASCII files from
disk such as can
be written on EDITOR.64 (or EDITOR.128) or on most word
processors, its memory
based source must be in Basic format. Basic source, unlike pure
ASCII text is
actually a linked list: each line starts with a two byte
pointer to the next.
Following this pointer are two more bytes representing the line
number. Next
comes the actual text with all Basic keywords tokenized (i.e.
crunched). At
the end of each line is a zero byte.

While this format does very well for Basic it may not be the
most efficient
for assembly language. However, many programmers are
comfortable with the
Basic editor and source format, have acquired

utilities such as POWER-64 which greatly extend its


capabilities, and have no
desire to switch to a different system. If you are one of these
people then
stay with POWER ASSEMBLER; it was made for you.

LOADING EBUD

On the disk is another version of the assembler which can be


invoked by
entering LOAD "EBUD",8 <RETURN> and then RUN. This will
result in the
editor compatible version of your assembler. ED-
BUDDY.64 or ED-
BUDDY.128 and the ASCII editor itself. EDITOR.64 or EDITOR.128,
being loaded
into memory. You will not return immediately to Basic as in the
case when
booting with POWER ASSEMBLER.

EDITOR.64 (or EDITOR.128)

Printed at the top of your screen will be COLUMN:1 LINE:1; a


solid cursor
will be in the upper left corner of the now clear text area.
Welcome to our
editor!

MEMORY USAGE

EDITOR.64 commandeers the highest 2k of Basic RAM and sets the


top of Basic to
a point below itself. Thus it is safe from Basic activities and
any utilities
(such as UNASM) which are sensitive to Basic's pointers.
Although 2k is quite
small by some standards the editor, as you will soon see, is no
weakling.

REPLACES BASIC EDITOR (C=128 only)

EDITOR.128 effectively replaces the Basic editor insofar as the


EBUD version
of your assembler is concerned. Basic is still completely at
your disposal,
but you will not be using its line number oriented editor to
write your source
on or assemble your source from. EDITOR.128 is short, as
editors go, and easy
to learn to use. Nonetheless, a number of very useful features
have been
built into it.

4-WAY SCROLLING and PAGING

Begin typing. When you come to the right of the screen instead
of wrapping to
the next line as you would in Basic the screen window scrolls
with you to the
right. Lines may be up to 250 characters long. With text in
memory you can
scroll up, down, left and right by using the cursor keys. You
may also page
up and down with the f3/f4 key and page left and right with the
f5/f6 key.
This allows you to fillip through your source very quickly. The
CLR HOME key
can be used to position you immediately to the top or bottom of
your source.

SIMPLE INSERT and DELETE


The INST DEL key works pretty much the way it does in Basic to
add or remove
text one character at a time. The f1/f2 key can be used to
delete the
remainder of a line or to insert a new line. This key can also
be used to
split and join lines.

CUT and PASTE

To delete an entire range of text position the cursor at one end


of the text
you wish to remove, then press <LOGO> S to set range. You will
see [RNG]
appear at the left of your status line next to COLUMN: Now move
to the other
end of the range of text to cut. It does not matter how far or
near this is.
Press <LOGO> D and this text will all disappear. Pressing
<LOGO> S twice in
a row takes you out of the Set Range mode.

Once you've cut a range of text you may paste (insert) it back
anywhere, as
often as you like and even move blocks of source between files.
To insert the
range simply position the cursor to where you would like it to
begin and press
<LOGO> T for Text and presto -- there it is again.

You may go back and forth from Basic, clear (new) source and
load files
without disturbing cut text so that routines can easily be moved
from one file
to another.

FIND and REPLACE

To find occurrences of any word or words in your source, press


<LOGO> F for
Find. This will temporarily position you on the status line.
Following the
"OLD:" prompt, enter the string of characters you would like to
find. When
you are done press <RETURN> to get back to where you where in
your text. Move
to where you would like the search to begin (to search all your
source press
<SHFT> CLR HOME to go to the top) and then the f7 key. Every
time you press
the f7 your cursor will move to the next occurrence of the
target string you
entered until you reach the bottom of your source.

If you would like this target string replaced in your source


with something
else, press <LOGO> R for Replace. Again you will move to the
status line
where following the "NEW:" you will type in whatever you would
like to change
the "OLD:" stuff to.

You may proceed in two ways: (1) If you press f7 only the next
occurrence of
the old will be replaced with the new. (2) If you press f8 then
all
occurrences following will be changed and you will finish at the
end of your
source.

LOADING and SAVING TEXT


Text may be kept as either sequential or program files. ASCII
Sequential
files can be disk assembled by either version of your assembler
via SEQ
FILENAME. Program files can be .LINK/.LOOP load chain assembled
(i.e.
assembled directly from memory) by EBUD only. The C=64 can also
take
advantage of FAST LOAD/SAVE cartridges for the 1541.

PROGRAM FILES

To save your source as a program file simply press RUN STOP to


return to
Basic, then enter SAVE "MY-STUFF",8 (C=64) or DSAVE "MYSTUFF"
(C=128) just the
way you would any Basic program. To load this file back in
tomorrow you would
enter LOAD "MY-STUFF",8 (C=64) or DLOAD "MYSTUFF",8 (C=128).

TO & FROM BASIC

To return to the editor from Basic use the ED command. If you


loaded
something new it will be there; otherwise whatever you were
working on will
still be waiting, unless of course you gave the NEW command to
Basic in which
case your source will have been cleared.

SEQUENTIAL FILES

To save and load sequential files it is not necessary to leave


the editor. To
save a file as a SEQ file begin by pressing <LOGO> P. Then,
following the
"PUT:" prompt enter the name you would like to give your source
on disk.

To load a SEQ file press <LOGO> G and following the "GET:"


prompt type in the
file-name and press RETURN. The file will be loaded in,
beginning at the
position of the cursor. This can be used to join two files.

ASSEMBLING
To assemble editor source, first press RUN STOP to return to
Basic. Then
enter the AS command. The source in the editor will be
assembled directly
from memory. It is not necessary to save it first (unless you
plan to kill
the machine). You may then issue the appropriate SYS command to
test the code
(hopefully) return to your still intact source via the ED
command afterwards.
Complete memory based operation is supported. Either EBUD and
EDITOR.64 and
EDITOR.128 you can also disk assemble, file chain, load and save
symbol
tables, create object files, and indeed do all of the things
POWER ASSEMBLER
does with the Basic editor.

SOMETHING TO TRY (C=64 only)

The source for the BUDDY-UNASMBLER is on disk in sequential


format. Either
version of Buddy will assemble it from disk to memory at 50000.
To create a
program file of ML code from this source you will have to use
EBUD. After
loading and running EBUD use <LOGO> G to get UNASM-SOURCE into
memory.

Change the .MEM on line 2 to .OBJ UNASM.OBJ then RUN STOP to


Basic. Enter the
AS command to assemble UNASM-SOURCE creating UNASM.OBJ which can
now be
LOADed...,8,1. If you would like a version to load somewhere
else in memory
change the .ORG 50000 line.

Now you've got a really powerful and fast memory based


unassembler that will
convert code to true POWER ASSEMBLER source.

CONVERTING SOURCE TO ASCII

On the disk is a program called MAKE-ASCII that will create an


ASCII file
completely compatible with the EBUD system from any Basic
format source file
such as created by UNASM and used by POWER ASSEMBLER.

LOAD and RUN MAKE-ASCII

Enter the name of the Basic file followed by the name of the
ASCII file you
would like to create. It will be done. You can get this file
into EDITOR.64
or EDITOR.128 using the <LOGO> G command to load a sequential
file. You will
probably see that this new ASCII file consumes less space on
disk than the
original Basic one did.

EDITOR COMMAND SUMMARY

f1 delete rest of line


f2 insert new line
f3 page up
f4 page down
f5 page right
f6 page left
f7 find/replace next occurrence
f8 replace all occurrences
CLR top of text
HOME bottom of text
<LOGO> S start set range
<LOGO> D delete range
<LOGO> T insert range
<LOGO> F set string to find
<LOGO> R set string to replace
<LOGO> P save (put) seq file
<LOGO> G get (load) seq file
RUN STOP go to Basic
ED go to editor
AS assemble source in editor

ZBUDDY (for Commodore 128 only)

The following is intended to assist the more advanced ML


programmer in making
use of the C=128's Z/80 micro processor via the very powerful
cross assembler,
ZBUDDY. ZBUDDY lets you use standard Z/80 mnemonics (see
"TEST.ZMNE" program
on disk) and BUDDY's expression syntax and rich body of pseudo-
ops (see those
sections of this manual) to create ML code for the 128's "other"
micro
processor. Symbol tables for these assemblers are fully
compatible (i.e.
symbols can be .SST saved on one and .LST loaded by another) so
that complex
programs involving both the Z/80 and the 8500 can be written.

PROGRAMMING THE Z/80 (C=128 only)

The C=128 is a two processor system. Inside are the 8500 and a
Z/80. The
Z/80 is one of the most advanced 8 bit processors alive. It,
unlike the 8500
which is memory based, is a register based micro processor. It
has two sets
of general purpose registers. Each of these sets contains an
accumulator, a
status register and a six, 8 bit, general purpose registers.
The second set
can be used for the interrupt flip-flop (IFF) or by the exchange
(EXX) command
to remember and restore register contents. Data registers can
also be paired
for 16 bit addressing and arithmetic. In addition to these
there are four
other 16 bit registers: the PC (program counter), the SP (stack
pointer) and
the (IX) and (IY) (index) registers.

8 BIT INTERNAL REGISTERS (C=128 only)

A A' accumulator
B B' general purpose
C C'
D D'
E E'
H H'
L L'
F F' flag status

16 BIT REGISTER PAIRS (C=128 only)

BC B=hi byte C=lo byte


DE D=hi byte E=lo byte
HL H=hi byte L=lo byte

TRUE 16 BIT REGISTERS (C=128 only)

IX index
IY index
SP stack pointer
PC program counter

COMMANDS (C=128 only)

The Z/80 recognizes several times as many instructions as the


8500; some
therefore require more than one byte of opcode. These commands
can be
functionally divided into 13 groups.

1. THE EIGHT BIT LOAD GROUP (C=128 ONLY)

The Z/80 assembler load instruction, LD, might more aptly be


named MOVE.
There is no store instruction. Every LD will be followed by two
operands
delimited by commas. The first operand represents the
destination and the
second the source, so that the instruction LD ($C000),A means
store the
contents of A at $C000 whereas LD A,($C000) would mean load A
from $C000. In
Z/80 mnemonics, parenthesis define memory location; otherwise an
immediate
value is assumed.

2. THE SIXTEEN BIT LOAD GROUP (C=128 ONLY)

This includes all the commands which move two byte values either
between
registers or between registers and addresses. Included here are
the PUSH and
POP instructions which is handy since addresses are what stacks
are mainly
for.

3. THE EXCHANGE GROUP (C=128 only)

Register contents can be swapped with the secondary set or


within the primary
set. There's nothing like this on the 8500 although we often
wish there was.

4. THE BLOCK TRANSFER GROUP (C=128 only)

Set a few register pairs and use one of these to move or fill
memory a byte at
a time or in a Z/80 controlled loop. The short Z/80 routine
which we will
later call from Basic to copy its ROM into 8500 visible RAM uses
an LDIR loop.

5. THE BLOCK SEARCH GROUP (C=128 only)

As above, the Z/80 can automatically control looping by counting


down the
value contained in the BC pair and incrementing the address
pointed to by DE.
Ranges of memory are compared with the A register until a match
is found or
the BC pair decrements to zero.

6. THE 8 BIT ARITHMETIC AND LOGICAL GROUP (C=128 only)

These allow for manipulation of one byte values in pretty much


the same way
6510 programmers are used to. Addition and subtraction are
possible with or
without a carry.

7. THE 16 BIT ARITHMETIC AND LOGICAL GROUP (C=128 only)

Same as above but with two byte values being manipulated. The
logical AND, OR
and XOR are not found in this group.

8. THE CPU CONTROL GROUP (C=128 only)

Processor and interrupt modes and status flags are handled.

9. THE ROTATE AND SHIFT GROUP (C=128 only)

Many different types of shifts accessing both one and two byte
values via a
variety of addressing modes are available.

10. THE BIT SET RESET AND TEST GROUP (C=128 only)
These commands provide for complete bit addressing. Each takes
two
parameters. The first will specify which bit (0-7) is to be
set, reset, or
tested; the second will designate the register or memory
location to be
manipulated. For example SET 3,(IX+0) would set bit 3 in the
address pointed
to by the ISX register (i.e. OR it with the number 8).

11. THE JUMP GROUP (C=128 only)

Conditional and unconditional, jumps (direct) and branches


(relative) are
supported. Anyone who had ever had to fake a conditional jump
in the 6510 via
BNE *+5:JMP FAR or an unconditional branch via SEC:BCS NEAR
will appreciate
the versatility of this Z/80 group.

12. THE CALL AND RETURN GROUP (C=128 only)

Subroutines may also be called and returned from conditionally


or
unconditionally.

13. INPUT OUTPUT GROUP (C=128 only)

These are specialized load and store instructions. In the


C=128, when
accessing I/O memory (D000-DFFF), IN and OUT commands should be
used instead
of LD.

PROGRAMMING THE Z/80 IN 128 MODE (C=128 ONLY)

The Z/80 brings a convenience and conciseness to ML programming


that is sure
to please and impress 6510 assembly language programmers. I
hope the above
has wetted your appetite for doing a little exploring. It will
inspire you to
know that this micro processor can be used in conjunction with
(not at the
same time as) the 8500 in the C=128, even from Basic; switching
between them
is not much more difficult than switching between memory banks
once you know
how.

SWITCHING PROCESSORS (C=128 only)

Bit 0 at $D505 (54533) controls the micro processor mode. If it


is turned on
then the 8500 becomes active; if it is not then the Z/80 takes
over.

You can't just poke it off. A little housekeeping is first in


order:

Disable the 8500 interrupts via SEI because you are going to
switch to a
memory configuration in which Kernal ROM is not visible.

To do this, store a $3E (62) at $FF00 (the configuration


register). This
leaves I/O RAM intact but switches everything else to RAM 0.

MANAGING THE PROGRAM COUNTERS (C=128 only)

You're still not quite ready. The Z/80 PC register holds $FFED
after 128
initialization. There is a NOP ($00) there. The first actual
Z/80 command
goes at $FFEE. If you look through the monitor you will see a
$CF there.
This is a RST 8 opcode byte which will cause the Z/80 to jump
(ReSTart) to its
own ROM routine at 0008. You do not want this. After moving
some 8500 code
into place at $3000, the Z/80 would return control to the 8500.
The 8500
wakes up exactly where it left off after you switched to the
Z/80. If you
followed this switch with a NOP (lets not wake it up to fast)
and then a JMP
$3000 (like the operating system does) you would go into the
128's boot CP/M
routine. This is pretty useless from a programming standpoint,
so don't
bother. Instead, put your own Z/80 code at $FFEE.

THE Z/80 STACK (C=128 only)


Before you do nay Z/80 subroutine calls you should set its stack
pointer
register (SP) to point to some area that will not interfere with
your code or
Basic.

The last thing the Z/80 will have to do is to turn the 8500 back
on. There
are two ways to do this:

LD A,$B1
LD ($D505),A

This is inferior. There is a bleed through condition in the


Z/80 mode using
this type of store. A $B1 will also be written to the
underlying RAM. (which
is where ZBUDDY sits, making this feature especially
bothersome.)

Here is the proper way:

LD BC,$D505
LD A,$B1
OUT (C),A

Bleed through will not occur using OUT storage and all I/O
memory between
$D000 and $DFFF can be written to. In our Basic coding sample
the background
($D021) and border ($D020) are poked via the Z/80 OUT
instruction.

Ordinarily you would have to bear in mind that the Z/80 might
not necessarily
take off at $FFEE the next time you activate it. It, like the
8500, wakes up
where it went to sleep. The best procedure for switching back
and forth is to
try to always put the micro processors to sleep in the same
spots. These
switches could be followed with jump commands. Before invoking
them you could
set the jump address for the other micro processor to anywhere
you like. Z/80
ROM puts a RET ($C9) command after the 8500 switch allowing the
Z/80 to CALL
the 8500 from anywhere and return when the 8500 switches back.
You can also
put an RTS ($60) after the Z/80 switch so that the 8500 can JSR
the Z/80.

TWO RAM ROUTINES FOR SWITCHING (C=128 only)

Now it just so happens that there are two routines high in RAM 0
through which
these two micro processors can invoke each other. The 8500
invokes the Z/80
at $FFD0. When the Z/80 returns control, the 8500 picks up at
$FFDB. Leave
the NOP ($EA). You can take over at $FFDC (65500).

The Z/80 invokes the 8500 at $FFE0. When the 8500 returns
control, the Z/80
picks up again at $FFEE -- and so on and so on.

SWITCHER (C=128 only)

On your disk is a small POWER ASSEMBLER source program called


"SWITCHER-
SOURCE" which handles the Z/80 stack, the user call, and
controls the "sleepy
time" program counters for the two micro processors while making
use of the
RAM routines at $FFE0 and $FFD0. SWITCHER thus allows you to
easily execute
hybrid programs and, as our "INVOKE-Z80.BAS" example shows, even
call the Z/80
from Basic.

SWITCHER code sits at 3000, high in the 128's tape buffer. The
address of the
Z/80 code to be executed should be in the 8500's X (=low byte)
and A (=high
byte) registers. These can be passed directly from ML or even
Basic via the
128's new improved SYS command, which is exactly what INVOKE-
Z80.BAS does.
The program pokes some Z/80 code in at $6000, then after having
SWITCHER get
the Z/80 to execute it, continues in Basic. The Z/80 code
copies its ROM into
RAM at $8000. Notice how easy it is to code this move (4
instructions, 11
bytes). The Z/80 then pokes the screen colors just to show off.

The SWITCHER code isn't long at all, and should pave the way for
some serious
exploration of the Z/80 language and environment in the 128 by
true Commodore
O/S hackers. You can use POWER ASSEMBLER to relocate the
SWITCHER code and
ZBUD to write much more interesting dual processing applications
than provided
in our little Basic demo.

POWER UNASSEMBLER

On the program disk is an ASCII source file called UNASM-SOURCE.


If you are
using the Basic format compatible POWER ASSEMBLER then running
then running
the following short program will assemble the necessary code to
memory.

For the COMMODORE 64:

10 SYS 999
20 .SEQ "UNASM-SOURCE"

If you are working with the EBUD version of the assembler then
only a .SEQ
"UNASM-SOURCE" line need be ASsembled, or you may GET this file
into the
editor, make modifications to it and assemble it directly from
memory. (See
EDITOR.64 section on "SOMETHING TO TRY" of this manual).

For the COMMODORE 128:

10 SYS 4000
20 .BURST ;if you have a 1571
30 .SEQ "UNASM-SOURCE"

If you would like a BLOAD'able object file, insert the following


line before
the .SEQ line:

25 .ORG 60000:.OBJ "UN-CODE"; any name will do


Do not try to change the load destination to other than 60000
from outside the
main source. To do this (1) DLOAD and RUN "EBUD", (2) press
<LOGO> G to
GET:UNASM-SOURCE, (3) change line 1's .ORG 60000 to your own
origin address
(then an .OBJ "NAME" line if you want the code saved), (4) press
RUN STOP to
enter Basic, (5) enter the AS command to assemble everything.

In any case you have a powerful memory based unassembler at your


disposal; one
that will convert raw code to LOAD'able, LIST'able, SAVE'able
source that you
can attack with LABELGUN (C=128 only), modify and/or re-assemble
using POWER
ASSEMBLER, or convert using MAKE-ASCII to source that can be
worked on in
EBUD's powerful ASCII editor.

HOW TO USE UNASM

After assembling UNASM-SOURCE to memory it must be enabled via


SYS 50000 for
the C=64 or BANK1:SYS 60000 for the C=128, (unless you've
changed the origin).
All this does is set some pointers and print a header. To use
UNASM enter the
UN command from Basic. Your "UN" will be extended to prompt:

UNASSEMBLE FROM $

Enter a start address in hexadecimal. (You can use POWER


ASSEMBLER display to
convert decimal to hex if need be). You will then be prompted

TO $

Another hex value must be entered representing the address of


the last byte of
code to be un-assembled.

HIDDEN RAM (C=64 only)

Next you will be asked if ROM is to be banked out. If you are


un-assembling
hidden RAM such as where BUDDY resides then you would press "Y"
for this. If
you are un-assembling ROM you must press "N", otherwise it
doesn't really
mater.

SELECT BANK (C-128 Only)

Next you will be asked to select the bank of memory which the
code you want to
un-assemble is in. As in the C-128 monitor you will use 0-F to
designate the
banks zero through fifteen.

FORMAT

Finally you will be asked if you want standard format. You


probably do not,
so press "N". Standard format cannot be reassembled; it is for
looking at.
The line number represents the decimal address of each
instruction. Following
this will be the same value in hex. Last will be the
instruction. Except for
the line numbers this resembles the format produced by ML
monitors. Again,
standard format is for examination purposes, not re-assembling.

Non-standard format produces actual POWER ASSEMBLER source that,


with a little
work, you can make as good as the original. Line numbers will
still represent
the address of the un-assembled code. Labels will be generated
and used if
and only if possible.

Depending on the amount of code being un-assembled you will have


to wait from
no time at all to about 10 seconds for the job to be done. When
Basic is
again "ready" enter LIST ...there is your source.

RANGE LIMITS

UNASM can take on almost 4k of code at a crack. It is sensitive


to the Top-
Of-Basic pointer ($1212) so that utilities such as your
assemblers and editor
which use this pointer to protect themselves will never be over
written by
UNASM generated source. If you enter a range too large to fit
in the Basic
buffer no harm will come of it. UNASM will do as much as it can
before
stopping.

PROBLEMS

Those of you who try to LOAD...,8,1 and un-assemble EDITOR.64


($9700-$9FFD)
will discover that the code, when assembled back to memory does
not work
properly. This program like many others has a certain amount of
ASCII and
other data embedded in it. Where UNASM encounters a non-opcode
it will
generate a .BYTE instruction to handle it; however, sometimes
some rather
awful (i.e. meaningless) instruction sequences will also be
generated by this
data. It is up to you to create the appropriate .ASC, .BYTE or
.WORD lines to
give meaning to this garbled source.

UNASM may also produce source lines like this:

49152 ZC000 ASL $0020

Absolute addressing has been used on a zero page address.


Whether this was
intended or the result of embedded data the assembler will
assume you mean ASL
$20 and code zero page addressing. The $00 byte is lost and the
code is
shortened.

SOLUTIONS

You can correct assembling un-intended zero page addressing by


changing such
un-assembled source lines to 49152 ZC000 ASL !$20, forcing
absolute. The
source should then assemble properly to its intended destination
although it
may not look pretty or be truly useful yet.

You can use POWER ASSEMBLER's .OFF and .MEM pseudo-ops to


assemble the code to
memory somewhere safe and then perhaps write a short Basic
program to compare
it byte for byte with the original. You will be able to spot,
then list,
lines which didn't re-assemble properly.

UNASM can also not possibly know when the low and high byte
values of internal
addresses are being used in order to set up RTS jumps, intercept
vectors, or
self modify. You will have to study the source to see where
this is being
done and create the correct symbolic expressions for these
statements before
it will be truly re-workable and re-locatable.

Having a symbol table for the un-assembled code (as you have for
the
assemblers) can make analyzing it and even reconstructing
meaningful source
much less work.

For the Commodore 128, LABELGUN commands can be used to attach


meaningful
names to the hex oriented symbols generated by UNASM. MAKE-
ASCII can be used
to convert the Basic format, un-assemble the source to stuff you
can work on
in the ASCII editor (you'll loose the line number references).

Inefficient disk space makes it impossible to provide complete


source listings
for your assembler as part of the system package. However you
should find
UNASM-SOURCE and the SYM files an interesting and useful
compromise.

STANDARD INSTRUCTION SET & ADDRESSING MODES

ADC #byte byte byte,x word word,x word,y (byte,x) (byte),y


add memory to accumulator with carry.
AND #byte byte byte,x word word,x word,y (byte,x) (byte),y
logical AND memory with the accumulator.

ASL implied byte byte,x word word,x


shift left one bit.

BCC word
branch on carry clear.

BCS word
branch on carry set.

BEQ word
branch on zero.

BIT word
test bits.

BMI word
branch on negative (128-255).

BNE word
branch on word not zero.

BPL word
Branch on positive (0-127).

BRK implied
break execution.

BVC word
branch on overflow clear (bit 6).

BVS word
branch on overflow set.

CLC implied
clear carry flag.

CLD implied
clear decimal mode.

CLI implied
clear for interrupts

CLV implied
clear overflow flag.

CMP #byte byte byte,x word word,x word,y (byte,x) (byte),y


compare with accumulator.

CPX #byte byte word


compare with x index.

CPY #byte byte word


compare with y index.

DEC byte byte,x word word,x


decrement memory by one.

DEX implied
decrement x index by one.

DEY implied
decrement y index by one.

EOR #byte byte byte,x word word,x word,y (byte,x)


(byte),y
exclusive OR accumulator.

INC byte byte,x word word,x


increment memory by one.

INX implied
increment x index by one.

INY implied
increment y index by one.

JMP word (word)


jump to new location.

JSR word
Jump to a new location, save return address.

LDA #byte byte byte,x word word,x word,y (byte,x)


(byte),y
load accumulator

LDX #byte byte byte,y word word,y


load x index.

LDY #byte byte byte,x word word,x


load y index.

LSR implied byte byte,x word word,x


shift right one bit.

NOP implied
no operation.

ORA #byte byte byte,x word word,x word,y (byte,x)


(byte),y
logical OR with accumulator.

PHA implied
push accumulator on stack.

PHP implied
push processor status (flags) on stack.

PLA implied
pull accumulator from stack.

PLP implied
pull processor status (flags) from stack.

ROL implied byte byte,x word word,x


rotate left one bit with carry.

ROR implied byte byte,x word word,x


rotate right one bit with carry.

RTI implied
return from interrupt.

RTS implied
return from subroutine.

SBC #byte byte byte,x word word,x word,y (byte,x)


(byte),y
subtract memory from accumulator with borrow.

SEC implied
set carry flag

SED implied
set decimal mode.

SEI implied
disable interrupts.

STA byte byte,x word word,x word,y (byte,x) (byte),y


store the accumulator in memory

STX byte byte,y word


store x index register in memory.

STY byte byte,y word


store y index register in memory.

TAX implied
transfer accumulator to x index register.

TA implied
transfer accumulator to y index register.

TSX implied
transfer stack pointer to x index register.

TXA implied
transfer x index register to accumulator.

TXS implied
transfer x index register to stack pointer.

TYA implied transfer y index register to accumulator.

NON STANDARD 6510 (.PSU) INSTRUCTIONS & ADDRESSING MODES

ASO #byte byte byte,x word word,x word,y (byte,x)


(byte),y
ASL the ORA result with accumulator.

RLA #byte byte byte,x word word,x word,y (byte,x)


(byte),y
ROL then AND result with accumulator.

LSE #byte byte byte,x word word,x word,y (byte,x)


(byte),y
LSR then EOR result with accumulator.

RRA #byte byte byte,x word word,x word,y (byte,x)


(byte),y
ROR then ADC result to accumulator.

AXS byte byte,x byte,y (byte,x)


store result of a AND x.

LAX byte byte,x word word,y (byte,x) (byte),y


LDA and LDX with same memory.

DCM byte byte,x word word,x word,y (byte,x) (byte),y


DEC memory then CMP.

INS byte byte,x word word,x word,y (byte,x) (byte),y


INC memory then SBC.

ALR #byte
AND with value then LSR result.

ARR #byte
AND with value then ROR result.

XAA #byte
AND with x then store in a.

OAL #byte
ORA eith #$EE then AND with data then TAX.

SAX #byte
SBC data from a AND x then TAX.

SKB byte
skip byte.

SKW word
skip word.

RECOMMENDED READING LIST

REFERENCE AUTHOR
PUBLISHER
MACHINE LANGUAGE FOR THE COMMODORE 64 Butterfield
Brady
AND OTHER COMPUTERS
ASSEMBLY LANGUAGE FOR THE COMMODORE 64 Sanders
Microcomscribe

INNER SPACE ANTHOLOGY 2ND EDITION Karl Hildon


Transactor

ADVANCED MACHINE LANGUAGE Data-Becker


Abacus
MACHINE LANGUAGE FOR BEGINNERS Mansfield
Compute!

SECOND BOOK OF MACHINE LANGUAGE Mansfield


Compute!

FOR THE SOURCE CODES and USAGE for BUDDY ASSEMBLER VISIT URL :
http://members.aol.com/fyarra001/
OR EMAIL ME – KNOWN PROGRAMMER and INSANE COMMODORE ENTHUSIAST:

(r_twiddy@yahoo.com)

You might also like