
                                          An Assembly Language Tutorial for the Model 100
  ______________________________________________________________________________________________________________________________
                                                                 
   
    By Mike Berro [75765,473]
    
   
    (C) 1984 BCS Software
    
  0. Preface.
  
   I have an ulterior motive for writing this. I want to sharpen my technical writing skills. Any comments or criticisms
   about contents or style would be appreciated. Also, I will be happy to try to answer any questions you have about assembly
   language.
   
     _____________________________________________________________________________________________________________________
                                                                
  1. Switching Gears from Basic to Assembly Language.
  
   After programming in Basic, using assembly language may at first seem like switching from the Model 100 to the HP-41
   calculator. Gone are string variables and the print statements. You don't even have a multiply or a divide command like
   the HP-41. You have to multiply or divide by using successive additions or subtractions. However, the benefits over Basic
   include high speed and small size.
   
   My game Planet Protector (PLANET.SRC on CompuServe's Model 100 SIG) is only 2.5K long, and the spaceships move reasonably
   smoothly across the screen. Compare tht to my earlier Basic game Starfighter (STARF.100), which is over 10K long and
   slower than anything. And doing what is essentially bit-mapped graphics to produce your own shapes would slow Basic down
   by at least another factor of ten.
   
   Anyway, since you're still reading this you probably already know the advantages of assembly language, so we'll begin.
   
2. How to Use This Tutorial.

   It would be to your benefit to have at least a reference book on 8085 assembly language, if not a textbook. I am not
   trying to write a book here, so there may be some information left out. This tutorial is designed as an adjunct to a text
   book, and not a substitute. It will also be useful for you to have an assembler. See HELP.ASM in XA4 for help in choosing
   one. The sample programs I use will be formatted for Custom Software's assembler, but only because that happens to be the
   one I have. You should have no difficulty translationg to your own assembler.
   
   Also it is very important to backup all the files on your M100 before you assemble any program. Basic (almost) always
   makes sure that it doesn't clobber other files (unless you open a file for OUTPUT instead of INPUT.) If you assign a value
   to a variable, the value does get stored in memory, but Basic will never store it in a protected area of memory.
   
   Machine language programs have no compunction about storing values anywhere. If you make a typo so that the program stores
   values where the file directory should be you're in big trouble. And it happens to the best of 'em. Maybe I'm being
   paranoid, but I'll say it again:
   
   >>>>>> BACKUP ALL YOUR FILES BEFORE YOU BEGIN ASSEMBLY <<<<<<
   
   Our goal for this series is to be able to write a program that allows you to design any shape, and then move it across the
   screen. The shape table will be stored in a text file created by the user. The program will be called SHAPE. It will take
   several installments of TUTOR before we are finished.
   
3. What an Assembler Does.

   An assembler converts your source file, which is a text file, into a machine language file. You create the source file
   using the built-in text editor. The source file contains the source code that the assembler converts into a series of
   numbers that represent the instructions you want carried out (machine language.)
   
   The numbers are stored in memory, and then the memory locations used are identified by assigning a filename to them, with
   the suffix ".CO". The .CO file is called a machine language file. It is possible to program directly in machine language
   by pokeing the numbers directly into memory, but that gets tedious quickly. Machine language is all numbers, no english
   commands at all. Assembly language consists of "memnonics" that represent the machine language numbers. You only have to
   remember the memnonics, which by definition are supposed to be easy to remember. Then the assembler converts the memnonics
   into machine language for you.
   
   The next section deals with binary and hex numbers, and with some of the inner workings of the 80C85.
   
4. The 80C85 and Number Systems.

   At this point you need to know the binary and hexadecimal number systems. In this section I will try to explain why you
   need these other number systems. If you are not familiar with them, most books about assembly language start with them. If
   your book does not, it probably is not a beginner's book.
   
   The CPU in the Model 100 is an 80C85 integrated circuit. (The C stands for CMOS, which is a battery conserving type of
   electrical device.)
   
   As such it only responds to electrical signals, no matter how much you yell at it. It is an 8-bit CPU, which means it can
   respond to eight signals simultaneously. Each signal is a bit. Now the CPU is also a digital device, so each signal can
   only be a one or a zero. Music is an analogue signal; you can send Beethoven's fifth symphony through one wire (although
   it sounds better through two.) A digital signal can only be on or off, one or zero. Given an 8-bit digital CPU, there is a
   limit to the number of different messages or commands that we can give it at any one time. In fact there are only 256.
   00000000 is one message, 00000001 is another, 00000010 is the "next" one, up to 11111111. These are all binary numbers
   since each digit (bit) can be one of two values. We normally use the decimal numbers from 0-9 (ten values). Assemblers
   very often use hexadecimal numbers, which use 16 values.
   
   All three number systems are used in assembly language programming. In fact, the only reason to use decimal numbers is
   because we are familiar with them. The CPU only recognizes ones and zeroes, eight at a time. There are 256 combinations
   possible. Hexadecimal is used because of convenience.
   
   Look at Table 1. Notice that the left hexadecimal digit precisely
   
                                                       Dec Binary Hex Oct
                                                       000 00000000 00 000
                                                       001 00000001 01 001
                                                       002 00000010 02 002
                                                                
                                                       010 00001010 0A 012
                                                       011 00001011 0B 013
                                                       012 00001100 0C 014
                                                       013 00001101 0D 015
                                                       014 00001110 0E 016
                                                       015 00001111 0F 017
                                                       016 00010000 10 020
                                                       017 00010001 11 021
                                                                
                                                    TABLE 1. Number Systems
                                                                
   corresponds the left four digits of the binary number, and the right hexa-decimal digit corresponds to the right four
   digits of the binary number.
   
   Binary numbers would be appropriate to use, but they take up more memory in your source file (8 digits). Hexadecimal
   numbers are the next logical choice.
   
   Octal numbers are sometimes recommended for the 8085. The first two digits of the binary number correspond to the first
   octal digit, the next three digits correspond to the second octal digit, and the last three binary digits correspond to
   the last octal digit.
   
   I do not recommend that you learn octal arithmetic, although it couldn't hurt. Octal arithmetic is very useful for
   converting the source code into the actual machine language, but since that is what our assembler is supposed to be doing
   for us, there is no real need to master it.
   
