6502 Assembly Lanuage Rountines Part 1 Text
6502 Assembly Lanuage Rountines Part 1 Text
Assembly Language
Subroutines
Lance A. Leventhal
Winthrop Saville
OSBORNE/McGraw-Hill
Berkeley, California
Disclaimer of Warranties
and Limitation of Liabilities
The authors have taken due care in preparing this book and the programs
in it, including research, development, and testing to ascertain their
effectiveness. The authors and the publishers make no expressed or
implied warranty of any kind with regard to these programs nor the sup-
plementary documentation in this book. In no event shall the authors or
the publishers be liable for incidental or consequential damages in con-
nection with or arising out of the furnishing, performance, or use of any
of these programs.
Published by
Osborne/ McGraw-Hill
2600 Tenth St.
Berkeley, California 94710
U.S.A.
For information on translations and book distributors outside of the U.S.A., please write OSBORNE/
McGraw-Hill at the above address.
Preface v
6 Arithmetic 230
10 Input/Output 418
1 1 Interrupts 464
Glossary 519
Index 543
III
Preface
This book intended to serve as a source and a reference for the assembly
is
works on a particular computer, or who wants to gain complete access to the com-
puter's facilities.
•
A student, hobbyist, or teacher who wants to see some examples of working
assembly language programs.
This book can also serve as supplementary material for students of the Assem-
bly Language Programming series.
This book should save the reader time and effort. There is no need to write,
debug, test, or optimize standard routines, nor should the reader have to search
through material with which he or she is thoroughly familiar. The reader should
•
NOMENCLATURE
We
have used the following nomenclature in this book to describe the
architecture of the 6502 processor, to specify operands, and to represent general
values of numbers and addresses.
6502 Architecture
Byte-length registers include
A (accumulator)
PREFACE VII
F (flags, same as P)
P (status register)
S or SP (stack pointer)
X (index register X)
Y (index register Y)
Of these, the general purpose user registers are A, X, and Y. The stack pointer
always contains the address of the next available stack location on page 1 of
memory (addresses 0100 l6 through 01FF, 6 ). The P (status) or F (flag) register
7 6 5 4 3 2 10 •* Bit Number
X B D I Z C Processor Status Register P
-Carry
-Zero
-Interrupt disable
-Decimal mode
-Break command
-Not used (Logic 1)
-Overflow
-Negative (Sign)
PC (program counter)
Flags include
Break (B)
Carry (C)
Decimal Mode (D)
Interrupt Disable (I)
6502 Assembler
Delimiters include
;
Before a comment
After a label (optional)
Pseudo-Operations include
.BLOCK Reserve bytes of memory; reserve the specified number of bytes of
memory for temporary storage
.BYTE Form byte-length data; place the specified 8-bit data in the next
available memory locations
.DBYTE Form double-byte (word) length data with more significant byte
first;place the specified 16-bit data in the next available memory
locations with more significant byte first
Designations include
Number systems:
$ (prefix) or H (suffix) Hexadecimal
@ (prefix) or Q (suffix) Octal
Others:
'
(in front of character) ASCII
* Current value of location
(program) counter
PREFACE IX
' '
or " " (around
a string of characters) ASCII string
# Immediate addressing
Indexed addressing with index
register X
Indexed addressing with index
register Y
The default addressing mode is absolute (direct) addressing.
General Nomenclature
ADDR a 16-bit address in data memory
ADDRH the more significant byte of ADDR
ADDRL the less significant byte of ADDR
BASE a constant 16-bit address
operations
and consumer products. Microcomputer users will make use of these
and systems software, and in
in writing I/O drivers, utility programs, diagnostics,
high-level
understanding, debugging, or improving programs written in
4 There is no true indirect addressing except with JMP. For many other
instructions, however, you can simulate indirect
addressing by clearing index
the
. 5 The always on page 1 of memory. The stack pointer contains
stack is
empty address. Thus, the stack is limited to 256
less significant byte of the next
bytes of memory.
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 3
6. The JSR (Jump to Subroutine) instruction saves the address of its own
third byte in the stack, that JSR saves the return address minus
is, 1. RTS
(Return from Subroutine) loads the program counter from the top of the stack
and then adds 1 to it. You must remember this offset of 1 in debugging and using
JSR or RTS for purposes other than ordinary calls and returns.
7. The Decimal Mode (D)
flag is used to perform decimal arithmetic. When
this flag is and subtractions produce decimal results. Increments
set, all additions
and decrements, however, produce binary results regardless of the mode. The
problem with this approach is that you may not be sure of the initial or current
state of the D
flag (the processor does not initialize it on Reset). A simple
way to
avoid problems in programs that use Addition or Subtraction instructions
is to
save the original D
flag in the stack, assign D the appropriate value, and
restore
the original value before exiting. Interrupt service routines, in particular,
should
always either set or clear D before executing any addition or subtraction
instruc-
tions. The PHP
(Store Status Register in Stack) and PLP (Load Status Register
from Stack) instructions can be used to save and restore the flag, if necessary. D
The overall system startup routine must initialize D
(usually to 0, indicating bin-
ary mode, with CLD). Most 6502-based operating systems
assume the binary
mode as a default and always return to that mode as soon as possible.
A minor quirk of the 6502's decimal mode is that the Zero and Negative flags
are no longer universally valid. These flags reflect only the binary result, not the
decimal result; only the Carry flag always reflects the decimal result.
Thus, for
example, subtracting 80 from 50 in the decimal mode sets the
l6 16 Negative flag
(since the binary result is D0 ), even though the decimal result
16 (70 16 ) has a most
significant bit of 0. Similarly, adding 50 and 50 in the decimal
16 16 mode clears the
Zero flag (since the binary result is A0 ), even though the decimal
I6 result is zero.
Note that adding 50
and 50 16 in the decimal mode does set the Carry. Thus when
16
working in the decimal mode, the programmer should use only
branches that
depend on the Carry flag or operations that do not depend on the mode
at all
(such as subtractions or comparisons followed by branches on
the Zero flag).
8. Ordinary Load (or Pull from the Stack) and Transfer
instructions (except
TXS) affect the Negative (Sign) and Zero flags. This is not the case with
the 8080,
8085, or Z-80 microprocessors. Storing data in memory does not affect
any flags!
9. INC and DEC cannot be applied to the
accumulator. To increment A, use
CLC
ADC #1 ; INCREMENT ACCUMULATOR BY 1
To decrement A, use
SEC
SBC #! ; DECREMENT ACCUMULATOR BY 1
4 6502 ASSEMBLY LANGUAGE SUBROUTINES
10. The index registers are only 8 bits long. This creates obvious problems in
this, use the indirect indexed (postindexed) addressing mode. This mode allows
you to store the starting address of the array in two memory locations on page 0.
Whenever the program completes a 256-byte section, it must add 1 to the more
significant byte of the indirect address before proceeding to the next section. The
processor knows that it has completed a section when index register Y returns to
0. A typical sequence is
Memory location INDR + 1 (on page 0) contains the most significant byte of the
indirect address.
zero page). If you want to test bit 3 of memory location ADDR, you
must use the
sequence
LDA #%00001000
BIT ADDR
Thus, you can perform the following operations without loading the accumulator
at all.' Branch to DEST if bit 7 of ADDR is 1
BIT ADDR
BM1 DEST
Branch to DEST if bit 6 of ADDR is
BIT ADDR
BVC DEST
Of course, you should document the special use of the Overflow flag for later
reference.
CHAPTER 1 : GENERAL PROGRAMMING METHODS 5
13. The
processor lacks some common instructions that are available on the
6800, 6809, and similar processors. Most of the missing instructions are easy to
simulate, although the documentation can become awkward. In particular, we
should mention Clear (use load immediate with instead), Complement (use
logical EXCLUSIVE OR with the all Is byte instead), and the previously men-
tioned Add (without carry) and Subtract (without borrow). There is also no direct
way to load or store the stack pointer (this can be done through index register X),
load or store the status register (this can be done through the stack), or perform
operations between registers (one must be stored in memory). Other missing
instructions include Unconditional Relative Branch (use jump or assign a value
to a flag and branch on it having that value), Increment and Decrement
Accumulator (use the Addition and Subtraction instructions), Arithmetic Shift
(copy bit 7 into Carry and rotate), and Test zero or minus (use a comparison with
or an increment, decrement sequence). Weller describes the definition of 1
occupied location) is used in the 8080, 8085, Z-80, and 6809 microprocessors.
Instructions store data in the stack using postdecrementing (they subtract 1 from
the stack pointer after storing each byte) and load data from the stack using
preincrementing (they add 1 to the stack pointer before loading each byte).
• The
(Interrupt) flag acts as a disable. Setting the flag (with SEI) disables the
I
maskable interrupt and clearing the flag (with CLI) enables the maskable inter-
rupt. This convention is the same as in the 6800 and 6809 but the opposite of that
used in the 8080, 8085, and Z-80.
instruc-
Accumulator. Source and destination for all arithmetic and logical
•
postindexed addressing.
Index register X. Can be incremented using INX or decremented
•
using
that can be used as an index in preindexing. Only register that
DEX- Oply register
can be used to load or store the stack pointer.
decremented using
Index register Y. Can be incremented using INY or
•
Register Instructions
A ADC, AND, ASL, BIT, CMP, EOR, LDA, LSR, ORA, PHA,
PLA, ROL, ROR, SBC, STA, TAX, TAY, TXA, TYA
P (processor status) PHP, PLP (CLC, CLD, CLV, SEC, and SED affect
particular flags)
Instruction Function
INC Increment by 1
Instruction Function
Instruction Function
Instruction Function
V)
LDX Load index register X
if STX Store index register X
on
-c
1
CHAPTER 1 : GENERAL PROGRAMMING METHODS 9
Instruction Function
Instruction Function
operand for arithmetic and logical instructions and the destination for the result.
Only a limited number of instructions operate directly on the index registers
•
•
The available set of addressing methods' varies greatly from instruction to
instruction. Note in particular the limited sets available with the instructions BIT,
CPX, CPY, LDX, LDY, STX, and STY.
Register Transfers
single instruction can transfer data from an index register to the accumulator,
from the accumulator to an index register, from the stack pointer to index
register X, or from index register X to the stack pointer. The mnemonics for the
transfer instructions have the form TSD, where "S" is the source register and
"D" is the destination register as in the convention proposed in IEEE Standard
694. 2 The status (P) register may only be transferred to or from the stack using
PHP or PLP.
memory using direct addressing. A special zero page mode loads registers from
CHAPTER 1: GENERAL PROGRAMMING METHODS 11
addresses on page more rapidly than from addresses on other pages. Ter-
minology for 6502 refers to zero page direct addressing as zero
page addressing and
to the more general direct addressing as absolute addressing.
Examples
1. LDA $40
This instruction loads the accumulator from memory location 0040 The .
l6
special zero page addressing mode requires less time and memory than the more
general absolute (direct) addressing.
2. LDX $C00O
This instruction loads index register X from memory location C000 l6 . It uses
absolute (direct) addressing.
2. LDA #$E3
This instruction loads the accumulator with the number E3„.
16
2. LDX $40,Y
This instruction loads index register X from the address obtained by indexing
with register Y from the base address 0040 l6 Here the special zero page indexed
.
The instruction LDA can be used in the postindexed mode, in which the base
address is taken from two memory locations on page 0. Otherwise, this mode is
Example
LDA ($40) ,Y
This instruction loads the accumulator from the address obtained by indexing
with index register Y from the base address in memory locations 0040 16 and
0041 16 This mode is restricted to page and index register Y. It also assumes that
.
the indirect address is stored with its less significant byte first (at the lower
address) in the usual 6502 manner.
Example
LDA ($40,X)
This instruction loads the accumulator from the indirect address obtained by
indexing with register X from the base address 0040 16 The indirect address is in
.
the two bytes of memory starting at 0040 16 + (X). This mode is uncommon; one
of its uses is to select from a table of device addresses for input/output.
loads the status (P) register. This is the only way to load the status register with a
specific value. The index registers cannot be loaded directly from the stack, but
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 1 3
they can be loaded via the accumulator. The required sequences are
• It is always located on page 1 of memory. The stack pointer contains only the
less significant byte of the next available address.
• Data is stored in the stack using postdecrementing — the instructions decre-
ment the stack pointer by 1 each byte. Data is loaded from the stack
after storing
using preincrementing — the instructions increment the stack pointer by 1 before
loading each byte.
STORING REGISTERS
IN MEMORY
The same approaches that we used to load registers from memory can also be
used to store registers in memory. The only differences between loading and stor-
ing registers are
• STX and STY allow only zero page indexed addressing. Neither allows
absolute indexed addressing.
• As you might expect, the order of operations in storing index registers in the
stack is the opposite of that used in loading them from the stack. The sequences
are
Examples
1. STA $50
This instruction stores the accumulator in memory location 0050 16 The special .
zero page mode is both shorter and faster than the absolute mode, since the more
significant byte of the address is assumed to be 0.
2. STX $17E8
This instruction stores index register X in memory location 17E8 16 . It uses the
absolute addressing mode with a full 16-bit address.
3. STA $A000,Y
This instruction stores the accumulator in the effective address obtained by
adding index register Y to the base address A000 16 The effective address is
.
A000 16 +(Y).
4. STA ($50) ,Y
This instruction stores the accumulator in the effective address obtained by
adding index register Y to the base address in memory locations 0050 l6 and
0051,,. The instruction obtains the base address indirectly.
16
5. STA ($43,X)
This instruction stores the accumulator in the effective address obtained
indirectly by adding index register X to the base 0043 16 The indirect address is in
.
Examples
.1. Store an 8-bit item (VALUE) in address ADDR.
LDA #VALUE ;GET THE VALUE
STA ADDR ; INITIALIZE LOCATION ADDR
We could use either LDX, STX or LDY, STY instead of the LDA, STA
sequence. Note that the 6502 treats all values the same; there is no special
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 1 5
Examples
1 Add memory location 0040 l6 to the accumulator with carry.
ADC $40
This instruction adds the contents of memory location 0040 |6 and the contents of
the Carry flag to the accumulator.
ORA $17E0,X
The effective address is 17E0 16 + (X).
3. Logically AND the accumulator with the contents of memory location
B470 15 .
AND $B4 70
Note the following special features of the 6502's arithmetic and logical instruc-
tions:
• The only addition instruction is ADC (Add with Carry). To exclude the
Carry, you must clear it explicitly using the sequence
CLC ,-MAKECARRY ZERO
ADC $40 ;ADD WITHOUT CARRY
1
•
The only subtraction instruction is SBC (Subtract with Borrow). This
instruction subtracts a memory location and the complemented Carry flag from
the accumulator. SBC produces
where M is the contents of the effective address. To exclude the Carry, you must
set it explicitly using the sequence
Note that you must set the Carry flag before a subtraction, but clear it before an
addition.
(except for the flags in the status register). Here we have not only (Com- CMP
pare Memory with Accumulator), but also CPX (Compare Memory with Index
Register X) and CPY (Compare Memory with Index Register Y). Note the
differences between CMP and SBC; CMP does not include the Carry in the
subtraction, change the accumulator, or affect the Overflow flag.
•
There is no explicit Complement instruction. However, you can comple-
ment EXCLUSIVE ORing it with a byte which contains all Is
the accumulator by
(1 1 1 1 1 1 1 or FF ). Remember, the EXCLUSIVE OR of two bits is 1 if they are
2 16
duce a result of if the other bit is 1 and 1 if the other bit is 0, the same as a logical
•
Add memory locations OPER1 and OPER2, place result in RESLT
LDA OPERl GET FIRST OPERAND
CLC MAKE CARRY ZERO
ADC 0PER2 ADD SECOND OPERAND
STA RESLT SAVE SUM
Note load the first operand into the accumulator and clear the Carry
that we must
before adding the second operand.
CHAPTER 1 : GENERAL PROGRAMMING METHODS 1 7
BIT MANIPULATION
The programmer can set, clear, complement, or test bits by means of logical
operations with appropriate masks. Shift instructions can rotate or shift the
accumulator or a memory location. Chapter 7 contains additional examples of bit
manipulation.
You may operate on individual bits in the accumulator as follows:
4. Test bit 5 of the accumulator. Clear the Zero flag if bit 5 is a logic and
1 set
the Zero flag if bit 5 is a logic 0.
You can change more than one bit at a time by changing the masks.
5. Set bits 4 and 5 of the accumulator.
LDA $40
ORA $%00010000 ;SET BIT 4 BY ORING WITH 1
STA $40
•
Clear bit 1 of memory location 17E0 I6 .
LDA $17E0
AND #%11111101 ;CLEAR BIT 1 BY ANDING WITH
STA $17E0
INC. These shortcuts are useful when you are storing a single 1-bit flag in a byte
of memory.
The instruction LSR (ASL) shifts the accumulator or a memory location right
(left) one position, filling the leftmost (rightmost) bit with a 0. Figures 1-1 and 1-
2 describe the effects of these two instructions. The instructions ROL and ROR
provide a circular shift (rotate) of the accumulator or a memory location as shown
in Figures 1-3 and 1-4. Rotates operate as if the accumulator or memory location
and the Carry flag formed a 9-bit circular register. You should note the following:
Left shifts set the Carry to the value that was in bit position 7 and the Nega-
•
•
Right shifts set the Carry to the value that was in bit position 0.
•
Rotates preserve all the bits, whereas LSR and ASL destroy the old Carry
fiag.
Rotates allow you to move serial data between memory or the accumulator
•
and the Carry flag. This is useful in performing serial I/O and in handling single
bits of information such as Boolean indicators or parity.
Multibit shifts simply require the appropriate number of single-bit instruc-
tions.
Examples
1. Rotate accumulator right three positions.
ROR A
ROR A
ROR A
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 1 9
HT^
After ASL (Arithmetic Shift Left)
B7 B 6 B 5 B4 B 3 B 2 B, Bo
E B, B
B„ B 7 B 6 B 5 B4 B 3 B 2 B,
M B, B6 B 5 B4 B 3 B 2
After R(3R (Rot ate Right)
B, Bo
Carry Data
B. C B 7 B6 B 5 B4 B3 B2 B,
ASL $1700
ASL $1700
ASL $1700
ASL $1700
An alternative approach would be to use the accumulator; that is,
LDA $1700
ASL A
ASL A
ASL A
ASL A
STA $1700
is shorter (10 bytes rather than 12) and faster (16
clock
The second approach
cycles rather than 24), but it destroys the previous contents of the accumulator.
You can implement arithmetic shifts by using the Carry flag to preserve the
bit 7. Shifting right arithmetically is called sign extension, since it
current value of
copies the sign bit to the right. A shift that operates in this manner preserves the
complement number and can therefore be used to divide or nor-
sign of a two's
malize signed numbers.
Examples
1. Shift the accumulator right 1 bit arithmetically, preserving the sign (most
"significant) bit.
the processor performs ROR A, it moves the Carry (the old bit 7)
to bit 7
When
and bit 7 to bit 6, thus preserving the sign of the original number.
2. Shift the accumulator left 1 bit arithmetically, preserving the sign (most sig-
nificant) bit.
TXA
ROR A ; SHIFT THE ACCUMULATOR, PRESERVING BIT 7
or
ASL A SHIFT A, MOVING BIT 7 TO CARRY
BCC CLRSGN WAS BIT 7 1?
ORA #%10U00000 YES, THEN KEEP IT 1
BMI EXIT
CLRSGN AND #%01111111 NO, THEN KEEP IT ZERO
EXIT NOP
MAKING DECISIONS
We will now discuss procedures for making three types of decisions:
The first type of decision allows the processor to sense the value of a flag,
switch, status line, or other binary (ON/OFF) input. The second type of decision
allows the processor to determine whether an input or a result has a specific value
(e.g., an input is a specific character or terminator or a result is 0). The
third type
of decision allows the processor to determine whether a value is above or below
a
numerical threshold (e.g., a value is valid or invalid or is above or below a warn-
ing level or set point). Assuming that the primary value is in the accumulator
and
the secondary value (if needed) is in address ADDR, the procedures are as
follows.
LDA #%00100000
BIT ADDR
BNE DEST
We must reverse the order of the operations, since BIT does not allow immediate
addressing. It does, however, leave the accumulator unchanged for later use.
22 6502 ASSEMBLY LANGUAGE-SUBROUTINES
available readily as the Negative flag after a Load or Transfer instruction; bit
can
be moved to the Carry with LSR A or ROR A; bit 6 can be moved to the Negative
flag with ASL A or ROL A.
Note that LDA affects the Zero and Negative flags; so do transfer instructions
such asTAX, TYA, TSX (but not TXS), and PLA. Store instructions (including
PHA) do not affect any flags.
4. Branch to DEST if bit 6 of the accumulator is 0.
The BIT instruction has a special feature that allows one to readily test bit 6 or
memory location. When the processor executes BIT, it sets the Negative
bit 7 of a
flag to the value of bit 7 of the addressed memory location and
the Overflow flag
instruction (CMP) is more useful than the Subtract instruction (SBC) because
Compare does not change the accumulator or involve the Carry.
Examples
1. Branch to DEST if the accumulator contains the number VALUE.
CMP #VALUE ;IS DATA = VALUE?
BEQ DEST ;YES, BRANCH
We could also use index register X with CPX or index register Y with CPY.
2. Branch to DEST if the contents of the accumulator are not equal to the con-
tents of memory location ADDR.
CMP ADDR ;IS DATA = VALUE IN MEMORY?
BNE DEST ;NO, BRANCH
3. Branch to DEST if memory location ADDR contains 0.
LDA ADDR ;IS DATA ZERO?
BEQ DEST ;YES, BRANCH
4. Branch to DEST if memory location ADDR contains 0", but do not change
the accumulator or either index register.
INC does not affect the Carry flag, but it does affect the Zero flag. Note that you
cannot increment or decrementthe accumulator with INC or DEC.
• Determine if the contents of the accumulator are greater than or less than
some other value by subtraction. If, as is typical, the numbers are unsigned, the
Carry flag indicates which one is larger. Note that the 6502's Carry flag is a nega-
tiveborrow after comparisons or subtractions, unlike the true borrow produced
by such processors as the 8080, Z-80, and 6800. In general,
.
•
Carry = 1 if the contents of the accumulator are greater than or equal to the
value subtracted from it. Carry = 1 if the subtraction does not require (generate)
a borrow.
•
Carry = if the value subtracted is larger than the contents of the accumula-
tor. That is, Carry = if the subtraction does require a borrow.
Examples
1 DEST if the contents of the accumulator are greater than
Branch to or equal
The Carry is set to 1 if the unsigned subtraction does not require a borrow.
2. Branch to DEST if the contents of memory address OPER1 are less than the
3. Branch to DEST if the contents of memory address OPERl are less than or
•
OPERl greater than or equal to OPER2 (Carry set)
•
OPERl less than OPER2 (Carry cleared)
opposite order.
If the values are signed, we must allow for the possible occurrence of two's
complement the situation in which the difference between the
overflow. This is
numbers cannot be contained in seven bits and, therefore, changes the sign of the
result. For example, if one number is +7 and the other is
- 125, the difference is
CHAPTER 1 :. GENERAL PROGRAMMING METHODS 25
- 132, which is beyond the capacity of eight bits (it is less than - 128, the most
negative number that can be contained in eight bits).
Thus, in the case of signed numbers, we must allow for the following two
possibilities:
• The result has the sign (positive or negative, as shown by the Negative flag)
that we want, and the Overflow flag indicates that' the sign is correct.
• The result does not have the sign that we want, but the Overflow flag indi-
cates that two's complement overflow has changed the real sign.
We have to look for both a true positive (the sign we want, unaffected by over-
flow) or a false negative (the opposite of the sign we want, but inverted by two's
complement overflow).
Examples
1. Branch to DEST if the contents of the accumulator (a signed number) are
greater than or equal to the number VALUE.
Note that we must set the Carry and use SBC, because CMP does not affect the
Overflow flag.
Tables 1-8 and 1-9 summarize the common instruction sequences used to
make decisions with the 6502 microprocessor. Table 1-8 lists the sequences that
depend only on the value in the accumulator; Table 1-9 lists the sequences that
depend on numerical comparisons between the contents of the accumulator and a
memory location. Tables 1-10 and 1-1 1 contain
specific value or the contents of a
the sequences that depend on an index register or on the contents of a memory
location alone.
26 6502 ASSEMBLY LANGUAGE SUBROUTINES
Conditional
Condition Flag-Setting Instruction
Branch
Any bit A =
of AND#MASK (1 in bit position) BEQ
Any bit A =
of 1 AND #MASK (1 in bit position) BNE
Bit 7 of A = ASL A or ROL A BCC
CMP#0 (preserves A) BPL
Bit 7 of A = 1 ASL A or ROL A BCS
CMP #0 (preserves A) BMI
Bit A
6 of = ASL A or ROL A BPL
Bit 6 of A = 1 ASL A or ROL A BMI
Bit of A = LSR A or ROR A BCC
Bit of A = 1 LSR A or ROR A BCS
(A) = LDA, PLA, TAX, TAY, TXA, or TYA BEQ
(A) +0 LDA, PLA, TAX, TAY, TXA, or TYA BNE
(A) positive (MSB = 0) LDA, PLA, TAX, TAY, TXA, or TYA BPL
(A) negative (MSB = 1) LDA, PLA, TAX, TAY, TXA, or TYA BMI
Conditional
Condition Flag-Setting Instruction
Branch
LOOPING
The simplest way to implement a loop (that is, repeat a sequence of instruc-
tions) with the 6502 microprocessor is as follows:
1. Load an index register or memory location with the number of times the
sequence is to be executed.
instructions to be repeated
DEX
BNE LOOP
Nothing except clarity stops us from counting up (using INX, INY, or INC); of
course, you must change the initialization appropriately. As we will see later, a
16-bit counter is much easier to increment than it is to decrement. In any case,
the instructions to be repeated must not interfere with the counting of the repeti-
tions. You can store the counter in either index register or any memory
location.
Index register X's special features are its use in preindexing and the wide
availability of zero page indexed modes. Index register Y's special feature is its
use in postindexing. As usual, memory locations on page are shorter and faster
to use than are memory
locations on other pages.
Of course, you use an index register or a single memory location as a
if
counter, you are limited to 256 repetitions. You can provide larger numbers of
repetitions by nesting loops that use a single register or memory location or by
• Nested loops
LDX #NTIMM ; START OUTER COUNTER
LOOPO LDY #NTIML ; START INNER COUNTER
LOO PI
instructions to be repeated
(NTIML) after each decrement of the outer counter (index register X) The nest- .
ARRAY MANIPULATION
The simplest way to access a particular element of an array is by using indexed
addressing. One can then
1. Manipulate the element by indexing from the starting address of the array.
2. Access the succeeding element (at the next higher address) by increment-
ing the index register using INX
INY, or access the preceding element (at the
or
next lower address) by decrementing the index register using DEX or DEY. One
30 6502 ASSEMBLY LANGUAGE SUBROUTINES
could also change the base; this is simple if the base is an absolute address, but
awkward if it is an indirect address.
Access an arbitrary element by loading' an index register with its index.
3.
Typical array manipulation procedures are easy to program if the array is one-
dimensional, the elements each occupy 1 byte, and the number of elements is
less than 256. Some examples are
•
Add an element of an array to the accumulator. The base address of the array
is a constant .BASE. Update index register X so that it refers to the succeeding 8-
bit element.
ADC BASE,X ;ADD CURRENT ELEMENT
INX ;ADDRESS NEXT ELEMENT
•
Check an element of an array is
to see if and add 1 to memory location
ZCOUNT if it is. Assume that the address of the array is a constant BASE and its
index is in index register X. Update index register X so that it refers to the pre-
•
Load the accumulator with the 35th element of an array. Assume that the
The most efficient way to process an array is to start at the highest address and
work backward. This is the best approach because it allows you to count the index
register down to and exit when the Zero flag is set. You must adjust the
initialization and the indexed operations slightly to account for the fact that the
index is never used. The changes are
•
Use the base address START- 1, where START is the lowest address
actually occupied by the array.
Manipulating array elements becomes more difficult if you need more than
one
element during each iteration (as in a sort that requires interchanging of ele-
ments), if the elements are more than one byte long, or if the elements are them-
selves addresses (as in a table of starting addresses). The basic problem is
the lack
of 16-bit,registers or 16-bit instructions. The processor can never be
instructed to
handle more than 8 bits. Some examples of more general array manipulation are
array (storedLSB first). The base address of the array is BASE and the index of
the element is in index register X. Update X so that it points to
the next 16-bit
-
element.
LDA BASE,X ;GET LSB OF ELEMENT
STA POINTL
INX
LDA BASE,X ;GET MSB OF ELEMENT
STA POINTH
!NX ;ADDRESS NEXT ELEMENT
The single instruction LDA BASE+1,X loads the accumulator from the same
address as the sequence
INX
LDA BASE,X
assuming that X did not originally contain FF 16 . If, however, we are using a base
address indirectly, the alternatives are
• Exchange an element of an array with its successor if the two are not already
in descending order. Assume that the elements are 8-bit unsigned
numbers. The
base address of the array is BASE and the index of the first number is in index
register X.
addressing mode.
LDX #24 ;GET DOUBLED OFFSET FOR INDEX
LDA (BASE,X) ;LOAD FROM INDEXED INDIRECT ADDRESS
We can generalize array processing by storing the base address in two locations
on page and using the postindexed (indirect indexed) addressing mode.
Now
This mode assumes the use of page and index
the base address can be a variable.
set of instructions.
register Y and is available only for a limited
Examples
of the
1 Add an element of an array to the accumulator. The base address
element
array is in memory locations PGZRO and PGZRO+1. The index of the
8-
is in index register Y. Update index register Y so that it refers to the succeeding
bit element.
CLC ADC (PGZRO) ,Y ;ADD CURRENT ELEMENT
IN y ; ADDRESS NEXT ELEMENT
described
by a complemented count in memory locations COUNTH and
COUNTL and an initial base address in memory locations PGZRO and
PGZRO+1, we can use the following program:
LDA #0 ;DATA = ZERO
TAY ;INDEX = ZERO
CLEAR STA (PGZRO), ;CLEARABYTE
INY ;MOVE TO NEXT BYTE
BNE CHKCNT
INC PGZRO + 1 ;AND TO NEXT PAGE IF NEEDED
CHKCNT INC COUNTL ;COUNT BYTES
BNE CLEAR
INC COUNTH ;WITH CARRY TO MSB
BNE CLEAR
The idea here proceed to the next page by incrementing the more significant
is to
byte of the indirect address when we finish a 256-byte section.
One can also simplify array processing by reducing the multiplications required
in indexing to additions. In particular, one can handle
arrays of two-byte elements
by using ASL A to double an index in the accumulator.
Example
Load the accumulator from the indirect address indexed by the contents of
memory location INDEX. Assume that the table starts at address BASE.
TABLE LOOKUP
manipulation.
Table lookup can be handled by the same procedures as array
Some examples are
address of the table is BASE (a constant) and the 8-bit index is in memory loca-
tion INDEX.
LDX INDEX ;GET INDEX
LDA BASE,X ;GET THE ELEMENT
•
Load the accumulator with an element from a table. Assume that the base
table. Assume that the base address of the table is BASE (a constant), and the
index is in memory location INDEX.
We can also handle the case in which the base address is a variable in two memory
locations on page (PGZRO and PGZRO + 1).
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 35
the only 6502 instruction that has true indirect addressing. Note
that
JMP is
TEMP and TEMP+ 1 can be anywhere in memory; they need not be on page 0.
•
RTS adds 1 program counter after loading it from the stack. Thus, the
to the
the table must all be one less than the actual values to
which you
addresses in
execution of
wish to transfer control. This offset evidently speeds the processor's
instruction, but also can confuse, the pro-
the JSR (Jump to Subroutine) it
grammer.
You must remember that the stack is growing down in memory, toward
•
significant byte at lower address), we must push the more significant byte first.
This is essentially a double negative; we store the address in the wrong order but
it ends up right because the stack is growing down.
•
The use of RTS How can one return from a routine that one has
is confusing.
never called? In fact, this approach uses RTS to call a subroutine. You should
remember that RTS is simply a jump instruction that obtains the new value for
the program counter from the top of the stack. While the common use of RTS is
The common uses of jump tables are to implement CASE statements (for
example, multiway branches as used in languages such as FORTRAN, Pascal,
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 37
and PL/I) to decode commands from a keyboard, and to respond to function keys
on a terminal.
CHARACTER MANIPULATION
The easiest way to manipulate characters is to treat them as unsigned 8-bit
numbers. The letters and digits form ordered subsequences of the ASCII charac-
ters; for example, the ASCII representation of the letter A is one less
than the
ASCII representation of the letter B. Handling one character at a time is just like
handling normal 8-bit unsigned numbers. Some examples are
CODE CONVERSION
You
can convert data from one code to another using arithmetic or logical
operations(if the relationship is simple) or lookup tables (if
the relationship is
complex).
38 6502 ASSEMBLY LANGUAGE SUBROUTINES
Examples
1. Convert an ASCII digit to its binary-coded decimal (BCD) equivalent.
SEC ; CLEAR THE INVERTED BORROW
SBC #'0 .-CONVERT ASCII TO BCD
Since the ASCII digits form an ordered subsequence, all you must do is subtract
You can also clear bit positions 4 and 5 with the single instruction
AND #%11001111 :CONVERT ASCII TO BCD
Either the arithmetic sequence or the logical instruction will, for example,
con-
The inverse'conversion is equally simple. You can also set bit positions 4 and 5
with the single instruction
ORA #%00110000 .-CONVERT BCD TO ASCII
con-
Either the arithmetic sequence or the logical instruction will, for example,
vert decimal 6 (06 16
) to ASCII 6 (36 I6
).
3.Convert one 8-bit code to another using a lookup table. Assume that the
lookup table starts at address NEWCD and is indexed by the value in the
original
code (for example, the 27th entry is the value in the new code corresponding to
27 in the original code). Assume that the data is in memory location CODE.
LDX CODE ;GET THE OLD CODE
LDA NEWCD, X ;CONVERT IT TO THE NEW CODE
MULTIPLE-PRECISION
ARITHMETIC
Multiple-precision arithmetic requires a series of 8-bit operations. One must
starting
•
Clear the Carry before starting addition or set the Carry before
subtraction, since there is never a carry into or borrow from the least significant
byte.
•
Use the Add with Carry (ADC) or Subtract with Borrow (SBC) instruction
to perform an 8-bit operation and include the carry or
borrow from the previous
operation.
CHAPTER 1 : GENERAL PROGRAMMING METHODS 39
MULTIPLICATION AND
DIVISION
Multiplication can be implemented in a variety of ways. One technique is to
convert simple multiplications to additions or left shifts.
Examples
1. Multiply the contents of the accumulator by 2.
ASL A ; DOUBLE A
2. Multiply the contents of the accumulator by 5.
STA TEMP
ASL A ;A TIMES 2
ASL A ;A TIMES 4
ADC TEMP ;A TIMES 5
This approach assumes that shifting the accumulator left never produces a
carry. This approach is often handy in determining the locations of elements of
two-dimensional arrays. For example, let us assume that we have a set of tem-
perature readings taken at four different positions in each of three different tanks.
We organize the readings as a two-dimensional array TO, J), where I is the tank
number (1, 2, or 3) and J is the number of the position in the tank (1, 2, 3, or 4).
We store the readings in the linear memory of the computer one after another as
follows, starting with tank 1:
Example
Divide the contents of the accumulator by 4.
LSR A ;DIVIDE BY 2
LSR A ;AND BY 2 AGAIN
Ifyou are multiplying or dividing signed numbers, you must be careful to sepa-
rate the signs from the magnitudes. You must replace logical shifts with
arithmetic shifts that preserve the value of the sign bit.
•
Algorithms involving and additions (multiplication) or
shifts shifts and
subtractions (division) can be used as described in Chapter 6.
•
Lookup tables can be used as discussed previously in this chapter.
LIST PROCESSING 5
Lists can be processed like arrays the elements are stored in consecutive
if
addresses. If the elements are queued or chained, however, the limitations of the
instruction set are evident in that
CHAPTER 1 : GENERAL PROGRAMMING METHODS 41
Examples
1. Retrieve an address stored starting at the address in memory locations
PGZRO and PGZRO + 1. Place the retrieved address in memory locations
POINTL and POINTH.
LDY #0 ; INDEX = ZERO
LDA (PGZRO) ,* ;GET LSB OF ADDRESS
STA POINTL
INY
LDA (PGZRO) ,¥ ;GET MSB OF ADDRESS
STA POINTH
This procedure allows you to move from one element to another in a linked list.
2. Retrieve data from the address currently in memory locations PGZRO and
PGZRO+1 and increment that address by 1.
INY
STA (PGZRO) ,Y
CLC ; INCREMENT POINTER BY 2
LDA PGZRO
ADC #2
STA PGZRO
BCC DONE ;WITH CARRY IF NECESSARY
INC PGZRO+1
DONE NOP
42 6502 ASSEMBLY LANGUAGE SUBROUTINES
This procedure you build a list of addresses. Such a list could be used, for
lets
Examples
1. Queues or linked lists. Assume that we have a queue header consisting of
the address of the element in memory locations HEAD and HEAD + 1 (on
first
page 0). If there are no elements in the queue, HEAD and HEAD + 1 both con-
tain 0. The first two locations in each element contain the address of the next ele-
ment or if there is no next element.
•
Add the element in memory locations PGZRO and PGZRO + 1 to the head
of the queue.
•
Remove an element from the head of the queue and set the Zero flag if no
element is available.
STA PGZRO+1
ORA PGZRO ;ANY ELEMENTS IN QUEUE?
BEQ DONE ;NO, DONE (LINK = 0000)
LDA (PGZRO) ,Y ;YES, MAKE NEXT ELEMENT NEW HEAD
STA (HEAD) ,Y
DEY
LDA (PGZRO) ,Y
STA (HEAD) ,Y
INY ;CLEAR ZERO FLAG BY MAKING Y 1
DONE NOP
Note that we can use the sequence
LDA ADDR
ORA ADDR+1
to test the 16-bit number in memory locations ADDR and ADDR+1. The Zero
flag is set only if both bytes are 0.
The address of the next empty location is in addresses SPTR and SPTR+ 1 on
page 0. The lowest address that the stack can occupy is LOW and the highest
address is HIGH.
• If the stack overflows, clear the Carry flag and exit. Otherwise, store the
accumulator in the stack and increase the stack pointer by 1. Overflow means that
the stack has exceeded its area.
stack pointer by 1 and load the accumulator from the stack. Underflow
means
that there is nothing left in the stack.
1. In registers. Three 8-bit registers are available (A, X, and Y). This
approach is adequate in simple cases but it lacks generality and can handle only a
limited number of parameters. The programmer must remember the normal uses
of the registers in assigning parameters. In other words,
•
The accumulator is the obvious place to put a single 8-bit parameter.
•
Index register X is the obvious place to put an index, since it is the most
accessible and has the most instructions that use it for addressing. Index register
X is also used in preindexing (indexed indirect addressing).
•
Index register Y is used in postindexing (indirect indexed addressing).
This approach is reentrant as long as the interrupt service routines save and
restore all the registers.
trol to the subroutine. This approach is general and can handle any number of
parameters, but it requires a large amount of management. If you assign different
areas of memory for each call or each routine, you are essentially creating your
own you use a
stack. If common area of memory, you
lose reentrancy. In this
interference between routines, and saving and restoring the pointers required to
resume routines after subroutine calls or interrupts. The extra memory locations
on page must be treated like registers.
•
The starting address of the memory area minus 1 is at the top of the stack.
the normal return address, which is 1 larger than
That is, the starting address is
the address the 6502's JSR instruction saves in the stack. You can move the start-
ing address to memory locations RETADR and RETADR+1 on page with the
following sequence:
;GET LSB OF RETURN ADDRESS
STA RETADR
PLA ;GET MSB OF RETURN ADDRESS
STA RETADR+1
INC RETADR ;ADD 1 TO RETURN ADDRESS
BNE DONE
INC '
RETADR+1
DONE NOP
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 45
Now we can access the parameters through the indirect address. That is, you can
load the accumulator with the first parameter by using the sequence
Subroutine call
JSR SUBR EXECUTE SUBROUTINE
.BYTE PAR8 8-BIT PARAMETER
.WORD PARI 16-BIT PARAMETER
. next instruction
. .
Subroutine
SUBR PLA ;GET LSB OF PARAMETER ADDRESS
STA RETADR
PLA ;GET MSB OF PARAMETER ADDRESS
STA RETADR+1
LDY #1 ;ACCESS FIRST PARAMETER
LDA (RETADR), Y ;GET FIRST PARAMETER
TAX
INY
LDA (RETADR), Y ,-ACCESS LSB OF 16-BIT PARAMETER
STA PGZRO
INY
LDA (RETADR), Y ;GET MSB OF 16-BIT PARAMETER
46 6502 ASSEMBLY LANGUAGE SUBROUTINES
STA PGZRO+1
LDA RETADR ; CALCULATE ACTUAL RETURN ADDRESS
CLC
ADA #3
TAY
BCC STRMSB
INC RETADR+1
STRMSB LDA RETADR+1 ; PUT RETURN ADDRESS ON TOP OF STACK
PHA
TYA
PHA
The sequence pops the return address from the top of the stack (JSR saved
initial
parameter. Remember that JSR actually saves the return address minus 1; that is
why we must start the index at rather than at 0. Finally, adding 3 to the return
1
address and saving the sum in the stack lets a final RTS instruction transfer con-
•
JSR stores the return address at the top of the stack. The parameters that the
calling routine placed in the stack begin at address Olss + 3, where ss is the con-
tents of the stack pointer. The 16-bit return address occupies the top two loca-
tions always refers to the next empty address, not the
and the stack pointer itself
its parameters, it must
last occupied one. Before the subroutine can obtain
remove the return address from the stack and save it somewhere.
•
The only way determine the value of the stack pointer is
for the subroutine to
by using the instruction TSX. After TSX has been executed, you can
access the
top of the stack by indexing with register X from the base address 0101 16
The .
•
The calling program must place the parameters in the stack before calling the
subroutine.
or
LDX #NRESLT ; COUNT = NRESLT
PUSHB PHA ;MOVE STACK POINTER DOWN 1
DEX
BNE PUSHB
Either approach leaves NRESLT empty locations at the top of the stack as shown
in Figure 1-5. Of course, if NRESLT is 1 or 2, simply executing PHA the
appropriate number of times will be much faster and shorter. The same
approaches can be used to provide stack locations for temporary storage.
Example
Assume that subroutine SUBR requires an 8-bit parameter and a 16-bit
parameter, and that it produces two 8-bit results. Show a call of SUBR, the
removal of the return address from the stack, and the cleaning of the stack after
the return. Figure 1-6 shows the appearance of the stack initially, after the
subroutine call, and at the end. If you always use the stack for parameters and
results, you will generally keep the parameters at the top of the stack in the proper
order. Then you will not have to save the parameters or assign space in the stack
for the results (they will replace some or all of the original parameters). You will,
however, have to assign space on the stack for temporary, storage to maintain
generality and reentrancy.
Calling program
Subroutine
Olss -NRESLT
I:
Empty space
for storing
results in the
Olss stack
t
Stack Stack
Pointer Pointer
Olss -7
i
LSB of return
address
MSB of return
address
8-bit parameter
LSB of 16-bit
parameter
MSB of 16-bit Olss -2
parameter
Empty byte for Result #1
result #1
I
Stack Sti ick St nek
I'ointer Poi nter Poi nter
SIMPLE INPUT/OUTPUT
Simple input/output can be performed using any memory addresses and any
instructions that reference memory. The most common instructions are the
following:
• LDA (load accumulator) transfers eight bits of data from an input port to the
accumulator.
• STA (store accumulator) transfers eight bits of data from the accumulator to
an output port.
Other instructions can combine the input operation with arithmetic of logical
operations. Typical examples are the following:
• AND logically ANDs the contents of the accumulator with the data from an
input port.
BIT logically ANDs the contents of the accumulator with the data from an
•
input port but does not store the result anywhere. It does, however, load the
Negative flag with bit 7 of the input port and the Overflow flag with bit 6, regard-
less of the contents of the accumulator.
• CMP subtracts the data at an input port from the contents of the accumula-
tor, setting the flags but leaving the accumulator unchanged.
Instructions that operate on data in memory can also be used for input or out-
put. Since these instructions both read and write memory, their effect on input
and output ports may be difficult to determine. Remember, input ports cannot
generally be written, nor can output ports generally be read. The commonly
used
instructions are the following:
• ASL shifts its data to the left, thus moving bit [7 to the Carry for possible
serial input.
• ROR rotates its data to the right, thus moving the old Carry to bit 7 and mov-
ing bit to the Carry.
• ROL rotates its data to the left, thus moving the old Carry to bit and mov-
ing bit 7 to the Carry.
The effects of these instructions on an input port are typically similar to their
effects on a ROM location. The microprocessor can read the data, operate on it,
and set the flags, but it cannot store the result back into memory. The effects on
50 6502 ASSEMBLY LANGUAGE SUBROUTINES
an output port are even stranger, unless the port is latched and buffered. If it is
not, the data that the processor reads is system-dependent and typically has no
connection with what was last stored there.
Examples
1. Perform an 8-bit input operation from the input port assigned to memory
address B000 16 .
3. Set the Zero flag if bit 5 of the input port assigned to memory address
75D0 16 isO.
LDA #%00100000 ;GET MASK
AND $75D0 ;SET FLAG IF BIT 5 IS ZERO
If the bit position of interest is number 6, we can use the single instruction
BIT $75D0
to set the Overflow flag to its value.
4. Set the Zero flag if the data at the input port assigned to memory address
1700 l6 islB l6 .
LDA #$1B
CMP $1700
5. Load the Carry flag with the data from bit 7 of the input port assigned to
memory address 33A5 I6 .
ASL $33A5
Note does not change the data in memory location 33A5 16
that this instruction
unless that location is latched and buffered. If, for example, there are eight simple
switches attached directly to the port, the instruction will surely have no effect on
whether the switches are open or closed.
6. Place a logic 1 in bit of the output port assigned to memory address B070 16 .
LDA $B070
ORA #%00000001
STA SB070
If none of the other bits in address B070 are connected, we can use the sequence
l(;
SEC
ROL SB070
1
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 5
If we know that bit of address B070 is currently a logic 0, we can use the single
l6
instruction
INC SB070
All of these alternatives will have strange effects memory
if address B070, can-
not be read. The first two will surely make bit a logic 1, but their effects on the
other bits are uncertain. The outcome of the third alternative
would be a total
mystery, since we would have no idea what is being incremented. We can
avoid
the uncertainty by saving a copy of the data in RAM
location TEMP. Now we can
operate on the copy using
LDA TEMP ;GET COPY OF OUTPUT DATA
ORA #%00000001 ;SET BIT
STA SB070 ,-OUTPUT NEW DATA
STA TEMP ;AND SAVE A COPY OF IT
LOGICAL AND
PHYSICAL DEVICES
One way to select I/O devices by number is to use an I/O device table. An I/O
device table assigns the actual I/O addresses (physical devices) to
the device num-
bers (logical devices) to which a program refers. Using this method,
a program
written in a high-level language may refer to device number 2 for input and num-
ber 5 for output. For testing purposes, the operator may assign devices numbers 2
and 5 to be the input and output ports, respectively, of his or her
terminal. For
normal stand-alone operation, the operator may assign device number
2 to be an
analog input unit and device number 5 the system printer. If the
system is to be
operated by remote control, the operator may assign devices numbers
2 and 5 to
be communications units used for input and output.
One way to provide this distinction between logical and physical devices is
to
use the 6502's indexed indirect addressing or preindexing. This
mode assumes
that the device table is located on page and is accessed via an index in register X.
If we have a device number in memory location DEVNO, the following programs
can be used:
In both cases, that the I/O device table starts at address DEVTAB (on
we assume
page 0) and consists of 2-byte addresses. Note that the 6502 provides an appropri-
ate addressing method, but does not produce any error messages if the pro-
grammer uses that method improperly by accessing odd addresses or by indexing
off theend of page (the processor does provide automatic wraparound). In real
applications (see Chapter 10), the device table will probably contain the
starting
If address CNTL cannot be read properly, we can use a copy in memory address
TEMP.
LDA TEMP GET CURRENT DATA FROM PORT
ORA #%00100000 SET BIT 5
STA CNTL RESTORE DATA TO PORT
STA TEMP UPDATE COPY OF DATA
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 53
You must update the copy every time you change the data.
PERIPHERAL CHIPS
The major peripheral chips in 6502-based microcomputers are the 6520 and
6522 (known as the Peripheral Interface Adapter or PIA and
parallel interfaces
the Versatile Interface Adapter or VIA, respectively), the 6551
and 6850 serial
interfaces (known as Asynchronous Communications Interface Adapters or
ACIAs), and the 6530 and 6532 multifunction devices (known as ROM-I/O-
timers or RAM-I/O-timers or ROM-RAM-I/O-timers, abbreviated
RIOT or
RRIOT and sometimes called combo chips). All of these devices can perform a
variety of functions, much like the microprocessor itself.
Of course, peripheral
chips perform fewer different functions than processors and the
range of func-
tions is limited. The idea behind programmable peripheral
chips is that each con-
tains many useful circuits; the designer selects the ones he or she wants
to use by
storing one or more selection codes in control registers, much as one selects
a
particular circuit from a Designer's Casebook by turning to
a particular page. The
advantages of programmable chips are that a single board containing
such devices
can handle many applications and changes, or, corrections can
be made by chang-
ing selection codes rather than by redesigning circuit boards.
The disadvantages
54 6502 ASSEMBLY LANGUAGE SUBROUTINES
of determining
of programmable chips are the lack of standards and the difficulty
how specific chips operate.
Chapter 10 contains typical initialization routines for the 6520, 6522, 6551,
in detail in the
6850, 6530, and 6532 devices. These devices are also discussed
Osborne 4 and 8-Bit Microprocessor Handbook 1
We will provide
.
only a brief over-
•
Data Direction Register determines whether the pins on port A
A (DDRA)
are inputs (0s) or outputs (Is). direction register determines only the
A data
direction in which traffic flows; you may compare it to a directional arrow that
indicates which way traffic can move on a highway lane or railroad track. The data
direction registerdoes not affect what data flows or how often it changes; it only
affects the direction. Each pin in the I/O port has a corresponding bit in the data
individually as either an
direction register, and thus, each pin can be selected
most common choices are to make an entire 8-
input or an output. Of course, the
in all eight bits of the data direc-
bit I/O port input or outport by storing 0s or Is
tion register.
•
Data Direction Register B (DDRB) similarly determines whether the pins in
Select Lines
Label Addressed Location
7 6 5 4 3 2 10 - Bit Number
-Peripheral Conirol register
transition of CAl
-000 CA2 input mode On interrupt
[
Request interrupt on
001 CA2 independent input mode I high-to-low CA2 transition request set
010 CA2 input mode I
Requesl inlerrU pi on Interrupt Flag
001
high-to-low CB2 transition request set
010 CB2 input mode
j
Figure 1-7: Bit Assignments for the 6522 VIA's Peripheral Control Register
7 6 5 4 3 2 10 -Bit Number
-Auxiliary Control register
n I
Register
Figure 1-8: Bit Assignments for the 6522 VIA's Auxiliary Control
CHAPTER 1 : GENERAL PROGRAMMING METHODS 57
In order to initialize a VIA properly, we must know what its start-up state is.
Reset clears all the VIA registers, thus making all the data and control lines
inputs, disabling all latches, interrupts, and other functions, and clearing all
status bits.
The data direction registers are easy to initialize. Typical routines are
LDA #$11110000
STA DDRA
• Make bit of port B input, bits 1 through 7 output.
LDA #$11111110
STA DDRB
Bit could, for example, be a serial input line.
The Peripheral Control Register is more difficult to initialize. We will briefly
discuss the purposes of the control lines before showing some examples.
Control lines CA1, CA2, CB1, and CB2 are basically intended for use as
handshaking handshake, the sender indicates the availability of data
signals. In a
by means of a transition on a serial line; the transition tells the'receiver that new
data is available to it on the parallel lines. Common names for this serial line are
VALID DATA, DATA READY, and DATA AVAILABLE. In response to this
signal, the receiver must accept the data and indicate its acceptance by means of a
transition on another serial line. This transition tells the sender that the latest
parallel data has been accepted and that another transmission sequence can begin.
Common names for the other serial line are DATA ACCEPTED, PERIPHERAL
READY, BUFFER EMPTY, and DATA ACKNOWLEDGE.
Typical examples of complete sequences are
•Input from a keyboard. When the operator presses a key, the keyboard pro-
duces a parallel code corresponding to the key and a transition on the DATA
READY or VALID DATA
line. The computer must determine that the transi-
has occurred, read the data, and produce a transition on the
tion DATA
ACCEPTED line to indicate that the data has been read.
• Output to a printer. The printer indicates to the computer that it is ready by
means of a transition on a BUFFER EMPTY or PERIPHERAL READY line.
Note that PERIPHERAL READY
is simply the inverse of ACCEPTED, DATA
since the peripheral obviously cannot be ready as long as it has not accepted the
58 6502 ASSEMBLY LANGUAGE SUBROUTINES
latest data. The computer must determine that the printer is ready, send the data,
and produce a transition on the DATA READY line to indicate that new data is
available. Of course, input and output are in some sense mirror images. In the
input case, the peripheral is the sender and the computer is the receiver; in the
output case, the computer is the sender and the peripheral is the receiver.
Thus, a chip intended for handshaking functions must provide the following
functions:
•
It must recognize the appropriate transitions on the DATA READY or PE-
•
must provide an indication
It that the transition has occurred in a form that is
output peripheral.
There are some obvious variations that the handshaking chip should allow for,
•
The may be either a high-to-low edge (a trailing edge) or a
active transition
low-to-high edge (a leading edge). If the chip does not allow either one, we will
need extra inverter gates in some situations, since both polarities are common.
•
The response may require either a high-to-low edge or a low-to-high edge. In
fact, it may require either a brief pulse or a long signal that lasts until the periph-
eral begins its next operation.
Experience has shown that the handshaking chip can provide still more conve-
nience, at virtually no cost, in the following ways:
READY lines, so that they are held until the computer is ready for them. The
computer need not monitor the status lines continuously to avoid missing a tran-
sition.
output port is written, thus preparing them for the next operation.
•
can produce the response automatically when an input port is read or an
It
option is known as an automatic mode. The problem with any automatic mode,
no
matter how flexible the designers make it, is that it will never satisfy all applica-
tions.Thus, most chips also provide a mode in which the program retains control
over the response; this mode is called a manual mode.
In cases where the peripherals are simple switches or lights and do
• not need
CHAPTER 1 : GENERAL PROGRAMMING METHODS 59
any status or control signals, the chip should allow independent operation of the
status lines. The designer can then use these lines (which would otherwise be
wasted) for such purposes as threshold detection, zero-crossing detection, or
clock inputs. In such cases, the designer wants the status and control signals to be
entirely independent of the operations on the parallel port. We should not have
any automatic clearing of latches or sending of responses. This is known as an
independent mode.
The 6522 programmer to provide any of
peripheral control register allows the
these functions. Bits through
govern the operation of port B and its control
3
signals; bits 4 through 7 govern the operation of port A and its control signals.
The status indicators are in the Interrupt flag register (see Figure 1-9). We may
characterize the bits in the control register as follows:
• Bit (for port A) and bit 4 (for port B) determine whether the active transi-
tion on control line 1high-to-low (0) or low-to-high (1). If control line 2 is an
is
extra input, bit 2 (for port A) and bit 6 (for port B) has a similar function.
• If control line 2 is an extra input, bit 1 (for port A) and bit 5 (for port B)
determine whether it operates independently of the parallel data port. This bit is
for normal handshaking and 1 for independent operation.
• Bit 3 (for port A) and bit 7 (for port B) determine whether control line 2 is an
extra input line (0) or an output response (1).
• If control line 2 is an output response, bit 2 (for port A) and bit 6 (for port B)
determine whether it operates in an automatic mode (0) or amanual mode (1).
• If control line 2 is being operated in the automatic mode, bit 1 (for port A)
and bit 5 (for port B) determine whether the response lasts for one clock cycle (1)
or until the peripheral produces another active transition on control line 1 (0).
• If control line 2 is being operated in the manual mode, bit 1 (for port A) and
bit 5 (for port B) determine its level.
7 6 5 4 3 2 1
m Bit Number
Bit Cleared by
Set by
Number
Bits 0, 1,3, and 4 are the I/O handshake signals. Bit 7 (IRQ) is 1 if any of the
interrupts is both active and enabled.
•
The peripheral indicates DATA READY or PERIPHERAL READY with a
These sequences do not depend on the contents of the peripheral control register,
since they do not change any of the bits except the one that controls the response.
Tables 1-13 and 1-14 summarize the operating modes for control lines CA2
and CB2. Note that the automatic output modes differ slightly in that port A pro-
duces a response after either read or write operations, whereas port B produces a
response only after write operations.
62 6502 ASSEMBLY LANGUAGE SUBROUTINES
Table 1-13: Operating Modes for Control Line CA2 of a 6522 VIA
Pulse Output Mode - CA2 goes low for one cycle following a read or
1 1
write of the Peripheral A Output register.
1 1 Manual Output Mode - The CA2 output is held low in this mode.
1 1 1 Manual Output Mode — The CA2 output is held high in this mode.
The auxiliary control register is less important than the peripheral control
register. Its bits have the following functions (see Figure 1-8):
•
Bits and 1, if set, cause the VIA to latch the input data on port A (bit 0) or
port B (bit 1) when an active transition occurs on control line 1. This option
allows for the case in which the input peripheral provides valid data only briefly,
and the data must be saved until the processor has time to handle it.
•
and 4 control the operations of the seldom-used shift register. This
Bits 2, 3,
register provides a simple serial I/O capability, but most designers prefer either to
use the serial I/O chips such as the 6551 or 6850 or to provide the entire serial
interface in software.
•
determines whether timer 2 generates a single time interval (the so-
Bit 5
called one-shot mode) or counts pulses on line PB6 (pulse-counting mode).
•
Bit 6 determines whether timer 1 generates one time interval (0) or operates
continuously (1), reloading its counters from the latches after each interval
elapses.
CHAPTER 1 ;
GENERAL PROGRAMMING METHODS 63
Table 1-14: Operating Modes for Control Line CB2 of a 6522 VIA
PCR7 PCR6 PCR5 Mode
Pulse Output Mode - Set CB2 low for one cycle following a write ORB
operation..
Manual Output Mode - The CB2 output is held low in this mode.
Manual Output Mode - The CB2 output is held high in this mode.
The uses of most of these functions are straightforward. They are not as com-
mon handshaking functions governed by the peripheral control register.
as the
You can also operate a 6522 VIA in an interrupt-driven mode. Interrupts are
enabled or disabled by setting bits in the interrupt enable register
(see Figures 1-
10 and 1-11) with bit 7 (the enable/disable flag) set (for enabling) or cleared (for
disabling). Interrupts can be recognized by examining the interrupt
flag register
(see Figure 1-9). Table 1-15 summarizes the setting and clearing
(resetting) of
interrupt flags on the 6522 VIA.
64 6502 ASSEMBLY LANGUAGE SUBROUTINES
7 6 5 4 3 1 -Bit Number
-Interrupt Flag register
:
Any active interrupt request
Figure 1-10: The 6522 VIA's Interrupt Flag and Interrupt Enable Registers
7 6 5 A 3 2 1
* Bit Number
1 1 1
Interrupt Enable Register
i .
Enable specified
the Interrupt
You can selectively enable or disable individual interrupts via
writing to the Interrupt
Enable register. You enable individual interrupts by ^
Enable register with a 1 in bit 7. Thus you could enable "time out for Timer 1
in the Interrupt Enable register:
and "active transitions of signal CB1" by storing D0 16
Set by Cleared by
4 Active transition of the signal on CB1 Reading from or writing to I/O Port B
By saving the program counter (more significant byte first) and the status
•
register in the stack in the order shown in Figure 1-12. Note that the status
register ends up on top of the program counter; the
sequence PHP, JSR would
produce the opposite order. The program counter value here is the address
of the
next instruction; there is no offset of 1 as there is with JSR.
• By disabling the maskable interrupt by setting the I flag in the status register.
• By fetching a destination address from a specified pair of memory addresses
(see Table 1-16) and placing that destination in the program
counter.
Thus, the programmer should consider the following guidelines when writing
interrupt-driven code for the 6502:
• The accumulator and index registers must be saved and restored explicitly if
the service routine changes them. Only the status register is saved automatically.
66 6502 ASSEMBLY LANGUAGE SUBROUTINES
Before After
Olss -4 Olss -4
Olss -3 Olss -3 Stack
Pointer
Olss -2 Olss -2 PP
Olss + 1
Olss + 2 Olss + 2
Stack Stack
The service routine must save the accumulator before it saves the index registers,
since can
it only transfer an index register to the stack via the accumulator. Typi-
cal saving and restoring sequences are
PHA ;SAVE ACCUMULATOR IN STACK
TXA ;SAVE INDEX REGISTER X
PHA
TYA ;SAVE INDEX REGISTER Y
PHA
•
The interrupt need not be reenabled explicitly, since the RTI (Return from
Interrupt) instruction restores the old status register as part of its execution. This
restores the original state of the Interrupt Disable flag. If you wish to return
with
interrupts disabled, you can set the Interrupt Disable flag in the stack with the
sequence
PLA ;GET STATUS REGISTER
ORA #%00000100 ;DISABLE INTERRUPT IN STACK
PHA ;PUT STATUS REGISTER BACK IN STACK
CHAPTER 1 :
GENERAL PROGRAMMING METHODS 67
The addresses are stored in the usual 6502 fashion with the less significant byte
at the lower address.
Note the convenience here of having the status register at the top, rather than
underneath the return address.
If you have code that the processor must execute with interrupts
•
disabled,
you can use SEI (Set Interrupt Disable) to disable maskable interrupts and ClI
(Clear Interrupt Disable) to enable them afterward. If the section of code could
be entered with interrupts either disabled or enabled, you must be sure to restore
the original state of the Interrupt Disable flag. That is, you must save and restore
the status register as follows:
• If you want to allow the user to select the actual starting address of the
ser-
vice routine, place an indirect jump at the vectored address. That is, the routine
starting at the vectored address is simply
• You must remember to save and restore incidental information that is essen-
tialfor the proper execution of the interrupted program. Such incidental
informa-
tion may include memory locations on page 0, priority registers
(particularly if
they are write-only), and other status.
• To achieve general reentrancy, you must use the stack for all temporary
storage beyond that provided by the registers. As we noted in the discussion
of
68 6502 ASSEMBLY LANGUAGE SUBROUTINES
parameter passing, you can assign space on the stack (NPARAM bytes) with the
sequence
TSX ;MOVE S OVER TO A
TXA
SEC ;ASSIGN NPARAM EMPTY BYTES
SBC INPARAM ;A GENERAL WAY TO ADJUST SP
TAX
TXS
Later, you can remove the temporary storage area with the sequence
TSX ;M0VE S OVER TO A
TXA
CLC
ADC #NPARAM ; REMOVE NPARAM EMPTY BYTES
TAX
TXS
If NPARAM is only 1 or 2, you can replace these sequences with the appropriate
number of push and pull instructions inwhich the data is ignored.
•
Theservice routine should initialize the Decimal Mode flag with either CLD
or SED if it uses ADC or SBC instructions. The old value of that flag is saved and
restored automatically as part of the status register, but the service routine
should
MAKING PROGRAMS
RUN FASTER
-
In general, you can a program run substantially faster by first determin-
make
ing where it is spending its time. This requires that you determine which loops
(other than delay routines) the processor is executing most often. Reducing the
the multiplying factor. It is thus critical to determine how often instructions are
being executed and to work on loops in the order of their frequency of execution.
Once you have determined which loops the processor executes most fre-
techniques:
quently, you can reduce their execution time with the following
added during each iteration or a special case that is being tested for repeatedly.
It
• Work backward through arrays rather than forward. This allows you to count
the index register down to and use the setting of the Zero flag as an exit condi-
tion. No explicit comparison is then necessary. Note that you will have to subtract
1 from the base addresses, since 1 is the smallest index that is actually used.
Increment 16-bit counters and indirect addresses rather than decrementing
•
them. 16-bit numbers are easy to increment, since you can tell if a carry has
occurred by checking the less significant byte for afterward. In the case of a
decrement, yOu must check for first.
• Use in-line code rather than subroutines. This will save at least a JSR instruc-
tion and an RTS instruction.
• Watch the special uses of the index registers to avoid having to move data
between them. The only register that can be
used in indirect indexed addressing
is register Y; the only register that can be used
in indexed indirect addressing or
in loading and storing the stack pointer is register X.
• Use the instructions ASL, DEC, INC, LSR, ROL, and ROR to operate
directly on data in memory without moving it to a register.
• Use the BIT instruction to test bits 6 or 7 of a memory location without load-
ing the accumulator.
• Use the CPX and CPY instructions to perform comparisons without using
the accumulator.
always resident. Then you can replace those sequences with calls to the systems
program as long as the return is handled properly.
Some of the methods that reduce execution time also reduce memory usage.
In particular, using page 0, reorganizing loops, working backward through
arrays,
instructions such as CPX, CPY, and BIT reduce both execution time- and
memory usage. Of course, using in-line code rather than loops and subroutines
REFERENCES
Weller, W.J., Practical Microcomputer Programming: The 6502,
Northern
1.
6. Ibid.
8. Schember, K.A. and J.R. Rumsey, "Minimal Storage Sorting and Search-
ing Techniques for RAM Applications," Computer, June 1977, pp. 92-100.
9. Seim, T. A., "Numerical Interpolation for Microprocessor-Based Systems,"
Two hobby magazines run many articles on 6502 assembly language program-
ming; they are Compute (P.O. Box 5406, Greensboro, NC 27403) and Micro
(P.O. Box 6502, Chelmsford, MA
01824).
Chapter 2 Implementing
Additional Instructions
And Addressing Modes
This chapter shows how to implement instructions and addressing modes that
are not included in the 6502's instruction set. Of course, no
instruction set can
ever include all possible combinations. Designers must make choices
based on
how many operation codes are available, how easily an additional combination
could be implemented, and how often it would be used. A description of
addi-
tional instructions and addressing modes does not imply that
the basic instruction
set is incomplete or poorly designed.
We concentrate our attention on additional instructions and addressing modes
that are
This chapter should be of particular interest to those who are familiar with
the
assembly languages of other computers.
73
74 6502 ASSEMBLY LANGUAGE SUBROUTINES
following order: byte (8-bit), word (16-bit), decimal, bit, nibble or digit, and
multiple. In describing addressing modes, we use the following order: direct,
indirect,immediate, indexed, register, autopreincrement, autopostincrement,
autopredecrement, autopostdecrement, indirect preindexed (also called prein-
dexed or indexed indirect) and indirect postindexed (also called postindexed or
,
indirect indexed).
ARITHMETIC INSTRUCTIONS
In this group, we consider addition, addition with carry, subtraction, subtrac-
tion in reverse, subtraction with carry (borrow), increment, decrement, multi-
plication, division, comparison, two's complement (negate), and extension.
Instructions that do not obviously fall into a particular category are repeated for
convenience.
Addition Instructions
(Without Carry)
A more general approach restores the original value of the D flag; that is,
Note that restoring the status register destroys the carry from the addition.
5. Decimal add VALUE to accumulator.
9. Add 16-bit number VAL16 (VAL16M more significant byte, VAL16L less
significant byte) to memory locations SUM and SUM + (MSB
1 in SUM + 1).
CLC • CLEAR CARRY
LDA SUM ;ADD LSB'S WITHOUT CARRY
ADC #VAL16L
STA SUM
LDA SUM+1 ;ADD MSB'S WITH CARRY
ADC #VAL16
STA SUM+1
Addition Instructions
(With Carry)
Carry.
5. Add 16-bit number VAL16 (VAL16M more significant byte, VAL16L less
significant byte) to memory locations SUM and SUM+1 (MSB in SUM + 1) with
Carry.
Subtraction Instructions
(Without Borrow)
The Carry flag acts as an inverted borrow, so it must be set to 1 if its value is to
The Carry flag has the same meaning in the decimal mode as in the binary mode.
5. Decimal subtract VALUE from accumulator.
Subtraction in Reverse
Instructions
The Carry acts as an inverted borrow in either method; that is, the Carry is set to
1 if no borrow is necessary.
accumulator.
Increment Instructions
or
TAX ;MOVE A TO X
INX ; INCREMENT X
TXA
INX does not affect the Carry flag; it does, however, affect the Zero flag.
TSX ;MOVE S TO X
INX ;THEN INCREMENT X AND RETURN VALUE
TXS
or
TAX SAVE A
PLA INCREMENT STACK POINTER
TXA RESTORE A
Remember that INC and DEC produce binary results even when the D flag is set.
5. Increment contents of memory locations
ADDR and ADDR+1 (MSB in
ADDR+1).
INC ADDR INCREMENT LSB
BNE DONE
INC ADDR+1 ;CARRY TO MSB IF LSB GOES TO ZERO
DONE NOP
or
LDA ADDR ; INCREMENT LSB
CLC
ADC #1
STA ADDR
LDA ADDR+1 ;WITH CARRY TO MSB
ADC #0
STA ADDR+1
(MSB in ADDR+1).
SED ;ENTER DECIMAL MODE
LDA ADDR ;ADD 1 TO LSB
CLC
ADC #1
CHAPTER 2: IMPLEMENTING ADDITIONAL INSTRUCTIONS AND ADDRESSING MODES 8
STA ADDR
BCC DONE
LDA ADDR+1 ;CARRY TO MSB IF NECESSARY
ADC #0
STA ADDR+1
DONE CLD ;LEAVE DECIMAL MODE
INC produces a binary result even when the Decimal Mode flag is set. Note that
we could eliminate the BCC instruction from the program without affecting the
result, but the change would increase the average execution time. .
Decrement Instructions
1 Decrement accumulator, clearing the Carry flag if the result is FF 16
SEC ;SET INVERTED BORROW
SBC #1 ;DECREMENT BY SUBTRACTING 1
or
or
CLC ;CLEAR CARRY
ADC #$FF /DECREMENT BY ADDING -1
DEX does not affect the Carry flag; it does, however, affect the Zero flag.
TSX ;MOVE S TO X
DEX ;THEN DECREMENT X AND RETURN VALUE
TXS
You can also decrement the stack pointer with PHA or PHP, neither of which
affects any flags.
one. In fact, incrementing not only faster but also leaves the accumulator
is
(MSB in ADDR+1).
SED ;ENTER DECIMAL MODE
LDA ADDR ;SUBTRACT 1 FROM LSB
SEC
SBC #1
STA ADDR
BCS DONE
LDA ADDR+1 ; BORROW FROM MSB IF NECESSARY
SBC #0
STA ADDR+1
DONE CLD ; LEAVE DECIMAL MODE
DEC produces a binary result even when the Decimal Mode flag is set. Note that
we could eliminate the BCS instruction from the program without affecting the
result, but the change would increase the average execution time.
Multiplication Instructions
1. Multiply accumulator by 2.
3. Multiply accumulator by 4.
ASL A ;2 X A
ASL A ;4 X A
TAX ;MOVE TO A
ASL A .-MULTIPLY BY SHIFTING LEFT
TXA ; RETURN RESULT
5. Multiply the contents of memory locations ADDR and ADDR + 1 (MSB in
ADDR + 1) by 2.
Division Instructions
The second instruction moves the original sign bit (bit 7) to the Carry flag, so the
final rotate can preserve it. This is known as an arithmetic shift, since it preserves
the sign of the number while reducing its magnitude. The fact that the sign bit is
copied to the right is known as sign extension.
ADDR+1) by 2 unsigned.
LSR ADDR+1 ;DIVIDE BY SHIFTING RIGHT
ROR ADDR ;AND MOVING CARRY OVER TO LSB
ADDR + 1) by 2 signed.
Comparison Instructions
1. Compare VALUE with accumulator bit by bit, setting each bit position that
is different.
EOR #VALUE
Remember, the EXCLUSIVE OR of two bits is 1, if and only if the two bits are
different.
memory locations ADR2 and ADR2+1 (MSB in ADR2+1). Set Carry if the
first operand is greater than or equal to the second one (that is, if ADR1 and
ADR1 + 1 contain a 16-bit unsigned number greater than or equal to the contents
of ADR2 and ADR2+1). Clear Carry otherwise. Set the Zero flag if the two
operands are equal and clear it otherwise.
equal to VAL16 in the unsigned sense. Clear Carry otherwise. Set the Zero flag if
the contents of ADRl and ADRl + 1 are equal to VAL16, and clear it otherwise.
We use SBC on the more significant bytes in order to include the borrow from the
This sequence destroys the value in A and sets the Zero flag
less significant bytes.
only from the final subtraction.
If you want to set the Carry if the contents of ADRl and ADRl + 1 are greater
than VAL16, perform the comparison with VAL16 + 1.
6. Compare stack pointer with the contents of
memory location ADDR. Set
Carry if the stack pointer
greater than or equal to the contents of the memory
is
location in the unsigned sense. Clear Carry otherwise. Set the Zero flag
if the two
values are equal and clear it otherwise.
Two's Complement
(Negate) Instructions
1. Negate accumulator.
EOR #SFF ,-ONE'S COMPLEMENT
CLC
ADC #1 ; TWO'S COMPLEMENT
or
There is no need to bother with the decimal mode, since 99- A is always a valid
BCD number if A originally contained a valid BCD number.
5. Ten's complement accumulator (that is* replace A with 100-A).
SED ; ENTER DECIMAL MODE
STA TEMP ,-FORM 100-A
LDA #0
SEC
SBC TEMP
CLD ; LEAVE DECIMAL MODE
Extend Instructions
1. Extend accumulator to a 16-bit unsigned number in memory locations
ADDR and ADDR+1 (MSB in ADDR + 1).
STA ADDR ;8-BIT MOVE
LDA #0 ;EXTEND TO 16 BITS WITH 'S
STA ADDR+1
The result of the calculation is -(-1+SIGN BIT)-1 = -SIGN BIT. That is, '
bitO = OandFF 16
ifbitO= 1.
#$FF, ADC#0 obviously produces the result -1+ Carry. The one's comple-
ment then gives us the negative of what we had minus 1 (or 1 -Carry- 1
=
-Carry).
LDA #SFF ;
ADC #
EOR #$FF ; (A) = -SIGN BIT
LOGICAL INSTRUCTIONS
In this group, we consider logical AND, logical OR, logical EXCLUSIVE OR,
logical NOT (complement), shift, rotate, and test instructions.
MASK has bits in the positions to be cleared and 1 bits in the positions that are
to be left unchanged. For example,
AND #%11011011 ;CLEAR BITS 2 AND 5
LDA #MASK
BIT ADDR ;TEST BIT OF ADDR
MASK should have a 1 in the position to be tested and Os everywhere else. The
Zero flag will be set to 1 if the bit tested is and to if the bit tested is 1.
Bits 6 or 7
3. Logical AND
immediate with condition codes (flags). Logically a AND
byte of immediate data with the contents of the status register, clearing those
flags that are logically ANDed with Os. This instruction is implemented on
the
6809 microprocessor.
Logical OR Instructions
MASK has 1 bits in the positions to be set and bits in the positions that are to be
left unchanged. For example,
ORA #%00010010 ;SET BITS 1 AND 4
90 6502 ASSEMBLY LANGUAGE SUBROUTINES
2. Test memory locations ADDR and ADDR+1 for 0. Set the Zero flag if
The Zero flag is set if and only if both bytes of the 16-bit number are 0. The other
flags are also changed.
immediate data (MASK) with the contents of the status register, setting those
flags that are logically ORed with Is. This instruction is implemented on the 6809
microprocessor.
Logical EXCLUSIVE OR
Instructions
MASK has 1 bits in the positions to be complemented and bits in the positions
3. Compare memory location ADDR with accumulator bit by bit, setting each
bit position that is different.
the Negative (Sign) flag is 1 if the two operands have different values in bit posi-
tion 7.
CHAPTER 2: IMPLEMENTING ADDITIONAL INSTRUCTIONS AND ADDRESSING MODES 9
MASK has 1 bits in the positions to be complemented and bits in the positions
that are to be left unchanged. For example,
EOR #%01010001 COMPLEMENT BITS 0, 4, AND 6
LDA ADDR
EOR #$FF ; COMPLEMENT
STA ADDR
Either of these instructions may, of course, affect the other bits in the memory
location.The final value of bit 0, however, will surely be if it was originally 1
and 1 if it was originally 0.
These procedures are useful the accumulator contains a decimal digit in nega-
if
tive logic (e.g., the input from a typical ten-position rotary or thumbwheel
switch)
BCC SETCAR
CLC ;CLEAR CARRY. IF IT WAS SET
BCC DONE
SETCAR SEC ;SET CARRY IF IT WAS CLEARED
DONE NOP
Shift Instructions
TA X ;SAVE ACCUMULATOR
ASL A ;MOVE SIGN BIT TO CARRY
TXA
ROR A ; SHIFT RIGHT, PRESERVING SIGN
logically.
The key point here we must shift the more significant byte circularly (i.e.,
is that
least significant bit for a right
rotate it). The first 8-bit shift moves one bit (the
a shift) to the Carry. The 8-bit rotate then
shift and the most significant bit for left
moves that bit from the Carry into the other half of the word.
logically.
A shorter but slower version that does not use the accumulator is
Rotate Instructions
A rotate through or with Carry acts as if the data were arranged in a circle with
its least significant bit connected to its most significant bit through the Carry flag.
A rotate without Carry differs in that it acts as if the least significant bit of the data
were connected directly to the most significant bit.
Test Instructions
2. Test index register. Set flags according to the value in an index register
without changing that value.
CPX #0 ;CHECK VALUE IN INDEX REGISTER
3. Test memory location. Set flags according to the value in memory location
ADDR without changing that value.
INC ADDR ;CHECK VALUE IN MEMORY LOCATION
DEC ADDR
4. Test a pair of memory locations. Set the Zero flag according to the value in
memory locations ADDR and ADDR+1.
LDA ADDR ;TEST 16-BIT NUMBER FOR ZERO
ORA ADDR+1
This sequence sets the Zero flag to 1 if and only if both bytes of the 16-bit number
are 0. This procedure can readily be extended to handle numbers of any
length.
5. Test bit of accumulator.
AND #MASK ;TEST BIT BY MASKING
MASK has a 1 bit in the position to be tested and bits elsewhere. The instruc-
tion sets the Zero flag to1 if the tested bit position contains and to if the tested
bit position contains 1. For example,
AND #%00001000 ;TEST BIT 3 BY MASKING
The result is if bit 3 of A is and 00001000 (binary) if bit 3 of A is 1. So the Zero
flag ends up containing the logical complement of bit 3.
6. Compare memory location ADDR with accumulator bit by bit. Set each
each bit position that is different.
Load Instructions
The only instruction that has true indirect addressing is JMP. However, you can
indexed)
produce ordinary indirect addressing by using the postindexed (indirect
addressing mode with index register Y set to 0.
The advantage of approach is that one can index from the indirect
the first
LD Y #0 ; AVOID INDEXING
LDA (PGZRO), Y ?GET LSB OF ADDRESS INDIRECTLY
STA POINTL
INY ;GET MSB OF ADDRESS INDIRECTLY
LDA (PGZRO),
STA POINTH i
Store Instructions
TSX* ;
STX ADDR
If you are saving values in the stack, you must save A before X or Y, since there
Move Instructions
FILBYT DEY
STA (PGZRO), Y ;FILL A BYTE
INY
DEY
BNE FILBYT ;COUNT BYTES
Chapter 5 contains a more general version.
Here again we can simplify the program by letting memory locations PGZRO and
PGZRO + 1contain an address one less than the lowest address in the area
to be
filled. The revised program is
FILBYT STA (PGZRO), Y ;FILL A BYTE
DEY
BNE FILBYT ;COUNT BYTES
1 00 6502 ASSEMBLY LANGUAGE SUBROUTINES
Exchange Instructions
1. Exchange index registers X and Y.
or
TXA ;SAVE X
PHA
TYA I* TO X
TAX
PLA ;X TO Y
TAY
Both versions take the same number of bytes (assuming TEMP is on page 0). The
second version is slower but reentrant.
2. Exchange memory locations ADDRl and ADDR2.
LDA ADDRl
LDX ADDR2
STX ADDRl
STA ADDR2
3. Exchange accumulator and top of stack.
TAY SAVE A
PLA GET TOP OF STACK
TAX SAVE TOP OF STACK
TYA A TO TOP OF STACK
PHA
TX A ?TOP OF STACK TO A
Clear Instructions
LDA #0
The 6502 treats like any other number. There are no special clear instructions.
LDX #o
or
LDY #0
3. Clear memory location ADDR.
LDA #0
STA ADDR
. .
LDA #0
STA ADDR
STA ADDR+1
5. Clear bit of accumulator.
AND #MASK ; CLEAR BIT BY MASKING
MASK has bits in the positions to be cleared and 1 bits in the positions that are
to be left unchanged. For example,
AND #%10111110 ;CLEAR BITS AND 6 OF A
Set Instructions
LDA #SFF
LDX #$FF
or
LDY #$FF
LDX #$FF
TXS
The next available location in the stack is at address 01FF„
4. Set a memory location to FF 16 .
LDA #SFF
STA ADDR
5. Set bit of accumulator.
ORA #MASK ;SET BIT BY MASKING
MASK has 1 bits in the positions to be set and bits elsewhere. For example,
ORA #%10000000 ;SET BIT 7 (SIGN BIT)
SEC
BCS DEST
or
LDA #0
BEQ DEST
or j
LDA #1
BNE DEST
RTS
RTS is just an ordinary indirect jump inwhich the processor obtains the destina-
tion from the top of the stack! Be careful, however, of the fact that the processor
adds 1 to the address before proceeding.
The second approach is faster but less straightforward. Note the following:
1. You must store the more significant byte first since the stack is growing
toward lower addresses. Thus the bytes end up in their usual order.
2. Since RTS adds 1 to the program counter after loading it from the stack, the
table entries must all be 1 less than the actual destination addresses for this
method to work correctly.
3. Documentation is essential, since this method uses RTS for the rather
1. Branch if zero.
• Branch if accumulator contains zero.
TAX ;TEST ACCUMULATOR
BEQ DEST
or
CMP #0 ;TEST ACCUMULATOR
BEQ DEST
Either AND #$FF or ORA #0 will set the Zero flag if (A) =0 without affecting
the Carry flag (CMP#0 sets Carry).
• Branch if an index register contains 0.
Bit position 6
LDA #MASK
BIT ADDR ;TEST BIT OF MEMORY
BEQ DEST
MASK has a 1 bit in the position to be tested and 0s elsewhere. Special cases are
Bit position 7
Bit position 6
BIT ADDR ;TEST MEMORY
BVC DEST ,-BRANCH ON BIT 6
The BIT instruction sets the Negative flag from bit 7 of the memory location and
the Overflow flag from bit 6, regardless of the contents of the accumulator.
We can also use the shift instructions to test the bits at the ends, as long as we
can tolerate changes in the memory locations.
Bit position 7
ASL ADDR ;TEST BIT 7
BCC DEST
Bit position 6
ASL ADDR ;TEST BIT 6
BPL DEST
CHAPTER 2: IMPLEMENTING ADDITIONAL INSTRUCTIONS AND ADDRESSING MODES 1 05
Bit position
MASK has a 1 bit in the position to be tested and Os elsewhere. Note the inver-
sion here; if the bit of the accumulator is a 1, the result is not and the Zero flag
is set to 0. Special cases are
Bit position 7
Bit position 6
Bit position
LDA #MASK
BIT ADDR ;TEST BIT OF MEMORY
BNE DEST
MASK has a 1 bit in the position to be tested and Os elsewhere. Special cases are
Bit position 7
Bit position 7
This alternative is slower than BIT by 2 clock cycles, since it must write the result
3. Branch if Equal.
• Branch if (A) = VALUE.
CMP #VALUE ; COMPARE BY SUBTRACTING
BEQ DEST
• Branch if (X) = VALUE.
CPX #VALUE ; COMPARE BY SUBTRACTING
BEQ DEST
Two special cases are
Branch if (X) = 1
DEX
BEQ DEST
Branch if (X) = FF.6-
INX
BEQ DEST
• Branch if (A) = (ADDR).
CMP ADDR ; COMPARE BY SUBTRACTING
BEQ DEST
•
Branch if (X) = (ADDR).
CPX ADDR ; COMPARE BY SUBTRACTING
BEQ DEST
Note: Neither of the next two sequences should be used to test for stack over-
flow or underflow, since intervening instructions (for example, a single JSR or
RTS) could change the stack pointer by more than 1.
•
Branch if (S) = VALUE.
TSX ;CHECK IF STACK IS AT LIMIT
CPX #VALUE
BEQ DEST
Branch if (S) = (ADDR).
TSX ;CHECK IF STACK IS AT LIMIT
CPX ADDR
BEQ DEST
Branch if Not Equal.
• Branch if (A) ^ VALUE.
CMP #VALUE .-COMPARE BY "SUBTRACTING
BNE DEST
Branch if (X) ± 1.
DEX
BNE DEST
•
Branch if the contents of memory locations PGZRO and PGZRO+1 are not
equal to VAL16 (VAL16L less significant byte, VAL16M more significant byte).
CHAPTER 2: IMPLEMENTING ADDITIONAL INSTRUCTIONS AND ADDRESSING MODES 1 09
Remember that BIT sets the Negative flag from bit 7 of the memory location,
6. Branch if Negative.
•
Branch if contents of accumulator are negative.
TAX ;TEST ACCUMULATOR
BMI DEST
or
CMP #0 ;TEST ACCUMULATOR
BMI DEST
•
Branch if 16-bit number in memory locations ADDR and ADDR + 1 (MSB
in ADDR+1) is negative.
The idea here is to branch if the result is greater than or equal to and overflow
did not occur, or if the result is less than and overflow did occur.
• Branch if (A) > (ADDR).
CMP ADDR ;COMPARE BY SUBTRACTING
BVS CHKOPP ;DID OVERFLOW OCCUR?
BPL DEST ;NO, THEN BRANCH ON POSITIVE
BMI DONE
CHKOPP BMI DEST ;YES, THEN BRANCH ON NEGATIVE
DONE NOP
The idea here is to branch if the result is 0, negative without overflow, or positive
with overflow.
• Branch if (A) < (ADDR) (signed).
11. Branch if Higher (Unsigned). That is, branch if the unsigned comparison
is nonzero and does not require a borrow.
•
Branch if (A) > VALUE (unsigned).
CMP #VALUE ;COMPARE BY SUBTRACTING
BEQ DONE ;NO BRANCH IF EQUAL
BCS DEST ;BRANCH IF NO BORROW NEEDED
DONE NOP
or
inPGZRO+1) are larger (unsigned) than VAL16 (VAL16L less significant byte,
VAL16M more significant byte).
LDA #VAL16L ; GENERATE BORROW BY COMPARING LSB'S
CMP PGZRO
LDA #VAL16M ;COMPARE MSB'S WITH BORROW
SBC PGZRO+1
BCC DEST ; BRANCH IF BORROW GENERATED
• Branch if the contents of memory locations PGZRO and PGZRO+1 (MSB
inPGZRO+1) are larger (unsigned) than the contents of memory locations
LIML and LIMH (MSB in LIMH).
LDA LIML ;GENERATE BORROW BY COMPARING LSB'S
CMP PGZRO
LDA LIMH .-COMPARE MSB'S WITH BORROW
SBC PGZRO+1
BCC DEST ; BRANCH IF BORROW GENERATED
• Branch if (S) > VALUE (unsigned).
TSX ;CHECK IF STACK BEYOND LIMIT
CPX #VALUE
BEQ DONE ;NO BRANCH IF EQUAL
BCS DEST ;BRANCH IF NO BORROW NEEDED
DONE NOP
or
TSX ;CHECK IF STACK BEYOND LIMIT
CPX #VALUE+1 ;COMPARE BY SUBTRACTING VALUE + 1
BCS DEST ;BRANCH IF NO BORROW NEEDED
• Branch if (S) > (ADDR) (unsigned).
If the two values are the same, CMP sets the Carry to indicate that no borrow was
necessary.
or
CMP #VALUE+1 ;COMPARE BY SUBTRACTING VALUE + 1
BCC DEST ;BRANCH IF BORROW NEEDED
in PGZRO+1) are less than or equal to (unsigned) the contents of memory loca-
tions LIML and LIMH (MSB in LIMH).
LDA LIML ,-GENERATE BORROW BY COMPARING LSB'S
CMP PGZRO
LDA LIMH ;COMPARE MSB'S WITH BORROW
SBC PGZRO+1
BCS DEST ,-BRANCH IF NO BORROW GENERATED
•
Branch if (S) < VALUE (unsigned).
,-BRANCH IF EQUAL
CHAPTER 2: IMPLEMENTING ADDITIONAL INSTRUCTIONS AND ADDRESSING MODES 115
or
TSX ;CHECK IF STACK AT OR BELOW LIMIT
CPX IVALUE+1 ;COMPARE BY SUBTRACTING VALUE + 1
BCC DEST
• Branch if (S) < (ADDR) (unsigned).
TSX ;CHECK IF STACK AT OR BELOW LIMIT
CPX ADDR
BCC DEST ; BRANCH IF BORROW NEEDED
BEQ DEST ; BRANCH IF EQUAL '
,
13. Branch if Lower (Unsigned). That is, branch if the unsigned comparison
requires a borrow.
• Branch if (A) < (unsigned).
inPGZRO+1) are less than (unsigned) VAL16 (VAL16L less significant byte,
VAL16M more significant byte).
LDA PGZRO .-GENERATE BORROW BY COMPARING LSB'S
CMP #VAL16L
LDA PGZRO+1 ,-COMPARE MSB'S WITH BORROW
SBC #VAL16M
BCC DEST ; BRANCH IF BORROW GENERATED
• Branch if the contents of memory locations PGZRO and PGZRO+1 (MSB
inPGZRO+1) are less than (unsigned) the contents of memory locations LIML
and LIMH (MSB in LIMH).
LDA PGZRO /GENERATE BORROW BY COMPARING LSB'S
CMP LIML
LDA PGZRO+1 ,-COMPARE MSB'S WITH BORROW
SBC LIMH
BCC DEST ,-BRANCH IF BORROW GENERATED
116 6502 ASSEMBLY LANGUAGE SUBROUTINES
•
Branch if (S) < (ADDR) (unsigned).
The Carry flag is set to one if the subtraction does not generate a borrow.
•
Branch if (A) > (ADDR) (unsigned).
•
Branch if (X) >(ADDR) (unsigned).
in PGZRO+ 1) are greater than or equal to (unsigned) VAL16 (VAL16L less sig-
nificant byte, VAL16M more significant byte).
SKIP INSTRUCTIONS
You can implement skip instructions on the 6502 microprocessor by using
branch or jump instructions with the proper destination. That destination should
be one instruction beyond the one that the processor would execute sequentially
after the branch. Note that skip instructions are awkward to implement on most
microprocessors, because their instructions vary in length and it is difficult to
determine how long a jump is required to skip an instruction.
JSR TRANS
where TRANS is the subroutine that actually transfers control using a jump
instruction. Note that TRANS ends with a jump, not with a return. Typical
TRANS routines are:
LDA INDEX
ASL A ; DOUBLE INDEX FOR 2-BYTE ENTRIES
TAX
LDA BASE,X ;GET LSB OF DESTINATION
STA INDIR
INX
LDA BASE,X ;GET MSB OF DESTINATION
STA INDIR+1
JMP (INDIR) ;JUMP INDIRECT TO DESTINATION
or
LDA INDEX
ASL A DOUBLE INDEX FOR 2-BYTE ENTRIES
TAX
LDA BASE+1,X GET MSB OF DESTINATION
PHA
LDA BASE,X GET LSB OF DESTINATION
PHA
RTS JUMP TO DESTINATION PLUS 1
In thesecond approach, the table must contain the actual destination addresses
minus 1, since RTS adds 1 to the program counter after loading it from the stack.
RETURN INSTRUCTIONS
Unconditional Return instructions
The RTS instruction returns control automatically to the address saved at the
top of the stack (plus the return address is saved elsewhere (i.e., in two
1). If
• Change the return address to RETPT. Assume that the return address is
stored currently at the top of the stack. RETPT consists of RETPTH (MSB) and
RETPTL (LSB).
1 20 6502 ASSEMBLY LANGUAGE SUBROUTINES
TSX
LDA #RETPTL
STA $0101,X
LDA #RETPT
STA $0102,
RTS
If the initial portion of the interrupt service routine saves all the registers with
the sequence.
MISCELLANEOUS INSTRUCTIONS
In this category, include push and pop instructions, halt, wait, break,
we
decimal adjust, enabling and disabling of interrupts, translation (table lookup),
and other instructions that do not fall into any of the earlier categories.
1. Push Instructions.
• Push index register X.
•
Pop index register X.
Wait Instructions
The simplest way to implement a wait on the 6502 microprocessor is to use an
endless loop such as:
The processor will continue executing the instruction until it is interrupted and
will resume executing it after the interrupt service routine returns control. Of
course, maskable interrupts must have been enabled or the processor will
1 22 6502 ASSEMBLY LANGUAGE SUBROUTINES
execute the loop endlessly. The nonmaskable interrupt can interrupt the pro-
cessor at any time.
Another alternative is a sequence that waits for a high-to-low transition on the
Set Overflow input. Such a transition sets the Overflow (V) flag. So the
required
sequence is
Adjust Instructions
4. Enter decimal mode but save the old Decimal Mode flag.
A final PLP instruction will restore the old value of the Decimal Mode flag (and
5. Enter binary mode but save the old Decimal Mode flag.
A final PLP instruction will restore the old value of the Decimal Mode flag (and
Translate Instructions
Examples
1. Load the accumulator indirectly from the address in memory locations
PGZROandPGZRO + 1.
LDY #0 ;SET INDEX TO ZERO
LDA (PGZRO),Y ; LOAD INDIRECT INDEXED
4. Logically shift right the data at the address in memory locations PGZRO
and PGZRO + 1.
LDY #0 ;SET INDEX TO ZERO
LDA (PGZRO), ;GET THE DATA
LSR A ;SHIFT IT RIGHT
STA (PGZRO), ;STORE THE RESULT BACK
The only way to provide indirect addressing for other pages is to move the
indirect address to page first.
No indexing is available for BIT, CPX, CPY, JMP, and JSR. Only page
indexing is available for STX and STY. We can overcome these limitations as
follows:
1. BIT
BIT indexed can be simulated by saving the accumulator, using AND, and
restoring the accumulator. You
should note that restoring the accumulator with
LDA, PHA, TXA, or TYA will affect the Zero and Negative flags. A typical
sequence without restoring the accumulator is:
PHA ;SAVE A
AND BASE,X ; LOGICAL AND INDEXED
The Zero flag is set as if an indexed BIT had been executed and the contents of A
are available at the top of the stack.
2. CPX or CPY
CPX or CPY indexed can be simulated by moving the index register to A and
using CMP. That is, CPX indexed with Y can be simulated by the sequence:
TXA ;MOVE X TO A
CMP BASE,Y ;THEN COMPARE INDEXED
3.JMP
JMP indexed can be simulated by calculating the required indexed address,
storing it in memory, and using either JMP indirect or RTS to transfer control.
The sequences are:
LDA INDEX
ASL A ;DOUBLE INDEX FOR 2-BYTE ENTRIES
TAX
LDA BASE,X ;GET LSB OF DESTINATION
STA INDIR
INX
LDA BASE,X ;GET MSB OF DESTINATION
STA INDIR+1
JMP (INDIR) ;JUMP INDIRECT TO DESTINATION
1 26 6502 ASSEMBLY LANGUAGE SUBROUTINES
or
LDA INDEX
ASL A DOUBLE INDEX FOR 2-BYTE ENTRIES
TAX
LDA BASE+1,X GET MSB OF DESTINATION
PHA
LDA BASE,X GET LSB OF DESTINATION
PHA
RTS JUMP INDIRECT TO DESTINATION OFFSET 1
The second approach requires that the table contain entries that are all 1 less than
the actual destinations, since RTS adds 1 to the program counter after restoring it
JSR
4.
JSR indexed can be simulated by calling a transfer program that executes JMP
indexed as shown above. The ultimate return address remains at the top of the
stack and a final RTS instruction will transfer control back to the original calling
program. That is, the main program contains:
JSR TRANS
TRANS performs an indexed jump and thus transfers control to the actual
subroutine.
5.STX or STY
STX or STY indexed can be simulated by moving the index register to A and
using STA. That is, we can simulate STX indexed with Y by using the sequence:
TXA ,-MOVE X TOA
STA . BASE,Y ;THEN STORE INDEXED
Examples
1. Load accumulator indexed.
LDY INDEX ;GET LSB OF INDEX
LDA (TEMP),Y ;LOAD A INDIRECT INDEXED
2. Store accumulator indexed, assuming that we have saved A at the top of the
stack.
the
index register are incremented automatically before they are
used. You can pro-
vide autopreincrementing on the 6502 processor either
by using INX or INY on
an index register or by using the 16-bit methods to
increment a base address in
memory.
Examples
Load the accumulator from address BASE using autopreincrementing on
index register X.
INX jAUTOPREINCREMENT X
LDA BASE ,
the index register are incremented automatically after they are used. You
can pro-
autopreincrementing on the 6502 processor either by using INX or INY on
vide
index in
an index register or by using the 16-bit methods to increment an
memory.
Examples
• Load the accumulator from address BASE using autopostincrementing on
index register Y.
LDA BASE,Y ; AUTOPOSTINCREMENT Y
INY
Load the accumulator from the address in memory locations PGZRO and
•
index register X.
DEX ;AUTOPREDECREMENT X
LDA BASE,X
the index register are decremented automatically after they are used. You can
provide autopostdecrementing on the 6502 processor by using either or DEX
DEY on an index register or by using the 16-bit methods to decrement an index
in memory.
Examples
• Load the accumulator from address BASE, using autopostdecrementing on
index register Y.
preindexing.
Examples
'
1. Rotate right the data at the preindexed address obtained by indexing with X
from base address PGZRO.
LDA (PGZRO, X) GET THE DATA
ROR A ROTATE DATA RIGHT
STA (PGZRO,X) STORE RESULT BACK IN MEMORY
provides
Indirect indexed addressing (postindexing). The 6502 processor
•
Examples
1. Decrement the data at the address in memory locations PGZRO and
2. Rotate left the data at the address in memory locations PGZRO and
PGZRO+1 using Y as an index.
REFERENCES
1. Osborne, A. An Introduction to Microcomputers, Volume 1: Basic Concepts,
2nd ed. Berkeley: Osborne/McGraw-Hill, 1980.
2. Leventhal, L.A. 6800 Assembly Language Programming. Berkeley: Osborne/
McGraw-Hill, 1978.
3. Leventhal, L.A. 6809 Assembly Language Programming. Berkeley: Osborne/
McGraw-Hill, 1981.
4. Fischer, W.P. "Microprocessor Assembly Language Draft Standard,"
IEEE Computer, December 1979, pp. 96-109.
5. Scanlon, L.J. 6502 Software Design, Howard W. Sams, Indianapolis, Ind
1980, pp. 111-13.
.
Chapter 3 Common
Programming Errors
and 2.
• To provide the beginner with a starting point in the difficult process of locat-
ing and correcting errors.
CATEGORIZATION OF
PROGRAMMING ERRORS
We may generally divide common 6502 programming errors into the following
categories:
• Using the Carry improperly. Typical errors include forgetting to clear the
Carry before addition or set it before subtraction, and interpreting it incorrectly
after comparisons (it acts as an inverted borrow)
133
1 34 6502 ASSEMBLY LANGUAGE SUBROUTINES
•
Using the other flags improperly. Typical errors include using the wrong flag
(such as Negative instead of Carry), branching after instructions that do not
affect a particular flag, inverting the branch conditions (particularly when the
Zero flag is involved) and changing a flag accidentally before branching.
,
Using the wrong formats. Typical errors include using BCD (decimal)
•
of
instead of binary, or vice versa, and using binary or hexadecimal instead
ASCII.
Handling arrays incorrectly. Typical problems include accidentally overrun-
•
ning the array at one end or the other (often by 1) and ignoring page boundaries
when the array exceeds 256 bytes in length.
Ignoring implicit effects. Typical errors include using the contents of the
•
accumulator, index register, stack pointer, flags, or page locations without con-
sidering the effects of intermediate instructions on these contents. Most errors
arise from instructions that have unexpected, implicit, or indirect effects.
indirect
repeating initialization routines, failing to update indexes, counters, or
addresses, and forgetting to save intermediate or final results.
A common source of errors, one that is beyond the scope of our discussion, is
conflict between user programs and systems programs. A simple
example is a
operating
user program that saves results in temporary storage locations that
systems or utility programs need for their own purposes. The results thus disap-
mysteriously even though a detailed trace of the user program does not
pear
reveal any errors.
sources of conflict may include the interrupt system, input/out-
More complex
put ports, the stack, or the flags. After all, the systems programs must employ the
same resources as the user programs. (Systems programs generally attempt
to
unex-
save and restore the user's environment, but they often have subtle
or
user is a problem
pected effects.) Making an operating system transparent to the
codes that have no
comparable to devising a set of regulations, laws, or tax
loopholes or side effects.
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 35
• ADC always includes the Carry in the addition. This produces the result (A)
= (A) + (M) + Carry. If you do not want the Carry flag to affect the result, you
must clear it with CLC. Note that the Carry has its normal meaning after ADC.
Examples
1. CMP ADDR
This instruction sets the flags as if the contents of memory location ADDR had
been subtracted from the accumulator. The Carry flag is set if the subtraction
does not require a borrow and cleared if it does. Thus
We are assuming that both numbers are unsigned. Note that the Carry is set (to
1) if the numbers are equal.
2. SBC #VALUE
This instruction subtracts VALUE and 1 -Carry from the accumulator. It sets
the flags just like a comparison. To subtract VALUE alone from the accumulator,
you must use the sequence
SEC ;SET INVERTED BORROW
SBC #VALUE ; SUBTRACT VALUE
This sequence produces the result (A) = (A) - VALUE. If VALUE = 1, the
sequence is equivalent to a Decrement Accumulator instruction (remember,
DEC cannot be applied to A).
1 36 6502 ASSEMBLY LANGUAGE SUBROUTINES
3. ADC #VALUE
This instruction adds VALUE and
Carry to the accumulator. To add VALUE
alone to the accumulator, you must use the sequence
This sequence produces the result (A) = (A) + VALUE. If VALUE = 1, the
sequence is equivalent to an Increment Accumulator instruction (remember,
INC cannot be applied to A).
•
Store instructions (STA, STX, and STY) do not affect the flags, so the flags
contents greater
larger. CMP, CPX, or CPY clears the Carry if the register's are
than or equal to the other operand and sets the Carry if the register's contents
are
operands sets the Carry. If these alternatives
less. Note that comparing equal
(greater than or equal and less than) are not what you need (you want
the alterna-
greater than and less than or equal), you can reverse the subtraction,
tives to be
subtract 1 from the accumulator, or add 1 to the other operand.
larger unless two's complement overflow has occurred. must first look at the We
Overflow flag. If that flag is 0, the Negative flag indicates which operand is larger;
After a comparison (if no overflow occurs), the Negative flag is set if the
register's contents are less than the other operand, and cleared if the
register's
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 37
contents are greater than or equal to the other operand. Note that comparing
equal operands clears the Negative flag. As with the Carry, you can handle the
equality case in the opposite way by adjusting either operand or by reversing the
subtraction.
The branch condition is the opposite of the condition under which the section
should be executed.
Increment and decrement instructions do not affect the Carry flag. This
•
allows the instructions to be used for counting in loops that perform multiple-
byte arithmetic (the Carry is needed to transfer carries or borrows between
bytes). Increment and decrement instructions do, however, affect the Zero and
Negative flags; you can use the effect on the Zero flag to determine whether an
increment has produced a carry. Note the following typical sequences:
We determine if a carry has been generated by examining the Zero flag after
incrementing the less significant byte.
• The BIT instruction has rather unusual effects on the flags. It places bit 6 of
the memory location in the Overflow flag and bit 7 in the Negative flag, regard-
less of the value in the accumulator. Thus, only the Zero flag actually reflects the
logical ANDing of the accumulator and the memory location.
1 38 6502 ASSEMBLY LANGUAGE SUBROUTINES
•
Only a Carry or Overflow flags. The instructions
few instructions affect the
will have unpredictable results, since STA does not affect any flags. Sequences
that will produce a jump if the value stored is are
STA $1700
CMP #0 ;TEST ACCUMULATOR
BEQ DONE
or
STA $1700
TAX ;TEST ACCUMULATOR
BEQ DONE
Thus, if you want to increment memory location COUNT, if (A) = 25 16 use the
,
sequence
25 ) does not hold. It is obviously easy to err by inverting the branch condition.
If you want to clear the Carry if the X register contains 25.., use CPX #$26
instead of CPX #$25. That is, we have
CPX #$25
BCC LESS ,-BRANCH IF (X) LESS THAN 25
or
CPX #$26
BCC LESSEQ ;BRANCH IF (X) 25 OR LESS
4. The sequence SEC, SBC #$40 sets the Negative (Sign) flag as follows:
Negative = if A
between 40 16 and 7F l6 (normal signed arithmetic) or
is if A
is between 80 16 and C0 16 (because of two's complement overflow)
Negative = 1 if A is between 00 16 and 3F |6 or between Cl and FF (normal
16 16
signed arithmetic)
Two's complement overflow occurs if A contains a number between 80
16
(-128 l0in two's complement) and C0 (-64 10 in two's complement). Then
16
subtracting 40 l6 (64 ) produces a result less than — 128 which is beyond the
10 10
,
range of an 8-bit signed number. The setting of the Overflow flag indicates this
out-of-range condition.
The following sequence will thus produce a branch if A contains a signed num-
ber less than 40 .
16
Note that we cannot use CMP here, since it does not affect the Overflow flag. We
could, however, use the sequence
5. The sequence
INC ADDR
BCS NXTPG
willhave unpredictable results, since INC does not affect the Carry flag. A
sequence that will produce a jump, if the result of the increment is 00 (thus
implying the production of a carry), is illustrated below.
1 40 6502 ASSEMBLY LANGUAGE SUBROUTINES
INC ADDR
BEQ NXTPG
We can tell when an increment has produced a carry, but we cannot tell when a
decrement has required a borrow since the result then is FF l6 not 0. Thus, it is ,
BIT ADDR
BPL DEST
LDA #MASK
BIT ADDR
This sequence sets the Zero flag if logically ANDing MASK and the contents of
ADDR produces a result of 0. A typical example using the Zero flag is
LDA #%00010000
BIT ADDR
BNE DEST ; BRANCH IF BIT 4 OF ADDR IS 1
This sequence forces a branch if the result of the logical AND is nonzero, that is,
if bit 4 of ADDR is 1.
The BIT on the Overflow and Negative flags do not generally cause
effects of
programming no standard, widely used effects that might
errors since there are
cause confusion. These effects do, however, create documentation problems
since the approach is unique and those unfamiliar with the 6502
cannot be
expected to guess what is happening.
7. The sequence
CMP #VALUE
BVS DEST
produces unpredictable results, since CMP does not affect the Overflow flag.
Instead, to produce a branch if the subtraction results in two's
complement over-
flow, use the sequence
SEC SET INVERTED BORROW
SBC #VALUE SUBTRACT VALUE
BVS DEST BRANCH IF OVERFLOW OCCURS
CHAPTER 3: COMMON PROGRAMMING ERRORS 141
• The immediate addressing mode requires the actual data as an operand. That
is, LDA #$40 loads the accumulator with the number 40,,.
. The absolute and zero page (direct) addressing modes require the address of
the data as an operand. That is, LDA $40 loads the accumulator with the contents
of memory location 0040 .
16
• The indirect indexed and indexed indirect addressing modes obtain the
indirect address from two memory locations on page 0. The indirect address is in
two memory locations starting at the specified address; it is stored upside-down,
with its less significant byte at the lower address. Fortunately, the indexed
indirect (preindexed) modeused and is seldom a cause of errors. The
is rarely
meaning of addressing modes with JMP
and JSR can be confusing, since these
instructions use addresses as if they were data. The assumption is that one could
not transfer control to a number, so a jump with immediate addressing would be
meaningless. However, the instruction JMP $1C80 loads 1C80 into the program
16
counter, just like a load with immediate addressing, even though we conven-
tionally say that the instruction uses absolute addressing. Similarly, the instruc-
tion JMP (ADDR) loads the program counter with the address from memory
locations ADDR and ADDR + 1; it thus acts like a load instruction with absolute
(direct) addressing.
Examples
1. LDX#$20 loads the number 20 into index register X. LDX $20 loads the
16
contents of memory location 0020 into index register X.
16
2. LDA ($40) ,Y loads the accumulator from the address obtained by indexing
with Y from the base address in memory locations 0040 and 0041 (MSB in l6 16
0041 16 ). Note that if LDA ($40), Y makes sense, then LDA ($41),Y generally
does not, since it uses the base address in memory locations 0041 and 0042
16
Thus, the indirect addressing modes generally make sertse only if the indirect
addresses are aligned properly on word boundaries; however, the 6502 does not
check this alignment in the way that many computers (particularly IBM
machines) do. The programmer must make sure that all memory locations used
indirectly contain addresses with the bytes arranged properly.
•
The address in which the result is to be saved.
•
The upper and lower thresholds against which the result is to be compared.
Thus, the block contains data, direct addresses, and indirect addresses. Typical
errors that a programmer could make are
•
Using a threshold as an address rather than as data.
•
Assuming that the next block starts within the current block, rather than at
the address given in the current block.
Jump tables are another common source of errors. The following are alterna-
tive implementations:
•
Form a table of jump instructions and transfer control to the correct element
(for example, to the third jump instruction).
the correct element (for example, to the address in the third element).
FORMAT ERRORS
The rules you should remember are
sembler and a % in front or a B at the end indicates binary. Be careful — some as-
semblers use different symbols.
The default mode of most assemblers is decimal; that is, most assemblers
•
assume all numbers to be decimal unless they are specifically designated as some-
thing else. A few assemblers (such as Apple's miniassembler and the mnemonic
entry mode' in Rockwell's AIM-65) assume hexadecimal as a default.
•
ADC and SBC instructions produce decimal results if the Decimal Mode flag
is 1 and binary Decimal Mode flag is 0. All other instructions,
results if the
including DEC, DEX, DEY, INC, INX, and INY, always produce binary results.
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 43
You should make special efforts to avoid trie following common errors:
item or address. The assembler will assume the item to be a decimal number if it
contains no letter digits. It will treat the item as a name if it is valid (it must start
with a letter in most assemblers). The assembler will indicate an error only if the
item cannot be interpreted as a decimal number or a name.
tations are not the same beyond nine. Standard BCD constants must be desig-
nated as hexadecimal numbers, not as decimal numbers.
ASCII input device produces ASCII characters and an ASCII output device re-
sponds to ASCII characters.
Examples
1. LDA 2000
This instruction loads the accumulator from memory address 2000 (07D0
10 ),
not address 2000
l6
The assembler will not produce an error message, since 2000
.
2. AND #00000011
This instruction logically ANDs the accumulator with the decimal number 11
(101 2 ), not with the binary number 11 (3 ). The assembler will not produce
10 an
error message, since 00000011 is a valid decimal number despite its
unusual
form.
3. ADC #40
This instruction adds 40 (not 40 = 64 ) and the Carry to the accumulator.
l0 l6 |0
Note that 40 is not the same as 40 BCD, which is 40
10
16
40 10 = 28 16 The assem-
; .
will not print that digit on an ASCII output device. The correct sequence is
or
6. If input port IPORT contains a single ASCII decimal digit, the sequence
LDA I PORT
STA $40
not store the actual digit in memory location 0040 l6 Instead, it will store the
will .
ASCII version, which is the actual digit plus 30 l6 The correct sequence is
.
or
Handling decimal arithmetic on the 6502 microprocessor is simple, since the pro-
cessor has a Decimal Mode (D) flag. When that flag is set (by SED), all additions
and subtractions produce decimal results. So, the following sequences implement
decimal addition and subtraction:
•
Decimal addition of memory location ADDR to the accumulator
•
Decimal subtraction of memory location ADDR from the accumulator
LDA $4
SEC
SBC #1
STA $40
The problem with the decimal mode is that it has implicit effects. That is, the
same ADC and SBC instructions with the same data will produce different
results, depending on the state of the Decimal Mode flag. The following pro-
cedures will reduce the likelihood of the implicit effects causing unforeseen
errors:
• Initialize the Decimal Mode flag (with CLD) as part of the regular system
initialization. Note that RESET has no effect on the Decimal Mode flag.
• Clear the Decimal Mode flag as soon as you are through performing decimal
arithmetic.
• Initialize the Decimal Mode flag in interrupt service routines that include
ADC or SBC instructions. That
such service routines should execute
is, CLD
before performing any binary addition or subtraction.
•If you are counting an index register down to 0, the zero index value may
never be used. The solution is to reduce the base address or addresses by 1. For
example, if the terminating sequence in a loop is
DEX
BNE LOOP
LDX #NTIMES
LDA #0
CLEAR STA BASE-1,X
DEX
BNE CLEAR
1 46 6502 ASSEMBLY LANGUAGE SUBROUTINES
Note the use of BASE- 1 in the indexed store instruction. The program clears
addresses BASE through BASE + NTIMES-1.
Although working backward through an array is often more efficient than
•
other hand, if you have N entries, they will occupy memory locations BASE
through BASE+N-1; now it is easy to find yourself working off the end of the
array.
•
You cannot extend absolute indexed addressing or zero-page indexed
addressing beyond 256 bytes. If an index register contains FF 16 ,
incrementing it
will produce a result of 00. Similarly, an index register contains 00, decrement-
if
or decrementing index registers when you might accidentally exceed the capacity
of eight bits. To extend loops beyond 256 bytes, use the indirect indexed (postin-
sequence will add 1 to the more sig-
dexed) addressing mode. Then the following
nificant byte of the indirect address when index register Y is incremented to 0.
INY ; INCREMENT INDEX REGISTER
BNE DONE
INC INDIR+1
DONE NOP
Here INDIR and INDIR + 1 are the locations on page that contain the indirect
address.
Example
Let us assume (INDIR) = 80 16 and (INDIR + 1) = 4C 16 so that
the initial ,
1.
Y, effective
base address is 4C80 I6 If the loop refers to the address (INDIR),
.
the
effective address is
(Y) = (Y) + 1 = 00
(INDIR+1) = (INDIR + 1) = 1 = 4D 16
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 47
IMPLICIT EFFECTS
Some of the implicit effects you should remember are
• The changing of the Negative and Zero flags by load and transfer instruc-
tions, such as LDA, LDX, LDY, PLA, TAX, TAY, TSX, TXA, and TYA.
• The dependence of the results of ADC and SBC instructions on the values of
the Carry and Decimal Mode flags.
• The special use of the Negative and Overflow flags by the BIT instruction.
The use of the memory address one larger than the specified one in the
indirect, indirect indexed, and indexed indirect addressing modes.
The changing of the stack pointer by PHA, PHP, PLA, PLP, JSR, RTS, RTI,
•
and BRK. Note that JSR and RTS change the stack pointer by 2, and BRK and
RTI change it by 3.
• The saving of the return address minus 1 by JSR and the addition of 1 to the
restored address by RTS.
• The inclusion of the Carry in the rotate instructions ROL and ROR. The
rotation involves nine bits, not eight bits.
Examples
1. LDX $40
This instruction affects the Negative and Zero flags, so those flags will no
longer reflect the value in the accumulator or the result of the most recent
opera-
tion.
2. ADC #$20
This instruction adds in the Carry flag as well as the immediate data (20
). The
l6
result will be binary if the Decimal Mode flag is cleared, but BCD
if the Decimal
Mode flag is set.
3. BIT $1700
This instruction sets the Overflow flag from the value of bit 6 of memory loca-
tion 1700 ]6 This is the only instruction that has a completely unexpected effect
.
on that flag.
1 48 6502 ASSEMBLY LANGUAGE SUBROUTINES
4. JMP ($1C00)
This instruction transfers control to the address in memory locations 1C00 16
and 1C01 16 (MSB in 1C01 16 ). Note that 1C01 16 is involved even though it is not
specified, since indirect addresses always occupy two bytes of memory.
5. PHA
This instruction not only saves the accumulator in memory, but it also decre-
7. ROR A
This instruction rotates the accumulator right 1 bit, moving the former con-
tents of bit position into the Carry and the former contents of the Carry into bit
position 7.
INITIALIZATION ERRORS
initialization routines must perform the following tasks, either for the
The
microcomputer system as a whole or for particular routines:
•
Load all RAM
locations with initial values. This includes indirect addresses
and other temporary storage on page 0. You cannot assume that a memory loca-
tion contains just because you have not used it.
•
Load all registers and flags with initial values. Reset initializes only the Inter-
rupt Disable flag (to 1). Note, in particular, the need to initialize the Decimal
Mode flag (usually with CLD) and the stack pointer (using the LDX, TXS
sequence)
•
Load all counters and indirect addresses with initial values. Be particularly
careful of addresses on page that areused in either the indirect indexed (postin-
dexed) addressing mode or the indexed indirect (preindexed) mode.
that a register, flag, or memory location contains zero just because you have not
used it.
branch. An awkward
feature of the 6502 is its lack of an unconditional relative
branch; you must either use JMP with absolute addressing or set a condition and
branch on it holding (SEC, BCS, DEST and CLV, BVC DEST).
These errors are generally easy to correct. Often the only problem is an error,
such as omitting the semicolon or other delimiter in front of a comment, that
confuses the assembler and results in a series of meaningless error messages.
There however, many common errors that assemblers will not recognize.
are,
The programmer should be aware that his or her program may contain such
errors even if the assembler does not report them. Typical examples are
ted line unless that line contains a label or definition that is used later in the pro-
gram. The easiest lines to omit are repetitions (that is, one or more lines that are
the same or sequences that start the same) or instructions that seem to be
unnecessary. Typical repetitions- are series of shifts, branches, increments, or
decrements. Instructions that may appear unnecessary include CLC, SEC, and so
forth.
Omitted designations. The assembler cannot tell if you omitted a designation
•
direct and all numbers to be decimal. Problems occur with numbers that are valid
as either decimal or hexadecimal values (such as 44 or 2050) and with binary
numbers (such as 00000110).
•
Misspellings that are examples are typing BCC instead of
still valid. Typical
an instruction line, the assembler will treat the line as a comment. This can be a
perplexing error, since the line appears in the listing but is not assembled
into
object code.
• If you enter an invalid operand such as LDA #$HX. Some assemblers will
accept this and generate incorrect code.
The assembler will recognize only errors that its developer anticipated. Pro-
grammers are often able to make mistakes that the developer never imagined,
much as automobile drivers are often capable of performing maneuvers that
never occurred in the wildest dreams of a highway designer or traffic planner.
Note that only a line-by-line hand checking of the program will find errors that
the assembler does not recognize.
IMPLEMENTATION ERRORS
Occasionally, a microprocessor's instructions simply do not
work the way the
designers or anyone else would expect. The 6502 has one implementation error
that is, fortunately, quite rare. The instruction JMP ($XXFF) where the Xs
represent any page number, does not work correctly. One would expect this
instruction to obtain the destination address from memory locations XXFF and
(XX + 1)00. Instead, it apparently does not increment the more significant byte
of the indirect address; it therefore obtains the destination address from memory
locations XXFF and XX00.
For example, JMP ($1CFF) will jump to the address
stored in memory locations 1CFF (LSB) and 1C00 16 (MSB), surely a curious
16
outcome. Most assemblers expect the programmer to ensure that no indirect
jumps ever obtain their destination addresses across page boundaries.
Confusing input ports and output ports. Many I/O interfaces use the READ/
•
WRITE line for addressing, so that reading and writing the same memory address
results in operations on different physical registers. Even when this is not done, it
may still be impossible to read back output data unless it is latched and buffered.
Attempting to perform operations that are physically impossible. Reading
•
data from an output device (such as a display) or sending data to an input device
(such as a keyboard) makes no physical sense. However, accidentally using the
wrong address will cause no assembly errors; the address, after all, is valid and the
assembler has no way of knowing that certain operations cannot be performed on
it. Similarly, a program may attempt to save data in a nonexistent address or in a
ROM.
1 52 6502 ASSEMBLY LANGUAGE SUBROUTINES
a port will change the status lines automatically, particularly if you are using a
6520 or 6522 parallel interface. Even reading or writing a port while debugging a
program will change the status lines. Be particularly careful of instructions like
comparisons and BIT which read a memory address even though they do not
change any registers, and instructions like decrement, increment, and shift which
both read and write a memory address (the actual operation, of course, takes
place inside the processor)
•
Reading or writing without checking status. Many devices can accept or pro-
vide data only when a status line indicates they are ready. Transferring data to or
from them at other times will have unpredictable effects.
Ignoring the differences between input and output. Remember that an input
•
although the computer is ready to accept data. On the other hand, an output
device normally starts out in the ready state, that is, it could accept data but the
computer usually has none to send it. In many situations, particularly when using
6520, 6522, 6551, or 6850 devices, you may have to disable the outputs initially
or send a null character (something that has no effect) to each output port just to
change its state from ready to not ready initially.
•
keep copies of output data. Remember that you may not be able to
Failing to
read the data back from the output port. If you need to repeat it later as part of
repeating a transmission that was incorrectly received, change part of it (turn on
or off one of several indicator lights attached to the same port), or save it as part
of the interrupted status (the data is the current priority level). You must save a
copy in memory. The copy must be updated every time the actual data is changed.
Reading data before it is stable or while it is changing. Be sure that you
•
understand exactly when the input device is guaranteed to produce stable data. In
the case of switches that may bounce, you may want to sample them twice (more
than a debouncing time apart) before taking any action. In the case of keys that
may bounce, you may want to take action only when they are released rather than
when they are pressed. The action on release also forces the operator to release
the key rather than holding it down. In the case of persistent data (such as in
serial I/O), you should center the reception, that is, read the data near the centers
of the pulses rather than at the edges where the values may be changing.
Forgetting to reverse the polarity of data being transferred to or from devices
•
that operate in negative logic. Many simple I/O devices, such as switches and dis-
plays, use negative logic. logic A
means that a switch is closed or a display is lit.
Common ten-position switches or dials also often produce data in negative logic,
as do many encoders. The solution is simple — complement the data (using EOR
#$FF) after reading it or before sending it.
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 53
Confusing actual I/O ports with registers that are inside I/O devices. Pro-
•
grammable I/O devices, such as the 6520, 6522, 6551, and 6850, have control or
command registers which determine how the device operates, and status registers
that reflect the current state of the device or the transfer. These registers are
inside the I/O devices; they are not connected to peripherals. Transferring data to
or from status or control registers is not the same as transferring data to or from
actual I/O ports.
• Using bidirectional ports improperly. Many devices, such as the 6520, 6522,
6530, and 6532, have bidirectional I/O ports. The ports (and perhaps 'even
individual lines) can be used either as inputs or outputs. Normally, resetting the
computer to avoid initial transients makes these ports inputs, so you must
explicitly change them to outputs if necessary. Be cautious when reading bits or
ports that are designated as outputs or writing into bits or ports that are desig-
nated as inputs.The only way to determine what will happen is to read the docu-
mentation for the specific device.
•
Forgetting to clear status after performing an I/O operation. Once the pro-
cessor has read data from an input port, that port should revert to the not ready
state. Similarly, once the processor has written data into an output port, that port
should revert to the not ready state. Some I/O devices change the status of their
ports automatically after input or output operations, but others either do not or
(as in the 6520) change status automatically only after input operations. Leaving
the status set can result in an endless loop or highly erratic operation.
COMMON ERRORS IN
INTERRUPT SERVICE ROUTINES
Many interrupt-related errors involve both hardware and software, but some
of the common mistakes include the following:
Failing to reenable interrupts during the service routine. The 6502 processor
•
any registers except the program counter and the status register. So the
accumulator, index registers, and scratchpad locations must be saved explicitly in
the stack.
• Saving or restoring registers in the wrong order. Registers must be restored
in the opposite order from that in which they were saved.
1 54 6502 ASSEMBLY LANGUAGE SUBROUTINES
•
Forgetting that the response to an interrupt includes saving the status
register and the program counter at the top of the stack. The status register is on
top and the program counter value is the actual return address, so the situation
differs from subroutines in which the return address minus 1 is normally at the
top of the stack.
•
Not disabling the interrupt during multibyte transfers or instruction
sequences that cannot be interrupted. In particular, you must avoid partial updat-
ing of data (such as time) that an interrupt service routine may use. In general,
interrupts should be disabled when the main program is changing memory loca-
tions that it shares with interrupt service routines.
• must run with inter-
Failing to reenable the interrupt after a sequence that
rupts disabled. A problem here
corollary
is that you do not want to enable inter-
rupts if they were not enabled when the sequence was entered. The solution is to
save the previous state of the Interrupt Disable flag (using PHP) before execut-
ing the sequence and restore the previous state (using PLP) afterward. Note,
however, that PLP restores the entire status register.
•
Failing to initialize or establish the value of the Decimal Mode flag. An inter-
rupt service routine should not assume a particular value (0) for the D flag.
Instead, it should initialize that flag with CLD or SED if it executes or ADC SBC
instructions. There is no need to save or restore the old D flag since that is done
automatically as part of the saving and restoring of the status register. Initializing
the D flag avoids problems if the service routine is entered from a program that
runs with the D flag set.
time clock interrupt will typically require no servicing other than an updating of
time, but the service routine still must clear the clock interrupt. This clearing may
involve reading a 6520 or 6522 I/O port or timer.
•
Failing to communicate with the main program. The main program will not
realize that the interrupt has been serviced unless it is informed explicitly. The
usual inform the main program is to have the interrupt service routine
way to
change a the main program can examine. The main program will then
flag that
know that the service routine has been executed. The procedure is comparable to
the practice of a postal patron raising a flag to indicate that he or she has mail to be
picked up. The postman lowers the flag after picking up the mail. Note that this
CHAPTER 3: COMMON PROGRAMMING ERRORS 1 55
simple procedure means that the main program must examine the flag often
enough to avoid missing data or messages. Of course, the programmer can always
provide an intermediate storage area (or buffer) that can hold many data items.
• Failing to save and restore priority. The priority of an interrupt is often held
in a write-only register or in a memory location. That priority must be saved just
like the registers and restored properly at the end of the service routine. If the
priority register is write-only, a copy of its contents must be saved in memory.
Introduction to the
Program Section
The program section contains sets of assembly language subroutines for the
6502 microprocessor. Each subroutine is documented with an introductory sec-
tion and comments; each is followed by at jeast one example of its use. The
introductory material contains the following information:
3. Registers used
4. Execution time
5. Program size
7. Special cases
8. Entry conditions
9. Exit conditions
10. Examples
Wehave made each routine as general as possible. This is most difficult in the
case of the input/output (I/O) and interrupt service routines described in Chap-
ters 10 and 11, since in practice these routines are always computer-dependent.
In such cases, we have limited the computer dependence to generalized input and
output handlers and interrupt managers. We have drawn specific examples there
from the popular Apple II computer, but the general principles are applicable to
other 6502-based computers as well.
In all routines, we have used the following parameter passing techniques:
157
1 58 6502 ASSEMBLY LANGUAGE SUBROUTINES
Where there has been a choice between execution time and memory usage, we
have chosen the approach that minimizes execution time. For example, in the
case of arrays that are more than 256 bytes long, it is faster to handle the full
pages, then handle the remaining partial page separately, than to handle the
entire array in a single loop. The reason is that the first approach can use an 8-bit
counter in an index register, whereas the second approach requires a 16-bit
counter in memory.
We have chosen the approach that minimizes the number of repetitive
also
calculations. For example, in the case of array indexing, the number of bytes be-
tween the starting addresses of elements differing only by one in a particular
subscript (known as the size of that subscript) depends only on the number of
bytes per element and the bounds of the array. Thus, the sizes of the various
subscripts can be calculated as soon as the bounds of the array are known; the
sizes are therefore used as parameters for the indexing routines, so that they need
not be calculated each time a particular array is indexed.
As for execution time, we have specified it for most short routines. For longer
routines, we have given an approximate execution time. The execution time of
programs involving many branches will obviously depend on which path is
followed in a particular case. This is further complicated for the 6502 by the fact
that branch instructions themselves require different numbers of clock cycles
depending on whether the branch is not taken, taken within the current page, or
taken across a page boundary. Thus, a precise execution time is often impossible
to define. The documentation always contains at least one typical example show-
ing an approximate or maximum execution time.
Our philosophy on error indications and special cases has been the following:
reasonable set of subroutines must deal with this issue, rather than ignoring it or
assuming that the user will always provide data in the proper form.
The subroutines are listed as follows:
Code Conversion
4A Binary to BCD Conversion 1 63
Arithmetic
6A 16-Bit Addition 230
6B 16-Bit Subtraction 233
6C 16-Bit Multiplication 236
6D 16-Bit Division 240
1 60 6502 ASSEMBLY LANGUAGE SUBROUTINES
String Manipulation
8A String Comparison 345
8B String Concatenation 349
8C Find the Position of a Substring 355
8D Copy a Substring from a String 361
8E Delete a Substring from a String 368
8F Insert a Substring into a String 374
Array Operations
9A 8-Bit Array Summation
382
9B 16-Bit Array Summation
385
9C Find Maximum Byte-Length Element 389
9D Find Minimum Byte-Length Element 393
9E Binary Search 397
9F Bubble Sort 403
INTRODUCTION TO THE PROGRAM SECTION 161
Input/Output
10A Read a Line of Characters from a Terminal 418
10B Write a Line of Characters to an Output Device 425
IOC Generate Even Parity 428
10D Check Parity 431
10E CRC-16 Checking and Generation 434
10F I/O Device Table Handler 440
10G Initialize I/O Ports 454
10H Delay Milliseconds 460
Interrupts
11A Unbuffered Interrupt-Driven Input/Output Using a 6850 ACIA 464
11B Unbuffered Interrupt/Driven Input/Output Using a 6522 VIA 472
11C Buffered Interrupt-Driven Input/Output Using a 6850 ACIA 480
1 1 Real-Time Clock and Calendar 490
Binary to BCD Conversion (BN2BCD) 4A
Examples
1. Data: (A) = 6E 16 (1 10 decimal) 2. Data: • (A) = B7 I6 (183 decimal)
163
164 CODE CONVERSION
BN2BCD:
CALCULATE 100 'S DIGIT
DIVIDE BY 100
Y = QUOTIENT
A = REMAINDER
LDY #0FFH ,-START QUOTIENT AT -1
SEC ;SET CARRY FOR INITIAL SUBTRACTION
D100LP:
INY ;ADD 1 TO QUOTIENT
SBC #100 ;SUBTRACT 100
BCS D100LP ;BRANCH IF A IS STILL LARGER THAN 100
ADC #100 ;ADD THE LAST 100 BACK
TAX ;SAVE REMAINDER
TYA
PHA ;SAVE 100'S DIGIT ON THE STACK
TXA ;GET REMAINDER
;DATA
TEMP: .BLOCK .-TEMPORARY USED TO COMBINE l'S AND 10'S DIGITS
4A BINARY TO BCD CONVERSION (BN2BCD) 165
SAMPLE EXECUTION:
SC0401:
.-CONVERT OA HEXADECIMAL TO 10 BCD
LDA #0AH
JSR BN2BCD
BRK -A=0, Y=10H
.END
BCD to Binary Conversion (BCD2BN) 4B
using shifts (10 = 8 + 2, and multiplying by Data Memory Required: One byte anywhere in
Examples
1. Data: (A) = 99 16 2. Data: (A) = 23 l6
Time: 38 cycles
166
4B BCD TO BINARY CONVERSION (BCD2BN) 167
BCD2BN:
.-MULTIPLY UPPER NIBBLE BY 10 AND SAVE IT
; TEMP := UPPER NIBBLE * 10 WHICH EQUALS UPPER NIBBLE (8 + 2)
TAY ;SAVE ORIGINAL VALUE
AND #0F0H ;GET UPPER NIBBLE
LSR A ;DIVIDE BY 2 WHICH = UPPER NIBBLE
STA TEMP ;SAVE * 8
LSR A .-DIVIDE BY 4
LSR A .-DIVIDE BY 8: A = UPPER NIBBLE * 2
CLC
ADC TEMP
STA TEMP ;REG A = UPPER NIBBLE * 10
RTS
; DATA
TEMP: . BLOCK
SAMPLE EXECUTION:
SC0402:
.END
Binary to Hexadecimal ASCII Conversion
(BN2HEX) 4C
Examples
1. Data: (A) - FB„ 2. Data: (A) = 59 16
168
4C BINARY TO HEXADECIMAL ASCII CONVERSION (BN2HEX) 1 69
BN2HEX:
.•SUBROUTINE NASCII
.-PURPOSE: CONVERT A HEXADECIMAL DIGIT TO ASCII
,-ENTRY: A = BINARY DATA IN LOWER NIBBLE
;EXIT: A = ASCII CHARACTER
.'REGISTERS USED: A,P
NASCII:
CMP #10
BCC NASI .-BRANCH IF HIGH NIBBLE < 10
CLC
ADC #7 ;ELSE ADD 7 SO AFTER ADDING '0 THE 1
SAMPLE EXECUTION:
' '
SC0403:
;CONVERT TO '00
LDA #0
JSR BN2HEX
BRK ;A='0'=3dH, Y='0'=30H
.END
Hexadecimal ASCII to Binary Conversion
(HEX2BN) 4D
digit left four bits and combines it with the representations of hexadecimal digits).
Examples:
1. Data: (A) = 44 16 (ASCII D) 2. Data: (A) = 31 (ASCII I)
6
(Y) =37 I6 (ASCII7) (Y) =
,
42 16 (ASCII B)
Result: (A) = D7 lf
-
171
172 CODE CONVERSION
HEX2BN:
PHA SAVE HIGH CHARACTER
TYA GET LOW CHARACTER
JSR A2HEX CONVERT IT
STA TEMP SAVE LOW NIBBLE
PLA GET THE HIGH CHARACTER
JSR A2HEX CONVERT IT
ASL A
ASL A
ASL A
ASL A ;SHIFT HIGH NIBBLE TO THE UPPER 4 BITS
ORA TEMP ;OR IN THE LOW NIBBLE
RTS
SUBROUTINE: A2HEX
PURPOSE: CONVERT ASCII TO A HEX NIBBLE
ENTRY: A = ASCII CHARACTER
EXIT: A = BINARY VALUE OF THE ASCII CHARACTER
REGISTERS USED: A,P
A2HEX:
SEC .•SUBTRACT ASCII OFFSET
SBC #'0'
CMP #10
BCC A2HEX1 ;BRANCH IF A IS A DECIMAL DIGIT
SBC #7 ;ELSE SUBTRACT OFFSET FOR LETTERS
A2HEX1:
RTS
;DATA
TEMP: .BLOCK 1
SAMPLE EXECUTION:
4D HEXADECIMAL ASCII TO BINARY CONVERSION (HEX2BN) 1 73
SC0404:
.-CONVERT 'C7' TO C7 HEXADECIMAL
LDA #'C
LDY #'7'
JSR HEX2BN
BRK ;A=C7H
;CONVERT 2F TO 2F HEXADECIMAL
'
'
LDA #'2'
LDY # 'F '
.END
Conversion of a Binary Number to Decimal ASCII
(BN2DEC) 4E
keeps dividing by ten until it produces a quo- value (address NGFLAG), temporary storage for
the original value (two bytes starting at address
tient of zero. It converts each digit of'the quo-
VALUE), and temporary storage for the value
tient to ASCII (by adding ASCII 0) and con- mod 10 (two bytes starting at address MOD10).
catenates the digits ajong with an ASCII Also, two bytes on page for the buffer pointer
(address BUFPTR, taken as 00D0, 6 and 00D1 I6
minus sign (in front) if the original number in the listing) This data memory does not include
.
More significant byte of return address ASCII - (if original number was negative)
ASCII digits (most significant digit first)
Less significant byte of output buffer address
More significant byte of output buffer
address
Examples
1. Data: Value to convert = 3EB7 16 2. Data: Value to convert = FFC8, 6
174
4E BINARY NUMBER TO ASCII DECIMAL STRING (BN2DEC) 1 75
;PROGRAM
BN2DEC:
;SAVE PARAMETERS
PLA
STA RETADR ;SAVE LOW BYTE OF RETURN ADDRESS
PLA
STA RETADR+1 ;SAVE HIGH BYTE
PLA
STA VALUE ;SAVE LOW BYTE OF VALUE
PLA
STA VALUE+1 ;SAVE HIGH BYTE OF THE VALUE TO CONVERT
STA NGFLAG ;SAVE MSB OF VALUE AS SIGN OF VALUE
BPL GETBP ; BRANCH IF VALUE IS POSITIVE
LDA #0 ;ELSE TAKE ABSOLUTE VALUE (0 - VALUE)
SEC
SBC VALUE
STA VALUE
Y
LDA #0
SBC VALUE+1
STA VALUE+1
GETBP:
PLa ;SAVE STARTING ADDRESS OF OUTPUT BUFFER
STA BUFPTR
PLA
STA BUFPTR+1
LDX #16
CLC ; CLEAR CARRY
DVLOOP:
ROL VALUE SHIFT THE CARRY INTO DIVIDEND BIT
ROL VALUE+1 WHICH WILL BE THE QUOTIENT
ROL MOD10 AND SHIFT DIVIDEND AT THE SAME TIME
ROL MOD10+1
DECCNT:
DEX
BNE DVLOOP
CONCH:
LDA MOD10
CLC
ADC #'0' ;CONVERT 0..9 TO ASCII '0',
JSR CONCAT
SUBROUTINE: CONCAT
PURPOSE: CONCATENATE THE CHARACTER IN REGISTER A TO THE
FRONT OF THE STRING ACCESSED THROUGH BUFPTR
ENTRY: BUFPTR [0] = LENGTH
EXIT: REGISTER A CONCATENATED (PLACED IMMEDIATELY AFTER THE LENGTH BYTE)
REGISTERS USED: A,P,Y
CONCAT:
PHA ;SAVE THE CHARACTER ON THE STACK
CLC
ADC #1 ; INCREMENT LENGTH BY 1
STA (BUFPTR) ,Y ; UPDATE LENGTH
RTS
;DATA
RETADR: . BLOCK SAVE RETURN ADDRESS
NGFLAG: •BLOCK SIGN OF ORIGINAL VALUE
VALUE: .BLOCK VALUE TO CONVERT
MOD10: , BLOCK MODULO 10 TEMPORARY
SAMPLE EXECUTION:
SC0405:
; CONVERT TO '0
LDA BUFADR+1 ;HIGH BYTE OF BUFFER ADDRESS
PHA
LDA BUFADR ;LOW BYTE BUFFER ADDRESS
PHA
LDA VALUEl+1 ;HIGH BYTE OF VALUE
PHA
LDA VALUE ;LOW BYTE OF VALUE
PHA
JSR BN2DEC ;CONVERT
BRK ;BUFFER SHOULD o 1
;CONVERT 32767 TC
LDA BUFADR+1 ;HIGH BYTE OF BUFFER ADDRESS
PHA
LDA BUFADR ;LOW BYTE BUFFER ADDRESS
PHA
LDA VALUE 2+1 ;HIGH BYTE OF VALUE
PHA ,
PHA
4E BINARY NUMBER TO ASCII DECIMAL STRING (BN2DEC) 1 79
.END
Conversion of ASCII Decimal to Binary
(DEC2BN) 4F
ble ASCII - or + sign, and a series of ASCII Execution Time: 670 cycles (approximately)
Program Size: 171 bytes
digits to two bytes of binary data. Note that
the length is an ordinary binary number, not Data MemoryRequired: Four bytes anywhere in
RAM an index, a two-byte accumulator
for
an ASCII number. (starting address ACCUM), and a flag indicating
Procedure: The program sets a flag if the the sign of the number (address NGLAG), two-
first ASCII character is a minus sign and skips bytes on page zero for a pointer to the string
(address BUFPTR, taken as 00F0 16 and 00F1 16
in
over a leading plus sign. It then converts each
the listing).
subsequent digit to decimal (by subtracting Special Cases:
ASCII zero) multiplies the previous digits by
,
1. If the string contains something other than a
ten (using the fact that 10=8 + 2, so a leading sign or a decimal digit, the program
retu.ns with the Carry flag set to The result in
multiplication by ten can be reduced to
1.
left
registers A and Y is invalid.
shifts and additions), and adds the new digit
2. If the string contains only a leading sign
to the product. Finally, the program subtracts (ASCII + or ASCII -), the program returns
the result from zero if the original number with the Carry flag set to 1 and a result of zero.
(A) = More significant byte of string (A) = More significant byte of binary value
address (Y) = Less significant byte of binary value
(Y) = Less significant byte of string Carry flag is if the string was valid; Carry
address flag is the string contained an invalid
1 if
Examples
1. Data: String consists of Result: (A) = 04, 6 (more significant byte of
binary data)
04 (number of bytes in string)
34 (ASCII 4)
That is, the number is + 1,234, .
180
4F ASCII DECIMAL STRING TO BINARY NUMBER (DEC2BN) 181
;PROGRAM
DEC2BN:
STA BUFPTR +1
STY BUFPTR ;SAVE THE STRING ADDRESS
182 CODE CONVERSION
INITIALIZE
LDY #0
LDA (BUFPTR) , Y ;GET LENGTH
TAX v
; TO REGISTER X
LDA #1
STA INDEX ; INDEX := 1
LDA #0
STA ACCUM ;ACCUM :=
STA ACCUM+1
STA NGFLAG ;SIGN OF NUMBER IS POSITIVE
CNVERT:
LDY INDEX
LDA (BUFPTR) ,Y ;GET NEXT CHARACTER
CHKDIG: CMP #'0'
BMI EREXIT ;ERROR IF < '0' (NOT A DIGIT)
CMP #'9'+l
BPL EREXIT jERROR IF > '9' (NOT A DIGIT)
PHA ;SAVE THE DIGIT ON THE STACK
LDA NGFLAG
BPL OKEXIT ;BRANCH IF THE VALUE WAS POSITIVE
LDA #0 ;ELSE REPLACE RESULT WITH -RESULT
SEC
SBC ACCUM
STA ACCUM
LDA #0
SBC ACCUM+1
STA ACCUM+1
EREXIT:
SEC
EXIT:
LDA ACCUM+1 ;GET HIGH BYTE OF VALUE
LDY ACCUM
RTS
; DATA
INDEX: .BLOCK 1 ; INDEX INTO THE STRING
ACCUM: .BLOCK 2 .-ACCUMULATED VALUE (2 BYTES)
NGFLAG: .BLOCK 1 ,-SIGN OF NUMBER
SAMPLE EXECUTION:
1 84 CODE CONVERSION
SC0406:
;CONVERT '1234 TO 04D2 HEX
'
LDA ADRS1+1
LDY ADRS1 ;AY = ADDRESS OF SI
JSR DEC2BN
BRK ?A = 04, Y = D2 HEX
SI .BYTE 4, '1234'
S2 .BYTE 6, '+32767'
S3 .BYTE 6, '-32768'
.END
Lower-Case to Upper-Case Translation (LC2UC) 4G
Procedure: The program determines from Execution Time: 18 cycles if the original
character is valid, fewer cycles otherwise.
comparisons whether the data is an ASCII
lower-case letter. If it is, the program Program Size: 12 bytes
subtracts 20, 6 from it, thus converting it to its Data Memory Required: None
upper-case equivalent. If it is not, the pro-
gram leaves it unchanged.
Examples
1. Data: (A) = 62 l6 (ASCII b) 2. Data: (A) = 74 16 (ASCII t)
185
'
LC2UC:
CMP #'a'
BCC $1 ; BRANCH IF < 'a'
CMP # '
z ' +1
BCS EXIT ;BRANCH IF > 'z'
SEC
SBC #20H .-CHANGE 'a'-.'z 1
into 'A'.-'Z'
EXIT:
RTS
SAMPLE EXECUTION:
SC0407:
;CONVERT LOWER CASE E TO UPPER CASE
e
LDA # '
JSR LC2UC
BRK ;A='E'=45H
JSR LC2UC
BRK ;A='Z'=5AH
LDA #'A"
JSR LC2UC
BRK ;A-'A'-41H
Examples
1. Data: (A) = 35, 6 (ASCII 5) 3. Data: (A) = 2A 16 (ASCII *)
187
188 CODE CONVERSION
; Time: 14 cycles
ASC2EB:
AND #7FH ;BE SURE BIT 7=0
TAY ;USE ASCII AS INDEX INTO EBCDIC TABLE
LDA EBCDIC, Y ;GET EBCDIC
RTS
BS HT LF VT FF CR SO SI ASCII ;
.BYTE
SPACE!"#$%&'
CAN EM
()* ,-/
SUB ESC FS GS RS
O0OH,O0OH,OOOH,O0OH,OO0H,OO0H,O0 0H,00OH
040H,05AH,0 7EH,040H,05BH,06CH,050H,07CH
VS
EBCDIC
ASCII
EBCDIC
i
.
BYTE
BYTE
01234567 +
04DH,05DH,05CH,04EH,0 6BH,060H,04BH,061H
.BYTE
.BYTE
8
HIJKLMNO
7BH,0C1H,0C2H,0C3H,0C4H,0C5H,0C6H,0C7H
? ASCII
EBCDIC
(ASCII
(EBCDIC
.BYTE
.BYTE
PQRSTUVW
0C8H, 0C9H, 0D1H, 0D2H, 0D3H, 0D4H, 0D5H, 0D6H
.BYTE
.BYTE
hijklmno
a b c d e f
7CH,081H,082H,083H,084H,085H,086H,087H
pqr stuvw
088H,089H,0 91H,092H,093H,094H,095H,0 96H
g •ASCII-
EBCDIC
(ASCII
(EBCDIC
(ASCII
. BYTE 97H,098H,099H,OA2H,OA3H,OA4H, 0A5H,0A6H (EBCDIC
x y z { }1
DEL (ASCII
.BYTE 0A7H,0A8H,0A9H,040H,04FH,040H,05FH,00 7H (EBCDIC
'
SAMPLE EXECUTION:
SC0408:
;CONVERT ASCII 'A'
LDA #'A' ;ASCII 'A'
JSR ASC2EB
BRK ;EBCDIC 'A' 0C1H
Examples
1. Data: (A) = 85, 6 (EBCDIC e) 2. Data: (A) = 4E I6 (EBCDIC +)
Time: 12 cycles
190
41 EBCDIC TO ASCII CONVERSION (EB2ASC) 191
EB2ASC:
TAY
LDA ASCII, Y TRANSLATE
RTS
ASCI
;
BRITISH $ . < + ( |
EBCDIC
,'.' ,'<- , (
;
.BYTE OOOH.OOOH,' •
+ |. . . .
f f ; ASCI
; EBCDIC
• BYTE '&' ,000H, OOOH.OOOH, OOOH.OOOH, 000H.000H
; ASCI
; EBCDIC
.BYTE 000H,000H, '
!
,$' ,'*> ,<)i
f
iii
f<
-i
;ASCli
; EBCDIC
• BYTE '-' ,'/'
.OOOH.OOOH, OOOH,OOOH,O0OH,000H ; ASCI
% -.- > ? ; EBCDIC
.BYTE 00OH,00OH,'*' ,',• ,•%' ,040H,'>' ,'?' ; ASCI
; EBCDIC
.BYTE 0OOH,00OH,O0OH,0O0H,000H,0OOH,00OH,00OH ; ASCI
: @ ' = »
,-EBCDIC
• BYTE OO0H,OOOH,': ,'@- ,•• ,• = •
,."., 000 h ; ASCI
a b c d e f a
.BYTE 000H,'a' ; EBCDIC
,'b' ,'c" ,'d' .'e' ,'f ,'g' ;ASCII
h i
; EBCDIC
.BYTE 'h' ,'i' ,OOOH,0OOH,OOOH,O00H,O00H,O00H ;ASCII
3 k 1 m n o .•EBCDIC
.BYTE OOOH.'j- ,'k' ,'l- ,'m 1
,'n 1
,'o' ,'p- ; ASCI
q r
.•EBCDIC
1 92 CODE CONVERSION
0OOH,OOOH,OOOH,OOOH,OOOH,OOOH,OOOH,OO0H ;ASCII -
BYTE ; EBCDIC
A B C D E F G
,'D 1
,'E' ,'F' ,'G' f ASCII
.BYTE 000H,'A' ,'B 1
, 'C '
; EBCDIC
H I
; ASCII
BYTE 'H' ,'I' ,O0OH,0OOH,O0OH,000H,O0OH,OOOH
K L M N P ; EBCDIC
J
,'K' ,'L' ,'M' ,'N' ,'0' ,'P' ;ASCII
.BYTE 000H,'J'
; EBCDIC
R
'Q' ,000H,O0OH,000H,O0OH,000H,000H ; ASCII
BYTE ,'R*
X ; EBCDIC
S T U V W
; ASCI
.BYTE OOOH,OOOH,'S' ,'T 1
,"0' ,'V , 'W ,
'X'
; EBCDIC
BYTE
.BYTE
01234567
Y
"Y*
'0'
Z
,'Z'
,'l"
,O0OH,00OH>O00H,OOOH,000H,0O0H
; EBCDIC
; ASCI
; EBCDIC
9
'9' ,000H,000H,0d0H,00UH,000H,000H,000H ;ASCII
.BYTE
SAMPLE EXECUTION:
SC0409:
.•CONVERT EBCDIC 'A'
LDA #0C1H ;EBCDIC 'A'
JSR EB2ASC
;ASCII 'A' = 041H
BRK
'1'
;CONVERT EBCDIC '1'
LDA #0F1H ,-EBCDIC
JSR EB2ASC
BRK ;ASCII '1' = 031H
Places a specified value in each byte of a one loop, since 8-bit counters can be used
memory area of known size, starting at a instead of a The approach
16-bit counter.
given address. does, however, require somewhat more
Procedure: The program fills all the whole memory than a single loop with a 16-bit
pages with the specified value first and then A size of 0000 16 causes an
counter. exit with
fills the remaining partial page. This approach
no memory changed.
is faster than dealing with the entire area
in
address VALUE), and the return address (two tion, sinceboth this routine and most systems
bytes starting at address RETADR). Also two programs use that page.
bytes on page for an array pointer (taken as
193
1 94 ARRAY MANIPULATION
Examples
1. Data: Value = FF. Data: Value = EA, 6 (6502 operation
MFILL:
;POP THE PARAMETERS FROM THE STACK
PLA
5A MEMORY FILL (MFILL) 195
STA RETADR
PLA
STA RETADR+1 ;GET THE RETURN ADDRESS
PLA
STA VALUE ;GET FILL VALUE
PLA
STA ARYSZ
PLA
STA ARYSZ+1 ;GET SIZE OF AREA
PLA
STA ARYPTR
PLA
STA ARYPTR+1 ;GET STARTING ADDRESS OF AREA
LDA RETADR+1
PHA
LDA RETADR
PHA ; RESTORE RETURN ADDRESS
;DATA
ARYSZ: .BLOCK ,-NUMBER OF BYTES TO INITIALIZE
VALUE: .BLOCK ; VALUE TO INITIALIZE ARRAY WITH
1 96 ARRAY MANIPULATION
SAMPLE EXECUTION
SC0501:
;FILL A SMALL BUFFER WITH 00
LDA BF1ADR+1
PHA
LDA BF1ADR
PHA ;PUSH STARTING ADDRESS
LDA BF1SZ+1
PHA
LDA BF1SZ
PHA ;PUSH NUMBER OF BYTES
LDA #0
PHA ;PUSH VALUE
JSR MFILL ;FILL BUFFER
BRK
.END
Block Move (BLKMOV) 5B
the data by handling complete pages sepa- address MVELEN), four bytes on page for
source and destination pointers (starting at
rately from the remaining partial page. This
addresses MVSRCE and MVDEST taken as
approach allows the program to use 8-bit addresses 00DO 16 and 00D1 16 - source
counters' rather than a 16-bit counter, thus pointer - and addresses 00D2, 6 and 00D3,
6
-
destination pointer — in the listing).
reducing execution time (although increas-
Special Cases:
ing memory usage) An area size (number of
.
table.
197
1 98 ARRAY MANIPULATION
Less significant byte of return address The block of memory is moved from the
More significant byte of return address source area to the destination area. If the
number of bytes to be moved is NBYTES,
Less significant byte of number of bytes
the lowest address in the destination area is
to move
DEST, and the lowest address in the source
More significant byte of number of
area is SOURCE, then the area from
bytes to move
addresses SOURCE through SOURCE +
Less significant byte of lowest address NBYTES - is moved to addresses DEST
1
Examples
1. Data: Number of bytes to move = 0200 )6 Note that Example 2 presents a more com-
Lowest address in destination plex problem than Example 1 because the
area
= 05D1 16
source and destination areas overlap. If, for
Lowest address in source area
= 035E 16 instance, the program were simply to move
Result:- The contents of memory locations data to the destination area starting from the
035E 16 through 055D 16 are moved lowest address, it would initially move the
to 05D 1,6 through 07D0 16 .
Lowest address in destination needed later in the move. The solution to this
area = C946 )6 problem is to move the data starting from the
Lowest address in source area
= C300, 6 highest address if the destination area is
Result: The contents of memory locations above the source area but overlaps it.
EXIT:
RTS
5B BLOCK MOVE (BLKMOV) 20
******************************************
SUBROUTINE: MVELFT
PURPOSE: MOVE SOURCE TO DESTINATION STARTING FROM
THE LOWEST ADDRESS
ENTRY: MVSRCE = 2 BYTE LOWEST ADDRESS OF SOURCE AREA
MVDEST = 2 BYTE LOWEST ADDRESS OF DESTINATION AREA
MVELEN = 2 BYTE NUMBER OF BYTES TO MOVE
EXIT: SOURCE MOVED TO DESTINATION
********************************************
MVELFT:
LDY #0 ;ZERO INDEX
LDX MVELEN+1 ;X= NUMBER OF FULL PAGES TO MOVE
BEQ MLPART ;IF X = THEN DO PARTIAL PAGE
MLPAGE:
LDA (MVSRCE) ,Y
STA (MVDEST) ,Y ;MOVE ONE BYTE
INY ,-NEXT BYTE
BNE MLPAGE .-CONTINUE UNTIL 256 BYTES ARE MOVED
INC MVSRCE+1 .•ADVANCE TO NEXT PAGE OF SOURCE
INC MVDEST+1 ; AND DESTINATION
DEX .•DECREMENT PAGE COUNT
BNE MLPAGE .-CONTINUE UNTIL ALL FULL PAGES ARE MOVED
ML PART:
LDX MVELEN ;GET LENGTH OF LAST PAGE
BEQ MLEXIT ;BRANCH IF LENGTH OF LAST PAGE =
;REGISTER Y IS
MLLAST:
LDA (MVSRCE) ,Y
STA (MVDEST) ,Y ,-MOVE BYTE
INY ,-NEXT BYTE
DEX ;DECREMENT COUNTER
BNE MLLAST .-CONTINUE UNTIL LAST PAGE IS DONE
MLEXIT:
RTS
******************************************
SUBROUTINE: MVERHT
PURPOSE: MOVE SOURCE TO DESTINATION STARTING FROM
THE HIGHEST ADDRESS
ENTRY: MVSRCE = 2 BYTE LOWEST ADDRESS OF SOURCE AREA
MVDEST = 2 BYTE LOWEST ADDRESS OF DESTINATION AREA
MVELEN = 2 BYTE NUMBER OF BYTES TO MOVE
•
MVERHT:
LDA MVELEN+1
CLC
ADC MVDEST+1
STA MVDEST+1 ; POINT TO LAST PAGE OF DESTINATION
MRO:
DEY ;BACK UP Y TO THE NEXT BYTE
LDA (MVSRCE) ,Y
STA (MVDEST) ,Y ;MOVE BYTE
CPY #0
BNE MRO ; BRANCH IF NOT DONE WITH THE LAST PAGE
MRPAGE:
LDX MVELEN+1 ;GET HIGH BYTE OF COUNT AS PAGE COUNTER
BEQ MREXIT ;BRANCH IF HIGH BYTE = (NO FULL PAGES)
MR1:
DEC MVSRCE+1 ;BACK UP TO PREVIOUS PAGE OF SOURCE
DEC •MVDEST+1 ; AND DESTINATION
MR2:
DEY ;BACK UP Y TO THE NEXT BYTE
LDA (MVSRCE) ,Y
STA (MVDEST) ,Y ;MOVE BYTE
CPY #0
BNE MR2 BRANCH IF NOT DONE WITH THIS PAGE
DEX DECREMENT PAGE COUNTER
BNE MR1 BRANCH IF NOT ALL PAGES ARE MOVED
MREXIT:
RTS
;DATA SECTION
MVELEN BLOCK
. 2 ; LENGTH OF MOVE
SC0502:
LDA SRCE+1
PHA PUSH HIGH BYTE OF SOURCE
LDA SRCE
PHA PUSH LOW BYTE OF SOURCE
LDA DEST+1
PHA PUSH HIGH BYTE OF DESTINATION
LDA DEST
PHA PUSH LOW BYTE OF DESTINATION
5B BLOCK MOVE (BLKMOV) 203
LDA ,
LEN+1
PHA ;PUSH HIGH BYTE OF LENGTH
LDA LEN
PHA ;PUSH LOW BYTE OF LENGTH
JSR BLKMOV ;MOVE DATA FROM SOURCE TO DESTINATION
BRK ; FOR THE DEFAULT VALUES MEMORY FROM 80 HEX
; THROUGH 97F HEX IS MOVED TO 900 HEX THROUGH
; A7F HEX.
JMP SC0502
.END ; PROGRAM
One-Dimensional Byte Array Index (D1BYTE) 5C
byte-length array, given the base address and Execution Time: 74 cycles
the subscript (index) of the element. Program Size: 37 bytes
The program simply adds
Procedure: the
Data Memory Required: Four bytes anywhere in
base address to the subscript. The sum is the RAM to hold the return address (two bytes start-
ing at address RETADR) and the subscript (two
address of the element.
bytes starting at address SUBSCR).
Examples
Base address = 0E00 16 Data: Base address = C4E1| 6
1. Data:
Subscript = 012C, 6 Subscript = 02E4 16
204
5C ONE-DIMENSIONAL BYTE ARRAY INDEX (D1 BYTE) 205
Time: 74 cycles
DlBYTEs
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
;GET SUBSCRIPT
PLA
STA SS
PLA
STA SS+1
LDA RETADR
PHA ; RESTORE RETURN ADDRESS
;DATA
RETADR: .BLOCK 2 ; TEMPORARY FOR RETURN ADDRESS
SS: .BLOCK 2 ; SUBSCRIPT INTO T HE ARRAY
SAMPLE EXECUTION:
SC0503:
;PUSH ARRAY ADDRESS
LDA ARYADR+1 ;HIGH BYTE
PHA
LDA ARYADR ;LOW BYTE
PHA
;PUSH A SUBSCRIPT
LDA SUBSCR+1 ;HIGH BYTE
PHA
LDA SUBSCR ;LOW BYTE
PHA
JSR D1BYTE CALCULATE ADDRESS
BRK AY = ARY+2
= ADDRESS OF ARY(2), WHICH CONTAINS 3
JMP SC0503
.END ; PROGRAM
One-Dirriensional Word Array Index (D1WORD) 5D
Examples
1. Data: Base address = A148 16 2. Data: Base address = C4E0, 6
Subscript = 01A9 16 Subscript = 015B 16
Result: Address of first byte of element Result: Address of first byte of element
= A148 16 + 2 x 01A9 16 = C4E0 16 + 2 x 015B 16
= A148 16 + 0342, 6 = A49A 16 .
= C4E0 16 + 02B6 16 = C796 16 .
207
208 ARRAY MANIPULATION
Time: 78 cycles
D1W0RD:
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
LDA RETADR+1
PHA
LDA RETADR
PHA ; RESTORE RETURN ADDRESS
TXA ;GET HIGH BYTE BACK TO REGISTER A
RTS ;EXIT V
;DATA
RETADR: .BLOCK 2 ; TEMPORARY FOR RETURN ADDRESS
SS: .BLOCK 2 SUBSCRIPT INTO THE ARRAY
SAMPLE EXECUTION:
SC0504:
;PUSH ARRAY ADDRESS
LDA ARYADR+1
PHA
LDA ARYADR
PHA
;PUSH A SUBSCRIPT
LDA SUBSCR+1
PHA
LDA SUBSCR
PHA
JSR D1WORD CALCULATE ADDRESS
BRK FOR THE INITIAL TEST DATA
AY = STARTING ADDRESS OF ARY(3)
= ARY + (3*2)
= ARY + 6
= ARY (3) CONTAINS 240 HEX
JMP SC0504
;TEST DATA
SUBSCR: .WORD 3 ;TEST SUBSCRIPT INTO ARY
ARYADR: .WORD ARY ;BASE ADDRESS OF ARRAY
;THE ARRAY (8 ENTRIES)
ARY: .WORD 0180H , 01C0H 0200H 0240H 0280H 02C0H 03E7H, 0A34H
, , , , ,
• END j PROGRAM
Two-Dimensional Byte Array Index (D2BYTE) 5E
both subscripts are assumed memory to hold the return address (two bytes
is, by rows) and
starting at addressRETADR), the row subscript
to begin at zero. (two bytes starting at address SSI), the size
Procedure: The program multiplies the row (length) of the rows (two bytes starting at address
size (number of columns in a row) times the SS1SZ), the column subscript (two bytes starting
at address SS2), and the product of row size
times
row subscript (since the elements are stored row subscript (two bytes starting at address
by rows) and adds the product to the column PROD).
subscript. then adds the sum to the base
It
210
5E TWO-DIMENSIONAL BYTE ARRAY INDEX (D2BYTE) 211
Examples
1. Data: Base address = 3C00 I6 The general formula is
Column subscript0004 16 = ADDRESS OF ELEMENT = BASE ADDRESS
Size of row (number of columns)
= 0018 16 OF ARRAY + ROW SUBSCRIPT x SIZE OF ROW
Row subscript = 0003 ]6
+ COLUMN SUBSCRIPT
Result: Address of element = 3C00, 6 Note that we refer to the size of the row
+ 0003 16 x 0018 16 + 0004 16 subscript; the size is the number of consecu-
= 3C00 I6 -f 0048, 6 + 0004, 6
= 3C4C I6 .
tive memory addresses for which the
Thus the address of ARRAY (3,4) subscript has the same value. This is also the
is3C4C 16 .
number of bytes from the starting address of
an element to the starting address of the ele-
Data: Base address = 6A4A ]6
= 0035 16 ment with the same column subscript but a
Column subscript
Size of row (number of columns) row subscript one larger.
= 0050 16
Row subscript = 0002, 6
of a row
D2BYTE:
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
STA PROD
STA PROD+1
L DX #17 ;NUMBER OF SHIFTS = 17
CLC
5E TWO-DIMENSIONAL BYTE ARRAY INDEX (D2BYTE) 213
MULLP:
ROR PROD+1 ; SHIFT PARTIAL PRODUCT
ROR PROD
ROR SS1+1 ; SHIFT MULTIPLIER
ROR SSI
BCC DECCNT
CLC ;ADD MULTIPLICAND TO PARTIAL PRODUCT
LDA SS1SZ ; IF NEXT BIT OF MULTIPLIER IS 1
ADC PROD
STA PROD
LDA SS1SZ+1
ADC PROD+1
STA PROD+1
DECCNT:
DEX
BNE MULLP
™
RTS
''GET
;EXIT
HIGH BYTE BACK TO REGISTER A
; DATA
SAMPLE EXECUTION:
SC0505:
;PUSH ARRAY ADDRESS
LDA ARYADR+1
PHA
LDA ARYADR
PHA
JMP SC0505
;DATA
SUBSl: .WORD 2 SUBSCRIPT 1
SSUBS1: .WORD 8 SIZE OF SUBSCRIPT 1
.END ; PROGRAM
Two-Dimensional Word Array Index (D2WORD) 5F
215
216 ARRAY MANIPULATION
Examples
= 5E14 16 The general formula is
1. Data: Base address
Column subscript = 0008, 6 STARTING ADDRESS OF ELEMENT
Size of a row (in bytes) = 001C 16 = BASE ADDRESS OF ARRAY
each row has 0014, or 0O0E 16
(i.e., + ROW SUBSCRIPT x SIZE OF ROW
word-length elements) + COLUMN SUBSCRIPT x 2
5EB0, 6 and5EBl 16
. umns or the maximum column index is that
A[0,1],..., A[K,L]),
,
5F TWO-DIMENSIONAL WORD ARRAY INDEX (D2WORD) 217
D2WORD:
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
ADD
;MULTIPLY FIRST SUBSCRIPT * ROW SIZE (IN BYTES) USING THE SHIFT AND
•ALGORITHM. THE RESULT WILL BE IN SSI
LDA #0 PARTIAL PRODUCT = ZERO INITIALLY
;
STA PROD
STA PROD+1
LDX #17 ;NUMBER OF SHIFTS =17
CLC
MULLP:
ROR PROD+1 ; SHIFT PARTIAL PRODUCT
ROR PROD
ROR SSl+1 ;SHIFT MULTIPLIER
ROR SSI
BCC DECCNT
CLC ;ADD MULTIPLICAND TO PARTIAL PRODUCT
LDA SS1SZ ; IF NEXT BIT OF MULTIPLIER IS 1
ADC PROD
STA PROD
LDA SS1SZ+1
ADC PROD+1
STA PROD+1
DECCNT:
DEX
BNE MULLP
PLA
CLC
ADC SSI
TAY REGISTER Y = LOW BYTE
;
PLA
;DATA
5F TWO-DIMENSIONAL WORD ARRAY INDEX (D2WORD) 219
SAMPLE EXECUTION:
SC0506:
;PUSH ARRAY ADDRESS
LDA ARYADR+1
PHA
LDA ARYADR
PHA
LDA SUBS2+1
PHA
LDA SUBS2
PHA
; DATA
SUBS1: • WORD 2 ; SUBSCRIPT 1
SSUBS1: .WORD 16 ;SIZE OF SUBSCRIPT 1
SUBS2: .WORD 4 ; SUBSCRIPT 2
ARYADR: .WORD ARY ; ADDRESS OF ARRAY
;THE ARRAY (3 ROWS OF 8 COLUMNS)
220 ARRAY MANIPULATION
. END ; PROGRAM
N-Dimensional Array Index (NDIM) 5G
221
222 ARRAY MANIPULATION
Example
Data: Base address = 3C00, where:
6
Number of dimensions = 03 16
N is the number of dimensions
Rightmost subscript = 0005
l6 SUBSCRIPT, is the fth subscript
Rightmost size = 0003 16 (3-byte entries)
SIZE, is the size of the dimension
Middle subscript = 0003 /th
6
Middle size = 0012, (six 3-byte entries)
6
Leftmost subscript = 0004, Note that we use the sizes of each dimen-
6
Leftmost size = 007E, (seven sets of six
6 sion as parameters to reduce the number of
3-byte entries)
repetitive multiplications and to generalize
Result: Address of entry 3C00 16 + 0005, 6 x the procedure. The sizes can be calculated
0003 16 4- 0003 16 x 0012 + 0004, (and saved) as soon as the bounds of the
16 6
x 007E I6 = 3C00
16 + 000F 16 + 0036 16
+ 01F8, 6 = 3E3D 16 .
array are known. Those sizes can then be
That is, the element is ARRAY (4,3,5); it used whenever indexing is performed on that
occupies addresses 3E3D, through array. Obviously, the sizes do not change if
6
3E3F, 6 The maximum values of the
.
NDIM:
;POP PARAMETERS
PLA
STA RETADR
PLA
STA RETADR+1 ;SAVE RETURN ADDRESS
PLA
STA NOMDIM ;GET NUMBER OF DIMENSIONS
;OFFSET : =
LDA #0
STA OFFSET
STA OFFSET+1
LOOP:
PLA ;POP SIZE
STA SIZE
PLA
STA SIZE+1
STA SS+1
ADBASE:
/•CALCULATE THE STARTING ADDRESS OF THE ELEMENT
OFFSET
; = BASE + OFFSET
PLA ;GET LOW BYTE OF BASE
CLC
ADC OFFSET ;ADD LOW BYTE OF OFFSET
STA OFFSET
PLA ;GET HIGH BYTE OF BASE
ADC OFFSET+1 ;A = HIGH BYTE OF BASE +
STA OFFSET+1 OFFSET
LDA RETADR+1
PHA
LDA RETADR
PHA
LDA OFFSET+1 ; RETURN THE ADDRESS WHICH IS IN OFFSET
LDY OFFSET
RTS
SUBROUTINE NXTOFF
PURPOSE: OFFSET := OFFSET + (SUBSCRIPT *
SIZE)-
''
ENTRY: OFFSET = CURRENT OFFSET
SUBSCRIPT = CURRENT SUBSCRIPT
SIZE = CURREN T SIZE OF THIS DIMENSION
cv Tm
EXIT: OFFSET = OFFSET + (SUBSCRIPT * SIZE)
REGISTERS USED: ALL '
'
NXTOFF
E CK IS P ° WER (E AS Y
u£ "«£! ; high byte = o
MULTIPLICATIONS
?
- SHIFT 0NLY)
BNE BIGSZ ,-BRANCH IF SIZE IS LARGE
LDA SIZE
LDY #0 ;Y=INDEX INTO EASY ARRAY
LDX #SZEASY ;X=SIZE OF EASY ARRAY
EASYLP:
CMP EASYAY,Y
BEQ ISEASY ;BRANCH IF SIZE IS AN EASY ELEMENT
INY ; INCREMENT INDEX
DEX ; DECREMENT
BNE
COUNT
EASYLP ,-BRANCH IF NOT THROUGH ALL EASY ELEMENTS
BEQ BIGSZ ;BRANCH IF SIZE IS NOT EASY
226 ARRAY MANIPULATION
ISEASY:
CPY #0
BEQ ADDOFF ; BRANCH IF SHIFT FACTOR =
DECCNT:
DEX
BNE MULLP
ADDOFF
LDA SS
CLC
ADC OFFSET ;ADD LOW BYTES
STA OFFSET
LDA SS+1
ADC OFFSET+1 ;ADD HIGH BYTES
STA OFFSET+1
RTS
.BYTE 8
.BYTE 16.
.BYTE 32.
.BYTE 64.
.BYTE 128.
SZEASY .EQU 5-EASYAY
.-DATA
RETADR: .BLOCK 2 ; TEMPORARY FOR RETURN ADDRESS
SS: .BLOCK 2 .'SUBSCRIPT INTO THE ARRAY
SIZE: .BLOCK 2 ;SIZE OF AN ARRAY ELEMENT
OFFSET: .BLOCK 2 /TEMPORARY FOR CALCULATING
NUMDIM: .BLOCK 1 .•NUMBER OF DIMENSIONS
PROD: .BLOCK 2 .-TEMPORARY FOR MULTIPLICATION IN NXTOFF
SAMPLE EXECUTION:
PROGRAM SECTION
;
SC0507:
FIND ADDRESS OF AY1 [1,3,0]
SINCE LOWER BOUNDS OF ARRAY 1 ARE ALL ZERO IT IS NOT
NECESSARY TO NORMALIZE THEM
;PUSH BASE ADDRESS OF ARRAY 1
LDA AY1ADR+1
PHA
LDA AY1ADR
PHA
LDA #0
PHA
LDA #0
PHA
LDA #0
PHA
LDA #A1SZ3
PHA
LDA #-1
SEC
SBC IA2D1L
TAX SAVE LOW BYTE
LDA #0FFH HIGH BYTE OF -1 SUBSCRIPT
SBC #0FFH HIGH BYTE OF A2D1L
PHA PUSH HIGH BYTE
TXA
PHA ;PUSH LOW BYTE
LDA #0
PHA
LDA #A2SZ1
PHA
DIMENSION
(SUBSCRIPT - LOWER BOUND) AND SIZE FOR
2
;PUSH
LDA #6
SEC
SBC #A2D2L
TAX ;SAVE LOW BYTE
LDA #0
SBC #0
PHA ;PUSH HIGH BYTE
TXA
PHA ;PUSH LOW BYTE
LDA #0
5G N-DIMENSIONAL ARRAY INDEX (NDIM) 229
PHA
LDA #A2SZ2
PHA
JMP SC0507
; DATA
3
..3,0 • 5 , .. 6 ]
on top of the more significant byte. DataMemory Required: Four bytes anywhere in
memory for the second operand (two bytes start-
Procedure: The program clears the Carry
ing at address ADEND2) and the return
address
flag initially and adds the operands one byte (two bytes starting at address RETADR).
at a time, starting with the less significant
bytes. It sets the Carry flag from the
addition
Examples
= 2. Data: First operand = A45D 16
1. Data: First operand 03E1 16
= Second operand = 97E1, 6
Second operand 07E4 16
230
6A1 6-BIT ADDITION (ADD1 6) 231
Time: 80 cycles
ADD16:
;SAVE THE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
;GET ADDEND 2
PLA
STA ADEND2
PLA
STA ADEND2+1
;SUM ADDEND 2 WITH ADDEND 1
PLA
CLC
ADC ADEND2
TAY ;SAVE LOW BYTE OF SUM
PLA
ADC ADEND2+1
;PUSH THE SUM
PHA ;PUSH HIGH BYTE
TYA
232 ARITHMETIC
•DATA
ADEND2: .BLOCK 2 ;TEMPORARY FOR ADDEND 2
RETADR: .BLOCK 2 ;TEMPORARY FOR RETURN ADDRESS
SAMPLE EXECUTION
SC0601:
;SUM OPRND1 + OPRND2
LDA OPRNDl+1
PHA
LDA OPRND1
PHA
LDA OPRND2+1
PHA
LDA OPRND2
PHA
JSR ADD 16
PLA
TAY
PLA
BRK ;A = HIGH BYTE, Y = LOW BYTE
JMP SC0601
.END ; PROGRAM
16-Bit Subtraction (SUB16) 6B
significant byte on top of the more significant DataMemory Required: Four bytes anywhere in
memory for the subtrahend (two bytes starting at
byte. The subtrahend (number to be address SUBTRA) and the return address (two
subtracted) is stored on top of the minuend bytes starting at address RETADR).
(number from which the subtrahend is
subtracted). The Carry flag acts as an
inverted borrow, its usual role in the 6502.
Procedure: The program sets the Carry flag a time, starting with the less significant bytes.
(the inverted borrow) initially
and subtracts It sets the Carry flag from the subtraction of
the subtrahend from the minuend one byte at the more significant bytes.
Less significant byte of return address Less significant byte of difference (minuend
More significant byte of return address — subtrahend)
Less significant byte of subtrahend More significant byte of difference (minuend
More significant byte of subtrahend — subtrahend)
Examples
1. Data: Minuend = A45D 16 2. Data: Minuend = 03E1, 6
Subtrahend = 97E1, Subtrahend = 07E4, 6
233
234 ARITHMETIC
Time: 80 cycles
SUB16:
;SAVE THE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
;GET SUBTRAHEND
PLA
STA SUBTRA
PLA
STA SUBTRA+1
;DATA
SUBTRA: .BLOCK 2 .-TEMPORARY FOR SUBTRAHEND
RETADR: .BLOCK 2 ; TEMPORARY FOR RETURN ADDRESS
SAMPLE EXECUTION
SC0602:
.-SUBTRACT OPRND2 FROM OPRND1
LDA OPRND1+1
PHA
LDA OPRND1
PHA
LDA OPRND2+1
PHA
LDA OPRND2
PHA
JSR SUB16
PLA
TAY
PLA
BRK ;A = HIGH BYTE, Y = LOW BYTE
JMP SC0602
;TEST DATA - CHANGE TO Tl
OPRND1 .WORD 123
OPRND2 .WORD 1023
.END ; PROGRAM
16-Bit Multiplication (MUL16) 6C
6502 style with the less significant byte on top Program Size: 238 bytes
of the more significant byte. DataMemory Required: Eight bytes anywhere in
Procedure: The program uses an ordinary memory for the multiplicand (two bytes starting
at address MCAND), the multiplier and less sig-
add-and-shift algorithm, adding the multipli- nificant word of the partial product (two bytes
cand to the partial product each time it finds a starting at address the more significant
MLIER),
The partial product word of the product (two bytes starting at
partial
1 bit in the multiplier.
address H1PROD), and the return address (two
and the multiplier are shifted 17 times (the bytes starting at address RETADR).
number of bits in the multiplier plus 1) with
the extra loop being necessary to move the
final Carry into the product. The program
HIPROD, MLIER + 1, and MLIER. The less
maintains a full 32-bit unsigned partial pro- significant word of the product replaces the
locations (starting with the multiplier as the multiplier is shifted and
duct in memory
most significant byte) HIPROD + 1, examined for 1 bits.
Examples
Multiplier = 0012 l6 (18, Data: Multiplier = 37D1, 6 .(14,289, )
1. Data: )
236
6C 16-BIT MULTIPLICATION (MUL16) 237
Note that MUL16 returns only the less user should note that it is correct only if the
significant word of the product to maintain operands are unsigned. If the operands are
compatibility with other 16-bit arithmetic signed numbers and either one is negative,
operations. The more significant word of the the user must determine the sign of the pro-
product is available in memory locations duct and replace negative operands with their
HIPROD (less significant byte) and absolute values (two's complements) before
HIPROD + 1 (more significant byte), but the calling MUL16.
MUL16:
;SAVE, RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
;GET MULTIPLIER
PLA
STA MLIER
PLA
238 ARITHMETIC
STA MLIER+1
;GET MULTIPLICAND
PLA
STA MCAND
PLA
STA MCAND+1
:PERFORM MULTIPLICATION USING THE SHIFT AND ADD ALGORITHM
; THIS ALGORITHM PRODUCES A UNSIGNED 32 BIT PRODUCT IN
; HIPROD AND MLIER WITH HIPROD BEING THE HIGH WORD.
LDA #0
STA HIPROD ;ZERO HIGH WORD OF PRODUCT
STA HIPROD+1
LDX #17 ;NUMBER OF BITS IN MULTIPLIER PLUS 1, THE
; EXTRA LOOP IS TO MOVE THE LAST CARRY INTO
THE PRODUCT
;
MULLP:
1 THEN
IF NEXT BIT =
HIPROD := HIPROD + MULTIPLICAND
ROR HIPROD+1
ROR HIPROD
ROR MLIER+1
ROR MLIER
BCC DECCNT ,-BRANCH IF NEXT BIT OF MULTIPLIER IS
DECCNT:
DEX
BNE MULLP ;CONTINUE UNTIL DONE
;DATA
MCAND: . BLOCK .-MULTIPLICAND
6C 16-BIT MULTIPLICATION (MUL16) 239
SAMPLE EXECUTION:
SC0603:
.-MULTIPLY OPRND1 * OPRND2 AND STORE THE PRODUCT AT PESULT
LDA OPRND1+1
PHA
LDA OPRND1
PHA
LDA OPRND2+1
PHA
LDA OPRND2
PHA
JSR MUL16 ^MULTIPLY
PLA
STA RESULT
PLA
STA RESULT+1
BRK RESULT OF 1023 * -2 = -2046 = 0F802H
IN MEMORY RESULT = 2H
RESULT+1 = F8H
JMP SC0603
OPRND1 • WORD -2
OPRND2 .WORD 1023
RESULT: .BLOCK 2 2 BYTE RESULT
.END ; PROGRAM
16-Bit Division
(SDIV16, UDIV16, SREM16, UREM16) 6D
two 16-bit unsigned operands, SREM16 re- bytes the divisor (starting at address
for
DVSOR); four bytes for the extended dividend
turns a 16-bit remainder (a signed number) (starting at address DVEND) and also for the
from dividing two 16-bit signed operands, quotient and remainder; two bytes for the return
address (starting at address RETADR); one byte
and UREM16 returns a 16-bit unsigned
for the sign of the quotient (address SQUOT);
remainder from dividing two 16-bit unsigned one byte for the sign of the remainder (address
operands. All 16-bit numbers are stored in SREM); and one byte for an index to the result
RSLT1X).
the usual 6502 style with the less significant (address
Special Case If the divisor is zero, the program
byte on top of the more significant byte. The :
cleared.
quotient each time a trial subtraction is suc-
Procedure: If the operands are signed, the
cessful. If the operands are signed, the pro-
program determines the sign of the quotient
and' takes the absolute values of any negative gram must negate (that is, subtract from
operands. It also must retain the sign of the zero) any result (quotient or remainder) that
is negative. The Carry flag is cleared
if the
dividend, since that determines the sign of
The program then performs division proper and set if the divisor is
is
the remainder.
the actual unsigned division by the usual
found to be zero. A zero divisor also results
in a return with the result (quotient or
shift-and-subtract algorithm, shifting quo-
tient and dividend and placing a 1 bit in the
remainder) set to zero.
240
6D 16-BIT DIVISION (SDIV16, UDIV16. SREM16. UREM16) 241
Examples
Data: Dividend = 03E0 l6 = 992 l0 2. Data: Dividend = D73A 16 = -10,438 :o
Divisor = 00B6 I6 = 182 I0 Divisor = 02F1, 6 = 753 10
Result: Quotient (from UDIV16) =» 0005 ]6 Result: Quotient (from SDIV16) = FFF3, 6
Remainder (from UREM16) = 0052 l(
=
-'3,o
= 0082,0 Remainder (from SREM16) = FD77, 6
Carry = (no divide-by-zero error) = -649 10
Carry = (no divide-by-zero error)
Note that we have taken the view that the Regardless of the entry point used, the
remainder of a signed division may be either program always calculates both the quotient
positive or negative. In our procedure, the and the remainder. Upon return, the quo-
remainder always takes the sign of the divi- tient available in addresses DVEND
is and
dend. The user can easily examine the quo- DVEND + 1 (more significant byte in
tient and change the form to obtain a DVEND + 1) and the remainder in addresses
remainder that is always positive. In that DVEND + 2 and DVEND + 3 (more signifi-
case, the final result of Example 2 would be cant byte in DVEND + 3). Thus, the user can
Quotient = FFF2 = -14 l6 l0
always obtain the result that is not returned
Remainder (always positive) = 0068,,
in the stack.
= 104.„
Purpose: SDIV16
Divide 2 signed 16 bit words and return a
16 bit signed quotient.
UDIV16
Divide 2 unsigned 16 bit words and return a
16 bit unsigned quotient.
SREM16
Divide 2 signed 16 bit words and return a
16 bit signed remainder.
UREM16
Divide 2 unsigned 16 bit words and return a
16 bit unsigned remainder.
If no errors then
carry :=
else
divide by zero error
carry := 1
quotient :=
remainder :=
UNSIGNED DIVISION
UDIV16:
LDA #0 ;RESULT IS QUOTIENT (INDEX=0)
BEQ UDIVMD
; UNSIGNED REMAINDER
UREM16:
LDA #2 ; RESULT IS REMAINDER (INDEX=2)
UDIVMD:
STA RSLTIX ; RESULT INDEX (0 FOR QUOTIENT,
2 FOR REMAINDER)
;GET DIVISOR
PLA
STA DVSOR
PLA
STA DVSOR+1
;GET DIVIDEND
PLA
STA DVEND
PLA
6D 16-BIT DIVISION (SDIV16, UDIV16. SREM16. UREM16) 243
STA DVEND+1
PERFORM DIVISION
;
JSR UDIV
BCC DIVOK ; BRANCH IF NO ERRORS
DIVER: JMP EREXIT
DIVOK: JMP OKEXIT
;SIGNED DIVISION
SDIV16:
LDA #0 ;RESULT IS QUOTIENT (INDEX=0)
BEQ SDIVMD
; SIGNED REMAINDER
SREM16:
LDA #2 ;RESULT IS REMAINDER (INDEX=2)
BNE SDIVMD
SDIVMD:
STA RSLTIX ; RESULT INDEX (0 FOR QUOTIENT,
»'
2 FOR REMAINDER)
;GET DIVIDEND
PLA
STA DVEND
PLA
STA DVEND+1
;D M IGN F U0TIEN T
!rru BYTES. IF °THEQ SIGNS BY PERFORMING AN EXCLUSIVE OR OF THE
HIGH ^Lf
; ARE THE SAME THEN BIT 7 WILL BE AND THE
! ?<°^r?™ POSITIVE. IF THE SIGNS ARE DIFFERENT ?HEN She QUOTIENT
LDA DVEND+1
EOR DVSOR+1
STA SQUOT
DOREM:
.-NEGATE REMAINDER IF IT IS NEGATIVE
LDA SREM
BPL OKEXIT .-BRANCH IF REMAINDER IS POSITIVE
LDA #0
SEC
SBC DVEND+2
STA DVEND+2
LDA #0
SBC DVEND+3
STA DVEND+3
JMP OKEXIT
BCS DVEXIT
PHA
LDA DVEND,X
PHA
LDA RETADR+1
PHA
LDA RETADR
PHA
RTS
**************************************
ROUTINE: UDIV
PURPOSE: DIVIDE A 16 BIT DIVIDEND BY A 16 BIT DIVISOR
ENTRY: DVEND = DIVIDEND
DVSOR = DIVISOR
EXIT: DVEND = QUOTIENT
DVEND+2 = REMAINDER
REGISTERS USED: ALL
***********************************
UDIV:
;ZERO UPPER WORD OF DIVIDEND THIS WILL BE CALLED DIVIDEND
[1] BELOW
LDA $0
STA DVEND+2
STA DVEND+3
CHKLT:
SEC
LDA DVEND+2
SBC DVSOR
TAY ;SAVE LOW BYTE IN REG Y
LDA DVEND+3
SBC DVSOR+1 SUBTRACT HIGH BYTES WITH RESULT IN REG A
BCC DECCNT BRANCH IF DIVIDEND < DIVISOR AND CARRY
STY DVEND+2 ELSE
STA DVEND+3 DIVIDEND [1] := DIVIDEND [1] - DIVISOR
DECCNT:
DEX
BNE DIVLP
;DATA
DVSOR: .BLOCK 2 DIVISOR
DVEND: BLOCK . 2 DIVIDEND [0] AND QUOTIENT
BLOCK
'
. 2 DIVIDEND [1] AND REMAINDER
RETADR: .BLOCK 2 RETURN ADDRESS
SQUOT BLOCK . 1 SIGN OF QUOTIENT
SREM: BLOCK 1 SIGN OF REMAINDER
IS QUOTIENT,
.
SAMPLE EXECUTION:
;PROGRAM SECTION
SC0604: QUOTIENT AT QUOT
;Signed DIVIDEf 0pRND1 / OPRND2, STORE THE
LDA OPRNDl+1
PHA
LDA OPRNDl
PHA
LDA OPRND2+1
PHA
LDA OPRND2
PHA
JSR SDIV16 ;SIGNED DIVIDE
PLA
STA QUOT -
PLA
STA QUOT+1
RESULT OF -1023 / 123 = -8
BRK
IN MEMORY QUOT = F8 HEX
QUOT+1 = FF HEX
6D 16-BIT DIVISION (SDIV16. UDIV16. SREM16. UREM16) 247
JMP SC0604
;DATA
OPRNDl .WORD -1023 DIVIDEND (64513 UNSIGNED)
OPRND2 .WORD 123 DIVISOR
QUOT: .BLOCK 2 QUOTIENT
REM: .BLOCK 2 REMAINDER
, END ; PROGRAM
16-Bit Comparison (CMP 16) 6E
Entry Conditions
Order in stack (starting from the top) Exit Conditions
Less significant byte of return address Flags set as if subtrahend had been
More significant byte of return address subtracted from minuend, with a correction if
two's complement overflow occurred.
Less significant byte of subtrahend (top
operand or WORD2) Zero flag = 1 if subtrahend and minuend are
More significant byte of subtrahend equal, if they are not equal.
(top operand or WORD2) Carry flag = if subtrahend is larger than
Less significant byte of minuend (bottom minuend the unsigned sense,
in 1 if it is less
operand or WORD1) than or equal to the minuend.
More significant byte of minuend (bottom Negative flag =
if subtrahend is larger than
1
operand or WORD1) minuend in the
signed sense, if it is less
than or equal to the minuend. This flag is cor-
rected if two's complement overflow occurs.
249
250 ARITHMETIC
Examples
Minuend (bottom operand) = 03E1, 6 3. Data: Minuend (bottom operand) = A45D !6
1. Data:
= 07E4, 6 Subtrahend (top operand) = 77E1 16
Subtrahend (top operand)
= 0, indicating subtrahend is Result: Carry = 1, indicating subtrahend is not
Result: Carry
larger in unsigned sense
larger in unsigned sense.
Subtrahend (top operand) = C51A, 6 two's complement number, whereas the top operand is
a positive two's complement number.
Result: Carry = 1, indicating- subtrahend is not
larger in unsigned sense
CMP16:
;SAVE THE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
;GET SUBTRAHEND
PLA
STA SUBTRA
PLA
STA SUBTRA+1
;GET MINUEND
PLA
STA MINEND
PLA
STA MINEND+1
LDA MINEND
CMP SUBTRA .-COMPARE LOW BYTES
BEQ EQUAL ;BRANCH IF THEY ARE EQUAL
;LOW BYTES ARE NOT EQUAL - COMPARE HIGH BYTES
LDA MINEND+1
SBC SUBTRA+1 ; COMPARE HIGH BYTES
0RA #1 ;MAKE Z = 0, SINCE LOW BYTES ARE NOT EQUAL
BVS OVFLOW ;MUST HANDLE OVERFLOW FOR SIGNED ARITHMATIC
RTS ;EXIT
252 ARITHMETIC
; DATA
SAMPLE EXECUTION
SC0605:
COMPARE OPRNDl AND OPRND2
;
LDA OPRND1+1
PHA
LDA OPRNDl
PHA
LDA OPRND2+1
PHA
LDA OPRND2
PHA
JSR CMP16
BRK ;LOOK AT TH
FOR 123 ; 1
C = 0,
JMP SC0605
.END ; PROGRAM
Multiple-Precision Binary Addition (MPBADD) 6F
253
254 ARITHMETIC
Example
Data: Length of operands = 6
(in bytes)
; EQUATES
MPBADD:
;SAVE RETURN ADDRESS
6F MULTIPLE-PRECISION BINARY ADDITION (MPBADD) 255
PLA
STA RETADR
PLA
STA RETADR+1
; INITIALIZE
LDY #0
CPX #0 ;IS LENGTH OF ARRAYS ?
BEQ EXIT ;YES, EXIT
CLC ; CLEAR CARRY
LOOP:
LDA (AYIPTR) ,Y ;GET NEXT BYTE
ADC (AY2PTR) ,Y ;ADD BYTES
STA (AYIPTR) ,Y ;STORE SUM
INY /•INCREMENT ARRAY INDEX
DEX .-DECREMENT COUNTER
BNE LOOP /•CONTINUE UNTIL COUNTER =
EXIT:
RTS
;DATA
RETADR , BLOCK .-TEMPORARY FOR RETURN ADDRESS
SAMPLE EXECUTION:
SC0606:
256 ARITHMETIC
LDA AY1ADR+1
PHA
LDA AY1ADR
PHA ;PUSH AYl ADDRESS
LDA AY2ADR+1
PHA
LDA AY2ADR
PHA ;PUSH AY2 ADDRESS
LDA ISZAYS
PHA PUSH SIZE OF ARRAYS
JSR MPBADD MULTIPLE-PRECISION BINARY ADDITION
BRK RESULT OF 1234567H + 1234567H = 2468ACEH
IN MEMORY AYl = CEH
AY1+1 = 8AH
AY 1+2 = 4 6H
AY1+3 = 02H
AY 1+4 = 0H
AY1 + 5 = UOH
AY 1+6 = 0H
OMP SC0606
AYl:
.BYTE 067H
.BYTE 04 5H
.BYTE Q23H
.BYTE 001H
.BYTE
.BYTE
.BYTE
AY2:
.BYTE 067H
.BYTE 045H
.BYTE 023H
.BYTE 001H
.BYTE
.BYTE
.BYTE
.END ; PROGRAM
Multiple-Precision Binary Subtraction
(MPBSUB) 6G
257
258 ARITHMETIC
Example
Data: Length of operands (in bytes). = 4
Minuend = 2F5BA7C3, 6
Subtrahend = 14DF35B8, 6
Result: Difference = 1A7C720B 16 .
EQUATES
;PAGE ZERO FOR MINUEND POINTER
;
MPBSUB:
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1
LDA RETADR+1
PHA
LDA RETADR
PHA
; INITIALIZE
LDY #0
CPX #0 ;IS LENGTH OF ARRAYS ?
BEQ EXIT ;YES, EXIT
SEC ;SET CARRY
LOOP:
LDA (MINPTR) ,Y ;GET NEXT BYTE
SBC (SUBPTR) ,Y jSUBTRACT BYTES
STA (MINPTR) ,Y ; STORE DIFFERENCE
INY ; INCREMENT ARRAY INDEX
DEX ; DECREMENT COUNTER
BNE LOOP ; CONTINUE UNTIL COUNTER =
EXIT:
RTS
; DATA
SAMPLE EXECUTION:
260 ARITHMETIC
SC0607:
LDA AY1ADR+1
PHA
LDA AY1ADR
PHA ;PUSH AY1 ADDRESS
LDA AY2ADR+1
PHA
LDA AY2ADR
PHA ;PUSH AY 2 ADDRESS
LDA ISZAYS
PHA PUSH SIZE OF ARRAYS
JSR MPBSUB MULTIPLE-PRECISION BINARY SUBTRACTION
RESULT OF 7654321H 1234567H = 641FDBAH
BRK OBAH
IN MEMORY AYl
AY 1+1 OFDH
AY 1+2 41H
AY 1+3 6H
AY 1+4 OOH
AYl+5 OOH
AY 1+6 OOH
JMP SC0607
AYl:
.BYTE 021H
.BYTE 043H
.BYTE 065H
.BYTE 007H
.BYTE
.BYTE
.BYTE
AY2:
.BYTE 067H
.BYTE 045H
.BYTE 023H
.BYTE 001H
.BYTE
.BYTE
.BYTE
,END ; PROGRAM
Multiple-Precision Binary Multiplication
(MPBMUL) 6H
261
262 ARITHMETIC
Example
Note that MPBMUL returns only the less
Data: Length of operands (in bytes) = 04 significant bytes (that is, the number of bytes
in the multiplicand and multiplier) of the
Top operand (array 2 or multiplicand)
= 0005D1F7, 6 = 381,431,0 product to maintain compatibility with other
multiplier)
multiple-precision binary arithmetic opera-
Bottom operand (array 1 or
= 00000AB1 16 = 2,737 10 tions. The more significant bytes of the pro-
duct are available starting with their least sig-
(array 1) = Bottom nificant byte at address HIPROD. The user
Result: Bottom operand
operand (array 1)« Top operand may need to check those bytes for a possible
(array 2) = 3E39D1C7 16 overflow or extend the operands with addi-
= 1,043,976,647,
tional zeros.
6H MULTIPLE-PRECISION BiNARY MULTIPLICATION (MPBMUL) 263
;EQUATES
AY1PTR: .EQU 0D0H ;PAGE ZERO FOR ARRAY 1 POINTER
AY2PTR: .EQU 0D2H ;PAGE ZERO FOR ARRAY 2 POINTER
MPBMUL:
;SAVE RETURN ADDRESS
PLA
STA RETADR
PLA
STA RETADR+1 ;SAVE RETURN ADDRESS
;GET LENGTH OF ARRAYS
PLA
STA LENGTH
;GET ADDRESS OF ARRAY 2 AND SUBTRACT 1 SO THAT THE ARRAYS MAY
; BE INDEXED FROM 1 TO LENGTH RATHER THAN TO LENGTH-1
PLA
SEC
264 ARITHMETIC
LDA #0
ASL COUNT ;COUNT * 2
ROL A ;A WILL BE UPPER BYTE
ASL COUNT (COUNT * 4
ROL A
ASL COUNT ; COUNT * 8
ROL A
STA COUNT+1 ;STORE UPPER BYTE OF COUNT
INC COUNT (INCREMENT LOW BYTE OF COUNT
BNE ZEROPD
INC COUNT+1 .-INCREMENT HIGH BYTE IF LOW BYTE BECOMES
SRPLP:
ROR HIPROD-l,X ; MINUS 1 FOR INDEXING FROM 1 TO LENGTH
DEX
BNE SRPLP .-CONTINUE UNTIL INDEX =
SHIFT CARRY WHICH IS THE NEXT BIT OF LOWER PRODUCT INTO THE MOST
;
DECCNT:
DEC COUNT ;DECREMENT LOW BYTE OF COUNT
BNE LOOP ;BRANCH IF IT IS NOT ZERO
LDX COUNT+1 ;GET HIGH BYTE
BEQ EXIT ;EXIT IF COUNT IS ZERO
DEX ;ELSE DECREMENT HIGH BYTE OF COUNT
STX COUNT+1
JMP LOOP
EXIT:
RTS
;DATA
RETADR: .BLOCK 2 ; TEMPORARY FOR RETURN ADDRESS
COUNT: .BLOCK 2 .•TEMPORARY FOR LOOP COUNTER
LENGTH: .BLOCK 1 ; LENGTH OF ARRAYS
HI PROD: .BLOCK 255 ;HIGH PRODUCT BUFFER
266 ARITHMETIC
SAMPLE EXECUTION:
SCU608:
LDA AY1ADR+1
PHA
LDA AYlADR
PHA ;PUSH AYl ADDRESS
LDA AY2ADR+1
PHA
LDA AY2ADR
PHA ;PUSH AY2 ADDRESS
LDA #SZAYS
PHA PUSH SIZE OF ARRAYS
JSR MPBMUL MULTIPLE-PRECISION BINARY MULTIPLY
BRK ESULT OF 12345H * 1234H = 14B60404H
IN MEMORY AYl = 04H
AY1+1 = 04H
AY1+2 = B6H
AYl+3 = 14H
AY1+4 = 0H
AY1+5 = 00H
AY 1+6 = 00H
JMP SC0608
AYl:
.BYTE 045H
.BYTE 023H
.BYTE 001H
.BYTE
.BYTE
•BYTE
'0
.BYTE
AY2:
.BYTE 034H
.BYTE 012H
.BYTE
•BYTE
.BYTE
.BYTE
.BYTE
.END ; PROGRAM
Multiple-Precision Binary Division (MPBDIV) 61
Divides two multi-byte unsigned binary Procedure: The program performs division
numbers. Both numbers are stored with their by the usual shift-and-subtract algorithm,
least significant byte at the lowest address. shifting quotient and dividend and placing a 1
The quotient replaces the dividend (the bit in the quotient each time a trial subtrac-
operand with the starting address lower in the tion is successful. An extra buffer
used to is
stack). The length of the numbers (in bytes) hold the result of the trial subtraction and
is 255 or less. The remainder is not returned, that buffer is simply switched with the buffer
but itsstarting address (least significant byte) holding the dividend if the trial subtraction is
is available in memory locations HDEPTR successful. The program exits immediately,
and HDEPTR + 1. The Carry flag is cleared if setting the Carry flag, if it finds the divisor to
no errors occur; if a divide by zero is be zero. The Carry flag is cleared otherwise.
attempted, the Carry flag is set to 1, the divi-
dend is left unchanged, and the remainder is
set to zero.
Registers Used: All RETADR), the loop counter (two bytes starting
Execution Time: Depends on the length of the ataddress COUNT), the length of the operands
operands and on the number of 1 bits in the quo- (one byte at address LENGTH), and the
tient (requiring a buffer switch). If the addresses of the high dividend buffers (two bytes
average
number of the quotient is four per byte,
1 bits in starting at addressAHIDE1 and two bytes start-
the execution time is approximately ing at address AHIDE2). The eight bytes on page
hold pointers to the two operands and to the
480 x LENGTH* + 438 x LENGTH + 208 two temporary buffers for the high dividend. The
cycles where LENGTH the number of bytes pointers start at addresses AY1PTR (00D0
is in 16 in
the listing), AY2PTR (O0D2
the operands. If, for example, LENGTH = 4 (32- in the listing), 16
bit division), the approximate execution time is
HDEPTR (00D4 16 in the listing), and ODEPTR
(00D6 16 in the listing). HDEPTR contains the
480 x + 438 x 4 + 208 =
42
address of the least significant byte of the
480 x 16 + 1752 + 208 = remainder at the conclusion of the program.
7680 + 1960 = 9,640 cycles
Special Cases:
Program Size: 206 bytes 1. A length of zero causes an immediate
exit
Data Memory Required: 519 bytes anywhere in with the Carry flag cleared, the quotient equal to
RAM plus eight bytes on page 0. The 519 bytes the original dividend, and the remainder
anywhere in RAM
are temporary storage for the undefined.
high dividend (255 bytes starting at address 2. A divisor of zero causes an exit with the
HIDED, the result of the trial subtraction (255 Carry flag set to 1, the quotient equal to the origi-
bytes starting at address HIDE2), the return nal dividend, and the remainder equal to zero.
address (two bytes starting at address
267
268 ARITHMETIC
Example
Data: Length of operands (in bytes) = 03
Top operand (array 2 or divisor) - ()00F45 16 = 3,909 10
Bottom operand (array 1 or dividend) = 35A2F7 I6 = 3, 515, 127,
; EQUATES
INITIALIZE
;
STA HDEPTR
LDA AHIDE1+1
STA HDEPTR+1
;SET OTHER HIGH DIVIDEND POINTER TO HIDE2
LDA AHIDE2
STA ODEPTR
LDA AHIDE2+1
STA ODEPTR+1
;CHECK IF DIVISOR IS ZEr6
LDX LENGTH ; LOGICALLY OR ALL BYTES OF DIVISOR
LDY #0
TYA
CHKOLP:
ORA (AY2PTR) ,Y
INY ; INCREMENT INDEX
DEX
BNE CHKOLP ;CONTINUE UNTIL REGISTER X =
CMP #0
BNE DIV ;BRANCH IF DIVISOR IS NOT ZERO
JMP EREXIT ; ELSE EXIT INDICATING ERROR
;DIVIDE USING THE TRIAL SUBTRACTION ALGORITHM
DIV:
CLC ; CLEAR CARRY FOR THE FIRST TIME THROUGH
LOOP:
;SHIFT CARRY INTO LOWER DIVIDEND ARRAY AS THE NEXT BIT
OF QUOTIENT
AND THE MOST SIGNIFICANT BIT OF THE LOWER DIVIDEND TO
;
CARRY.
LDX LENGTH
LDY #0
SLLP1:
LDA (AYlPTR).Y
ROL A .•ROTATE NEXT BYTE
STA (AY1PTR),Y
INY ; INCREMENT THE INDEX
DEX
BNE SLLP1 ; CONTINUE UNTIL REGISTER X =
(HDEPTR) , Y
ROL A
272 ARITHMETIC
STA (HDEPTR) ,Y
INY INCREMENT INDEX
DEX
BNE SLLP2 ;CONTINUE UNTIL REGISTER X =
DIFFERENCE INTO
;SUBTRACT ARRAY 2 FROM HIGH DIVIDEND PLACING THE
]
EXIT:
jARRAY 1 IS THE QUOTIENT
;HDEPTR CONTAINS THE ADDRESS OF THE REMAINDER
RTS
;DATA
RETADR: .BLOCK 2 TEMPORARY FOR RETURN ADDRESS
COUNT: .BLOCK 2 TEMPORARY FOR LOOP COUNTER
LENGTH: .BLOCK 1 LENGTH OF ARRAYS
61 MULTIPLE-PRECISION BINARY DIVISION (MPBDIV) 273
SAMPLE EXECUTION:
SC0609:
LDA AY1ADR+1
PHA
LDA AYIADR
PHA ;PUSH AY1 ADDRESS
LDA AY2ADR+1
PHA
LDA AY2ADR
PHA ;PUSH AY2 ADDRESS
LDA #SZAYS
PHA PUSH SIZE OF ARRAYS
JSR MPBDIV MULTIPLE-PRECISION BINARY DIVIDE
BRK RESULT OF 14B60404H / 1234H = 12345H
IN MEMORY AY1 4 5H
AY1+1 2 3H
AY 1+2 01H
AY1+3 00H
AY 1+4 0H
AY1 + 5 00H
AY1+6 0H
JMP SC0609
SZAYS: .EQU 7 ;SIZE OF ARRAYS
AYIADR: • WORD AY1 ,-ADDRESS OF ARRAY 1 (DIVIDEND)
AY2ADR: .WORD AY2 ADDRESS OF ARRAY
; 2 (DIVISOR)
AY1:
•BYTE 004H
.BYTE 004H
.BYTE 0B6H
.BYTE 014H
.BYTE
•BYTE
•BYTE
AY 2:
.BYTE 034H
.BYTE 012H
.BYTE
.BYTE
274 ARITHMETIC
, BYTE
•BYTE
. BYTE
. END ; PROGRAM