5. The 80C85 Architecture.

   This section describes what's available for the programmer to use in the 8085. It is written from the programmer's point
   of view, and not the engineer's. A more complete analysis of the 8085 will be left for later.
   
   One of the most important functions of the CPU is to manipulate data. In Basic we store the data in variables, both
   numeric and string. We don't know exactly where in memory the data is stored, but we know that Basic knows, so we don't
   worry about it. In assembly language we have to worry about it.
   
   We can store data directly inside the CPU. The 8085 has seven 8-bit registers. Each register can store a value from $00 to
   $FF (0 to 255: a dollar sign prior to a number indicates a hexadecimal value.) The registers are designated by single
   letters: A, B, C, D, E, H and L. They are not all created equal, and are used for different purposes.
   
   The A register is also called the accumulator. All of the 8-bit arithmetic is done on the value in the accumulator.
   Addition, subtraction, ANDing, EXCLUSIVE ORing, ORing, rotations and comparisons all involve the A register (don't worry
   if you don't know what they all mean. You can worry if you don't know what the first two mean though.) For example, the
   instruction ADI $10 adds the value $10 to what is already in the A register. The instruction ADD B adds the value of the B
   register to the value of the A register, and then stores the value back into the A register. The B register is unchanged.
   Only the A register is affected by any of these operations.
   
   Now that we know how to do arithmetic on the A register, how do we get a value into it? There are five ways to get a value
   into the A register. The first is to load it with a preset value, called an immediate value, like $07, or $AD. The second
   is to transfer it from a different register. The third is to get the value from somewhere in memory. The fourth is to get
   it from a peripheral device, and the final method is to get the value from the serial data port.
   
   The last two methods involve advanced topics. The serial data port is used solely for the cassette input. A peripheral
   device is something that communicates with the 8085, something that facilitates input and output (I/O). The keyboard, LCD
   display, modem, speaker, light-pen and printer (with their associated circuitry) are all I/O devices.
   
   Luckily we don't have to learn the I/O instructions, because there are subroutines already built-in that read data from
   these devices, or send data to them. Some of these subroutines are listed starting on page 79 of the Model 100 Technical
   Manual. We will return to them in section 9.
   
6. The MoVe Immediate (MVI) Instructions.

   The MVI instructions move an immediate value into any specified register. The following examples should explain how it
   works better than mere words:
   
MVI A,$08 ;A will have the value 8 after this instruction
MVI C,$ED ;C will have the value $ED after this instruction
MVI H,$11 ;H will have the value $11 after this instruction

   Technically speaking, MVI is an instruction by itself, part of the 8085 "instruction set". The grouping following it is
   called the operand. For any move instruction, including the move immediate, the operand always consists of a source and a
   destination. The destination always comes first, then a comma, and then the source. The source for the MVI instruction is
   always an 8-bit number, a value between $00 and $FF.
   
   The destination is always either a register or a memory location. One of the nice features of the 8085 is the ability to
   treat a memory location just like it was a register. You have to set up certain conditions before you can do so, and that
   will be covered in section 8.
   
   The MVI instruction takes up two bytes of memory. A byte is an 8-bit value, so each memory location stores one byte. The
   first byte is the MVI r, part, where r stands for any register. The second byte is the immediate value, the source.
   
7. The MOVe (MOV) Instruction.

   The MOV instruction moves data from one register to another. The instruction format is MOV dr,sr, where dr is the
   destination register, and sr is the source register. The content of the source register is the same after this instruction
   is executed. The destination register will contain the same value as the source register. The MOV instruction takes only
   one byte of memory.
   
8. Moving Data to and from Memory.

   So far we have moved immediate numbers into registers, and values from register to register. Now we will see how to
   retrieve data from memory. In this section we will also see what a source program looks like. To move data into a memory
   location, we must first decide what address to use. Each of the 65,536 memory locations has a unique address, a number
   from $0000 to $FFFF. It takes a 16-bit number count that high, and it can be done on the 8085. We have already seen that
   the 8085 has seven 8-bit registers. Six of the registers can be "doubled-up" to give you three 16-bit registers. The B and
   the C register give you the BC register, the D and E registers givve you the DE register, and the H and L registers give
   you, surprise!, the HL register.
   
   The 16-bit registers are not separate from the 8-bit registers. If you store an 8-bit number in L, and then a 16-bit
   number in HL, the 8-bit number is lost. To add to the confusion, most assemblers allow you to use only the first letter of
   a 16-bit register. Then the only way to tell the difference between an 8 or 16-bit register is from context, i.e. from the
   instruction that precedes it. Here we will use both letters for a 16-bit register.
   
   The HL register is the only register that can be used to specify a memory address. It is called a pointer register because
   it points to a memory location. The H register contains the high byte of the address, and the L register the low byte
   (that's why it's not called the FG register.)
   
   High and low bytes may be unfamiliar to you. Consider the decimal number 47. From convention we know that 4 is the number
   of "tens", and the 7 is the number of "units", high and then low. For the hex number $47, 4 is the number of "sixteens",
   and 7 is the number of "units". In the same way for the number $4768, $47 is the number of "two-hundred-fifty-sixes", and
   $68 is the number of "units". We split it this way because the first two digits can fit in the high byte (register) and
   the second two can fit in the low register. However, as we shall see later, sometimes the low byte comes first, with the
   high byte following, but the HL register is always high-low.
   
   The instruction that loads an address into the HL register is the LXI instruction. The LXI instruction loads a 16-bit
   immediate value into the register pair designated in the first half of the operand, the destination. The second half of
   the operand is the 16-bit immediate value. The format is LXI rp,$nnnn, where rp is any of the three register pairs, and
   $nnnn is a 2 byte (up to four hex digit) number.
   
   Once HL points to a memory address, we can treat that memory location just like it was a register. We can use:
   
MVI M,$AB ;memory location pointed to by HL will equal $AB
MOV A,M ;A register will have value of memory location

   The LXI instruction requires three bytes of memory. The first specifies the instruction and the destination register (all
   in one byte), and the last two contain the immediate 16-bit value. It is interesting (and confusing) to note that
   addresses in memory are always stored low byte first.
   
   The instruction LXI HL,$1234 assembles into the three consecutive bytes $21, $34, $12. $21 is the LXI H, instruction. The
   $34 and $12 is the address with the low byte first. You don't have to worry about the order when you are programming
   because the assembler puts all the addresses in the proper order.
   
   You do have to worry about it when debugging your program. Sometimes you have to examine the machine language code to find
   the problem, and this high-low business can get confusing. To repeat, addresses in the registers are always stored
   high-low. Addresses in memory are stored low-high.
   
9. Subroutines and a Simple Program.

   Let us now jump into some actual programming. Reading about instructions is all very well, but the only way to learn is to
   use them. Let us suppose that we want to print the seconds from the real time clock onto the LCD screen. We will have our
   program print the word "Time = ", and then print the seconds. We don't know how the screen works, but browsing through the
   technical manual, we find a ROM subroutine that seems like it will do most of the work for us. It is a subroutine that
   Radio Shack calls "LCD". The manual describes it as "Displays a character on the LCD screen at current cursor position."
   Sounds good, but what does current cursor position mean? We don't know, so we'll just use the subroutine and see where the
   numbers get displayed. It is now time to back-up every file in memory.
   
   Twice.
   
   There is more information in the subroutine description. It says the entry address is (hex) 4B44, entry conditions:
   A=character to be displayed, exit conditions: None. The entry address is simple the address of the subroutine. In Basic we
   gosub to a line number. We dont have line numbers here, but we do have memory addresses.
   
   The entry condition tells us that the A register must contain the character we want printed when we call the subroutine.
   Of course registers don't contain characters, they contain 8-bit numbers. We need to know what number represents each
   character. The User's Manual tells us that starting on page 211. It gives us the numbers in decimal, hex and binary. We
   will use hex to be consistent.
   
   Look at program 1, and then we will discuss it.
   
  ______________________________________________________________________________________________________________________________
                                                                 
001 ;PRTIME
002 ;Oct 30, 1984
;
003 ORG $DAC0
004 ENT $DAC0
;
005 MVI A,$54 ;character code for "T"
006 CALL $4B44 ;print character in A onto screen
007 MVI A,$49 ;character code for "I"
008 CALL $4B44 ;print character
009 MVI A,$4D ;character code for "M"
010 CALL $4B44 ;print character
011 MVI A,$45 ;character code for "E"
012 CALL $4B44 ;print character
013 MVI A,$20 ;character code for space
014 CALL $4B44 ;print character
015 MVI A,$3D ;character code for "="
016 CALL $4B44 ;print character
017 MVI A,$20 ;character code for space
018 CALL $4B44 ;print character
;
019 LXI HL,$F934 ;load address of tens of seconds
020 MOV A,M ;move tens of seconds into register A
021 CALL $4B44 ;print character
022 LXI HL,$F933 ;load address of unit seconds
023 MOV A,M ;move unit seconds into register A
024 CALL $4B44 ;print character
;
025 CALL $12CB ;wait for keypress
026 JMP $5797 ;jump to menu
;
027 END

   PROGRAM 1. Print seconds to screen.
   
     _____________________________________________________________________________________________________________________
                                                                
   Please note that the line numbers are NOT part of the program. I added them here for clarity, but the source code should
   not have them. Line numbers are often printed out by the assembler itself, when you direct the assembler to produce a
   listing.
   
   The first thing you may notice are all the semicolons. They indicate that what follows is a comment. Not all assemblers
   use the semicolon as the comment indicator, so check your manual. There are no blank lines either. Every line must have at
   least one character. However, it makes the program easier to read if different portions of the program are separated by
   blank lines, so the next best thing is to use a comment line without a comment, just a semicolon. I've also put a comment
   after every instruction. Comments are great if you have to come back to a program after not working on it for a while, but
   they do take up space. I would recommend you use comments liberally at first, and then delete some of them if you need
   more memory.
   
   The second thing you may notice is the ORG and ENT commands. These commands are not commands for the 8085, but are
   commands for the assembler.
   
   The ORG command is a standard (almost universal) command that tells the assembler where the program is to reside in
   memory. It is the address of the first instruction (or data) of your program (the ORiGin.) In this case the program will
   start at $DAC0, which is 56000 in decimal.
   
   The highest available memory address for your programs is 62959.
   
   Memory above that is used for the file directory, and other RAM data the M100 needs. Never let your program extend past
   there, and don't write to an address above 62959 unless you know what you're doing.
   
   The ENT instruction tells the assembler where the program should begin execution. I have never found it neccessary to make
   the ENT address different from ORG. Just make sure the first part of your program is an instruction and not data.
   
   The final instruction is END, and that is also an assembler directive. It tells the assembler that that's all there is.
   You can put comments after the END command without using a semicolon.
   
   All the other lines contain 8085 assembly code.
   
   Line five is the move immediate command, and moves the value $54 into register A. $54 is the ASCII value for the letter
   "T". It is important to note that even though all computers use ASCII code, the ASCII values may be different. The ASCII
   value of "T" on the Apple is $D4.
   
   Line 006 says it prints the character in A onto the screen. What it actually does is CALL the subroutine at $4B44. The
   CALL instruction is just like the GOSUB command in Basic. When a return instruction (and there are several types) is
   encountered, program execution will resume at the instruction immediately following the CALL instruction.
   
   In lines 007 to 018, each letter of the word "IME = " is moved into the A register, and then $4B44 is CALLed. The next
   section prints the seconds from the real-time clock onto the screen.
   
   To do this, we need to know where the seconds are located in memory.
   
   Information like this can be found on a "memory map" of the Model 100. A memory map tells what each address or range of
   addresses is used for. Most commercial assemblers come with one. By looking in Custom Software's rather extensive one, I
   discover that the tens of seconds are stored at address$F934, and the unit seconds are at $F933.
   
   So now we want to move a value from a memory location into the A register. To do that we must first load the memory
   address into the HL register (pointer register.) Line 019 does that with the 16-bit load immediate instruction. Line 020
   moves the value from memory pointed to by HL ($F934), into the A register, and line 021 CALLs the subroutine at $4B44
   print the value in A to the screen.
   
   Lines 022 through 024 do the same thing with the unit seconds at $F933. Once the seconds have been displayed, we want the
   program to stop and wait until we press any key before it returns to the menu. Line 024 does that for us.
   
   Line 024 calls a subroutine at $12CB. $12CB is the entry address for the subroutine that the M100 technical manual calls
   CHGET, which "waits and gets character from keyboard. Since we don't care what key is pressed, that is all we need to
   know. As it happens, the ASCII value of the key we press will be in the A register when execution returns from that
   subroutine.
   
   Line 025 will only be executed after a key has been pressed. At line 025 execution JuMPs to address $5797. The jump
   instruction is like the GOTO instruction in Basic. The jump is executed no matter what. $5797 returns execution to the
   main menu, it is like the MENU command in Basic.
   
   Remember to leave out the line numbers when you type in this program.
   
   Be careful to use either tabs or spaces as required by your assembler. They are usually very finicky, and a space where a
   tab should be, or vice versa, may give you a strange error message when you assemble it.
   
   When you run the program from the menu, the screen clears, and the message we programmed will be displayed in the
   upper-left corner. Evidently the "current cursor position" described in the technical manual is line 1 and row 1. What
   happens is that running any program from the menu automatically clears the screen and sets the cursor to the upper-left
   cornaer of the screen.
   
   If you run the program from Basic with a CALL 56000 command, the message will appear wherever the cursor was.
   
10. Sneak Preview.

   That concludes the first chapter of this tutorial. The next chapter will cover conditional branching. The jump instruction
   used in the previous section is an unconditional branch, it always occurs. Suppose you want to jump back to the beginning
   of the program if the key pressed was a "B", and jump to the menu if it was not. Two lines should be inserted between
   lines 024 and 025:
   
024a CPI $42 ;compare A with the immediate value $42 (= ASCII "B")
024b JZ $DAC0 ;if equals, then jump to start of this program

   As you can see, Conditional branching makes things a lot more interesting.
   
11. Using Labels.

   In section 9 a program was presented that displays "TIME = ", followed by the seconds. In this section a more useful way
   of displaying messages is discussed.
   
   There are several ways to print characters to the LCD. The sample program in section 9 actually uses the most inefficient
   method. However, the Radio Shack Technical Manual doesn't tell you that there is a ROM subroutine that prints a whole
   series of characters (a "string") for you. The subroutine to do that is located at $5A58. I learned this from the
   instruction manual for Custom Software's Model 100 Assembler. The assembler even has a nifty little "macro" that does it
   for you. (A macro is a one word abbreviation for any number of instructions. They can make the source code shorter.)
   
   To use the subroutine, you need to have the string stored in memory. Each assembler has it's own way of assigning data to
   memory. Here are examples of how Custom Software does it:
     _____________________________________________________________________________________________________________________
                                                                
EL DB $4C   ;stores ASCII "L" to memory location "EL"
JK DW $4A4B ;stores $4A to location "JK" & $4B to "JK"+1
MESSAGE DM TIME = ;stores characters starting at location "MESSAGE" ---------------------------------------------------------------
----------------

   DB means Define Byte. DW means Define Word (two bytes), and DM means Define Message. DB and DW are pretty standard, but on
   some assemblers DM is STR, for STRing. DB, DW and DM are all assembler directives. They tell the assembler that there is
   data here, and not an instruction. The assembler stores the appropriate value(s) in memory for you.
   
   Notice the words "EL", "JK" and "MESSAGE" in the first column of each assembler directive. The first column is called the
   label field, and those words are labels. The use of labels is discussed below.
   
   Now that we have the message stored in memory, we can use the ROM subroutine at $5A58. According to Custom Software's
   manual, there are two requirements that must be met (entry conditions). The data to be displayed must be terminated with a
   zero. The subroutine will display character after character until it reaches a zero, and then it will RETurn to your
   program. Remember, it is looking for the value zero, not the ASCII value for zero, which is $30. We must therefore add:
   
DB $00 ;store the value zero as a terminator

   after the message line above.
   
   The other entry condition is that HL should point to the characters to be displayed. That means the HL register needs to
   contain the address of the message. What is the address of the message? If it is the first line of the program, it will be
   wherever the program is ORG'ed. If it isn't, we would then have to count the number of bytes from the beginning of the
   program, and add the ORG value to that. If you change the program, you would then have to recalculate each address.
   
   Luckily, there is a easier way, using labels. In the example above, MESSAGE is a label. When the source code is assembled,
   the assembler keeps track of all the labels. In this case, it would know the memory location of the label MESSAGE. You can
   then use "MESSAGE" whenever you mean "the memory location of the label MESSAGE".
   
   Now we can load the HL register with the address of the message:
   
LXI HL,MESSAGE ;load HL with the address of the message

   and then call the subroutine to display it:
   
CALL $5A58 ;display message

   We don't even have to know what address MESSAGE stands for, the assembler will take care of it for us. However, most
   assemblers will give you a list of all labels and their values at the end of the source code listing.
   
   Suppose now we wanted to call the subroutine at location $5A58 "DISPLAY". If we could tell the assembler that the label
   DISPLAY means $5A58, then we could use the label instead. We can do that using the EQUate directive.
   
   The EQUate directive tells the assembler to assign a value to the label of the EQUate directive. For example:
   
DISPLAY EQU $5A58 ;assign $5A58 to the label DISPLAY

   at the beginning of your program will tell the assembler to substitute $5A58 wherever it sees DISPLAY.
   
   Program 2 does the same thing as program 1, but a little more elegantly. Here we use labels, and the ROM subroutine at
   $5A58.
   
     _____________________________________________________________________________________________________________________
                                                                
;PRTIME2
;Nov 12, 1984
;
ORG $DAC0
ENT $DAC0
;
; These are all ROM subroutines
DISPLAY EQU $5A58  ;print message pointed to by HL
LCD EQU $4B44      ;print character in register A
CHGET EQU $12CB    ;wait for keypress
MENU EQU $5797     ;main MODEL 100 menu
;
SECS EQU $F933     ;memory location for seconds
;
BEGIN LXI HL,MESSAGE ;set HL pointer to start of message data
CALL DISPLAY       ;display message
;
LXI HL,SECS+1 ;SECS+1 = $F933+1 = $F934
MOV A,M
CALL LCD
LXI HL,SECS
MOV A,M
CALL LCD
;
CALL CHGET
JMP MENU
;
MESSAGE DM TIME = ;message data
DB $00 ;terminator
;
END

   PROGRAM 2. Print seconds to screen (using labels).
   
     _____________________________________________________________________________________________________________________
                                                                
   In program 2 you can see that you can perform arithmetic on labels. Some assemblers can evaluate very complex expressions,
   while others can handle only addition and subtraction. Check the documentation for your particular assembler.
   
   On page 80 of the Radio Shack Technical Manual is listed the addresses for several useful display subroutines. Also listed
   are the cursor locations. By moving different values into these two locations, you can start your printing anywhere on the
   screen. Experimenting is fun, but don't forget to be prepared for those cold starts!
   
   (Thanks to Greg Susong of Custom Software for permission to use information taken from the Custom Software Assembler
   Manual.)
